class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = MyUser
        fields = ('url', 'id', 'username', 'email', 'is_staff', 'is_active') class ProjectSerializer(serializers.ModelSerializer):
    class Meta:
        model = Project
        fields = ('url', 'id', 'name', 'desc', 'create_time', 'members') class ImageSerializer(serializers.ModelSerializer):
    class Meta:
        model = Image
        fields = ('id', 'user', 'project', 'name', 'desc', 'version', 'digest',
            'token', 'is_public', 'create_time', 'status') class ApplicationSerializer(serializers.ModelSerializer):
    class Meta:
        model = Application
        fields = ('id', 'user', 'image', 'name', 'replicas', 'resource_limit',
            'session_affinity', 'internal_ip', 'external_ip', 'is_autoscaler',
            'create_time','status') class PortSerializer(serializers.ModelSerializer):
    class Meta:
        model = Port
        fields = ('id', 'app', 'name', 'protocol', 'internal_port',
            'external_port') class ResourceLimitSerializer(serializers.ModelSerializer):
    class Meta:
        model = ResourceLimit
        fields = ('id', 'name', 'cpu', 'cpu_unit', 'memory', 'memory_unit') class VolumeSerializer(serializers.ModelSerializer):
    class Meta:
        model = Volume
        fields = ('id', 'user', 'project', 'app', 'mount_path', 'name', 'desc',
            'capacity', 'capacity_unit', 'status', 'create_time') class AutoScalerSerializer(serializers.ModelSerializer):
    class Meta:
        model = AutoScaler
        fields = ('id', 'app', 'min_replicas', 'max_replicas', 'cpu_target') class EnvironmentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Environment
        fields = ('id', 'app', 'name', 'value') class MyUser(AbstractBaseUser, PermissionsMixin):
    username = models.CharField(max_length=32, unique=True, db_index=True)
    email = models.EmailField('email address', max_length=256)

    is_staff = models.BooleanField('staff status', default=False)
    is_active = models.BooleanField('active', default=True)

    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email']

    objects = MyUserManager()

    class Meta:
        # db_table = 'myuser'
        pass

    def get_full_name(self):
        return self.username

    def get_short_name(self):
        return self.username class Project(models.Model):
    """
    Each User has many projects called project, each project also contains one
    or more images.
    """
    name = models.CharField(max_length=32, default='')
    desc = models.TextField(max_length=256, null=True)
    create_time = models.DateTimeField(auto_now=True)

    members = models.ManyToManyField(MyUser, blank=True) class Image(models.Model):
    """
    Image represents the model of every application, and there are many
    different version images in every application.
    """
    STATUS_CHOICES = (
        ('deleted', 'deleted'),
        ('deleting', 'deleting'),
        ('active', 'active'),
        ('creating', 'creating'),
        ('failed', 'failed')
    )

    user = models.ForeignKey(MyUser,  on_delete=models.CASCADE, null=True,
        default=True)
    project = models.ForeignKey(Project, on_delete=models.CASCADE, null=True,
        default=True)

    name = models.CharField(max_length=32, default='')
    desc = models.TextField(max_length=256, null=True)
    version = models.CharField(max_length=32)
    digest = models.CharField(max_length=64, blank=True, null=True, default='')
    token = models.CharField(max_length=64, blank=True, null=True, default='')
    is_public = models.BooleanField(default=False)
    status = models.CharField(max_length=16, choices=STATUS_CHOICES,
        default='creating')
    create_time = models.DateTimeField(auto_now=True) class Application(models.Model):
    """
    Application represents the running image, which maybe an internal service or
    an external application.
    """
    STATUS_CHOICES = (
        ('creating', 'creating'),
        ('active', 'active'),
        ('down', 'down'),
        ('deleting', 'deleting'),
        ('deleted', 'deleted'),
        ('error', 'error'),
    )

    user = models.ForeignKey(MyUser, on_delete=models.CASCADE, null=True,
        default=True)
    image = models.ForeignKey(Image, blank=True, null=True, on_delete=models.SET_NULL)
    resource_limit = models.ForeignKey(ResourceLimit, on_delete=models.CASCADE)

    name = models.CharField(max_length=32, default='')
    replicas = models.IntegerField()
    session_affinity = models.BooleanField(default=False)
    status = models.CharField(max_length=10, choices=STATUS_CHOICES,
        default='creating')
    internal_ip = models.CharField(max_length=16, blank=True, null=True)
    external_ip = models.CharField(max_length=16, blank=True, null=True)
    create_time = models.DateTimeField(auto_now=True)

    is_autoscaler = models.BooleanField(default=False) class Port(models.Model):
    """
    Ports for the application. The external_port represents the port of external
     service. The internal_port represents the port of internal service.
    """
    PROTOCOL_CHOICES = (
        ('TCP', 'TCP'),
        ('UDP', 'UDP'),
    )

    app = models.ForeignKey(Application, on_delete=models.CASCADE)
    name = models.CharField(max_length=32, default='')
    protocol = models.CharField(max_length=3, choices=PROTOCOL_CHOICES,
        default='TCP')
    external_port = models.IntegerField(blank=True, null=True)
    internal_port = models.IntegerField() class ResourceLimit(models.Model):
    """
    Resource Limit for every pods/containers. Every application should have a
    resource limit. Default: cpu: m, memory: Mi
    """
    MEMORY_SIZE = (
        ('Ki', 'Ki'),
        ('Mi', 'Mi'),
        ('Gi', 'Gi')
    )

    name = models.CharField(max_length=32, default='')
    cpu = models.IntegerField()
    cpu_unit = models.CharField(max_length=4, default='m')
    memory = models.IntegerField()
    memory_unit = models.CharField(max_length=4, choices=MEMORY_SIZE,
        default='Mi') class Volume(models.Model):
    """
    Volume is for persistent storage. It can be mounted on to application and
    umounted.
    """
    CAPACITY_UNIT =(
        ('Ki', 'Ki'),
        ('Mi', 'Mi'),
        ('Gi', 'Gi')
    )

    STATUS_CHOICES = (
        ('creating', 'creating'),
        ('active', 'active'),
        ('deleting', 'deleting'),
        ('deleted', 'deleted'),
        ('error', 'error'),
    )

    user = models.ForeignKey(MyUser, on_delete=models.CASCADE, null=True,
        default=True)
    project = models.ForeignKey(Project, on_delete=models.CASCADE)
    app = models.ForeignKey(Application, blank=True, null=True, on_delete=models.SET_NULL)
    mount_path = models.CharField(max_length=256, null=True, blank=True)
    name = models.CharField(max_length=32)
    desc = models.TextField(max_length=256, null=True)
    capacity = models.IntegerField()
    capacity_unit = models.CharField(max_length=4, choices=CAPACITY_UNIT,
        default='Mi')
    status = models.CharField(max_length=10, choices=STATUS_CHOICES,
        default='creating')
    create_time = models.DateTimeField(auto_now=True) class AutoScaler(models.Model):
    """
    AutoScaler is to auto scale the application.
    """
    app = models.ForeignKey(Application, on_delete=models.CASCADE)
    min_replicas = models.IntegerField()
    max_replicas = models.IntegerField()
    cpu_target = models.IntegerField() class Environment(models.Model):
    """
    Environment variable for application.
    """
    app = models.ForeignKey(Application, on_delete=models.CASCADE)
    name = models.CharField(max_length=32)
    value = models.CharField(max_length=32) def save_upload_file_to_disk(file_obj, filename):
    """
    Save the upload file into disk.
    """
    file_dir = os.path.dirname(filename)
    if not os.path.exists(file_dir):
        os.makedirs(file_dir)

    with open(filename, 'wb+') as destination:
        for chunk in file_obj.chunks():
            destination.write(chunk) def is_image_or_dockerfile(is_image=None):
    """
    Parse the is_image option of the http data.
    1 represents the image file is an image, 0 represents the image
    file is a dockerfile used to build the image, 2 represents the container
    snapshot.
    """
    if not is_image:
        return 0
    if is_image == '1':
        return 1
    elif is_image == '2':
        return 2
    return 0 def get_upload_image_filename(image, user):
    """
    Return the filename of the uploaded image file in disk
    """
    filename = image['name'] + '_' + image['version'] + '.tar'
    return os.path.join(settings.FILE_DIR, user.username, 'images', filename) def get_ports_by_protocol(protocol, ports):
    """
    Get tcp or udp ports from the list of POST data.
    """
    result_ports = {}
    if not ports:
        return result_ports

    for port in ports:
        if port['protocol'] == protocol:
            result_ports[port['name']] = port['port']
    return result_ports def get_upload_volume_filename(volume, user):
    """
    Return the filename of the uploaded volume file in disk.
    """
    filename = volume.name + '.tar'
    return os.path.join(settings.FILE_DIR, user.username, 'volumes',
        volume.project.name, filename) def get_volume_direction_on_nfs(volume):
    """
    Return the direction of the uploaded volume file on nfs.
    """
    return os.path.join(settings.NFS_BASE_DIR, volume.project.name,
        volume.name) def big_file_iterator(filename, chunk_size=512):
    """
    Used to download big file.
    """
    with open(filename, 'rb') as f:
        while True:
            content = f.read(chunk_size)
            if content:
                yield content
            else:
                break def check_member_in_project(project, user):
    """
    Check user whether or not in project.
    """
    if user.is_staff:
        return True

    if user in project.members.all():
        return True
    return False class ImageBuilder(object):
    """
    ImageBuilder is to build image. One way is to use image file directly, the
    other way is to use Dockerfile to build image.

    is_image: 0|1|2, 0 represents build file, 1 represents image file,
        2 represents container snapshot.
    """
    build_file = None
    is_image = 0
    dockerfile = None
    image = None
    user = None

    def __init__(self, build_file, is_image, dockerfile, image_id,
        old_image_name, old_image_version):
        self.build_file = build_file
        self.is_image = is_image
        self.dockerfile = dockerfile
        self.image = Image.objects.get(id=image_id)
        self.user = self.image.user
        self.old_image_name = old_image_name
        self.old_image_version = old_image_version

    def create_image(self):
        """
        Create image by two ways.
        """
        target = None
        if self.is_image != 0:
            target = self._create_image_by_imagefile
        else:
            target = self._create_image_by_dockerfile

        creating_thread = Thread(target=target)
        creating_thread.start()

    def _create_image_by_imagefile(self):
        """
        Create image by imagefile.
        """
        logger.debug("creating an image by imagefile.")

        docker_host = get_optimal_docker_host()
        if not docker_host:
            logger.error("there is no available active docker host.")
            self._update_image_status(status="failed")
            return None

        # TODO: create image on docker host
        base_url = self._get_docker_host_base_url(docker_host)
        image_name = self._get_image_name()

        if self.is_image == 1:
            token = self._load_image_on_docker_host(base_url, self.build_file,
                image_name, self.image.version)
        elif self.is_image == 2:
            token = self._import_snapshot_on_docker_host(base_url,
                self.build_file, image_name, self.image.version)

        if not token:
            logger.error("Import image on docker host failed")
            self._update_image_status(status="failed")
            return None
        logger.info('Image %s:%s has been imported, with token %s', image_name,
            self.image.version, token)

        digest = self._push_image_to_registry(base_url, image_name,
            self.image.version, token)
        if not digest:
            logger.error("Push image from docker host to registry failed")
            self._update_image_status(status="failed")
            return None
        logger.info('Image %s:%s has been pushed to registry, with digest %s',
            image_name, self.image.version, digest)

        self._update_image_status(status="active", digest=digest, token=token)

        remove_file_from_disk(self.build_file)

    def _create_image_by_dockerfile(self):
        """
        Create image by dockerfile, this maybe take a long time.
        """
        logger.debug("creating an image by dockerfile.")

        docker_host = get_optimal_docker_host()
        if not docker_host:
            logger.error("there is no available active docker host.")
            self._update_image_status(status="failed")
            return None

        base_url = self._get_docker_host_base_url(docker_host)
        image_name = self._get_image_name()

        logger.debug('%s %s' % (base_url, image_name))

        token = self._build_image_on_docker_host(
            base_url=base_url,
            build_file=self.build_file,
            dockerfile=self.dockerfile,
            image_name=image_name,
            image_version=self.image.version)
        if not token:
            logger.error("Build image on docker host failed")
            self._update_image_status(status="failed")
            return None
        logger.info('Image %s:%s has been builded, with token %s', image_name,
            self.image.version, token)

        digest = self._push_image_to_registry(base_url, image_name,
            self.image.version, token)
        if not digest:
            logger.error("Push image from docker host to registry failed")
            self._update_image_status(status="failed")
            return None
        logger.info('Image %s:%s has been pushed to registry, with digest %s',
            image_name, self.image.version, digest)

        self._update_image_status(status="active", digest=digest, token=token)

        remove_file_from_disk(self.build_file)


    def _update_image_status(self, status, digest=None, token=None):
        """
        Update image metadata after building the image.
        """
        self.image.status = status
        if digest:
            self.image.digest = digest
        if token:
            self.image.token = token
        self.image.save()

    def _get_build_docker_host(self):
        """
        Returns the optimal docker host to build image.
        """
        scheduler = DockerSchedulerFactory.get_scheduler()
        docker_host = scheduler.get_optimal_docker_host()
        logger.debug("select the optimal docher host %s" % docker_host)
        return docker_host

    def _get_docker_host_base_url(self, host):
        """
        Returns the base url of docker host.
        """
        return 'tcp://%s:%s' % (host, str(settings.DOCKER_PORT))

    def _get_image_name(self):
        """
        Returns the complete name of the build image.
        """
        return '{}/{}/{}-{}'.format(settings.IMAGE_REGISTRY, self.user.username,
            self.image.project.name, self.image.name)

    def _load_image_on_docker_host(self, base_url, build_file, image_name,
                image_version='latest'):
        """
        Import container snapshot on the selected docker host.

        'base_url': the url of docker host.
        'build_file': the name of the build file in absolute path.
        'image_name': the name of the image, containing registry address, user
        name and image name.
        'image_version': the version of the image.

        Returns:
        'token': the image token
        """
        self._delete_image_on_docker_host(base_url, self.old_image_name,
            self.old_image_version)
        self._delete_image_on_docker_host(base_url, image_name, image_version)

        client = Client(base_url=base_url)
        try:
            with open(build_file, 'rb') as fileobj:
                client.load_image(fileobj)
        except Exception:
            logger.error('load image file on docker host %s failed.' % base_url)
            return None

        return self._tag_image_with_new_name(base_url, self.old_image_name,
            self.old_image_version, image_name, image_version)

    def _import_snapshot_on_docker_host(self, base_url, build_file, image_name,
                image_version='latest'):
        """
        Import container snapshot on the selected docker host.

        'base_url': the url of docker host.
        'build_file': the name of the build file in absolute path.
        'image_name': the name of the image, containing registry address, user
        name and image name.
        'image_version': the version of the image.

        Returns:
        'token': the image token
        """
        self._delete_image_on_docker_host(base_url, image_name, image_version)

        client = Client(base_url=base_url)
        try:
            res_json = client.import_image_from_file(build_file, image_name,
                image_version)
            res = json.loads(res_json)
        except Exception:
            logger.error('import snapshot on docker host %s failed.' % base_url)
            return None

        return res.get('status', None)

    def _delete_image_on_docker_host(self, base_url, image_name, image_version):
        """
        Delete image from docker host if exists image called
        image_name:image_version.
        """
        image_complete_name = '%s:%s' %(image_name, image_version)
        client = Client(base_url=base_url)
        try:
            client.remove_image(image=image_complete_name, force=True)
        except Exception:
            logger.info('There is no image called %s on docker host %s' %
                (image_complete_name, base_url))
            return None

        logger.info('Image %s on docker host %s has been deleted.' %
                (image_complete_name, base_url))

    def _push_image_to_registry(self, base_url, image_name, image_version,
        image_token):
        """
        Push image from docker host to private registry.

        Returns the sha256 digest of the image.
        """
        image_complete_name = '%s:%s' %(image_name, image_version)

        if not self._is_image_on_docker_host(base_url, image_token):
            logger.error('There is no image called %s on docker host %s' %
                (image_complete_name, base_url))
            return None

        client = Client(base_url=base_url)
        try:
            response = [res for res in client.push(image_complete_name,
                stream=True)]
        except Exception:
            logger.error('Communicate with %s failed.' % base_url)
            return None

        try:
            digest = fetch_digest_from_response(response[-1])
        except Exception:
            logger.error('Parse the digest response error.')
            return None

        return digest

    def _is_image_on_docker_host(self, base_url, image_token):
        """
        Check the image whether or not on docker host.
        """
        client = Client(base_url=base_url)
        try:
            response = client.images(quiet=True)
        except Exception:
            logger.error("Connected %s failed." % base_url)
            return False
        if image_token not in response:
            return False
        return True

    def _tag_image_with_new_name(self, base_url, old_image_name,
            old_image_version, image_name, image_version):
        """
        Docker tag old_image_name:old_image_version image_name:image_version.
        """
        client = Client(base_url=base_url)
        old_image = "{}:{}".format(old_image_name, old_image_version)
        try:
            response = client.tag(image=old_image, repository=image_name,
                tag=image_version)
        except Exception as e:
            logger.debug(e)
            response = False
        if not response:
            logger.info("Tag image {} to {}:{} failed.".format(old_image,
                image_name, image_version))
            return None

        image_token = self._get_image_token_on_docker_host(base_url,
            image_name, image_version)

        self._delete_image_on_docker_host(base_url, old_image_name,
            old_image_version)

        return image_token

    def _get_image_token_on_docker_host(self, base_url, image_name,
        image_version):
        """
        Given the image name and version, return the token of the image on the
        docker host.
        """
        image_complete_name = '%s:%s' %(image_name, image_version)

        logger.debug(image_complete_name)

        client = Client(base_url=base_url)
        try:
            images = client.images()
        except Exception as e:
            logger.debug(e)
            logger.debug("Communicate with docker host {} failed.".format(
                base_url))
            return None

        tokens = [image['Id'] for image in images
            if image_complete_name in image['RepoTags']]

        if not tokens:
            logger.info("The docker host {} has no image {}:{}".format(base_url,
                image_name, image_version))
            return None
        return tokens[0]

    def _build_image_on_docker_host(self, base_url, build_file, dockerfile,
            image_name, image_version):
        """
        Build image on the selected docker host by Dockerfile.

        'base_url': the url of docker host.
        'build_file': the name of the build file in absolute path.
        'dockerfile': Dockerfile path in build_file.
        'image_name': the name of the image, containing registry address, user
        name and image name.
        'image_version': the version of the image.

        Returns:
        'token': the image token
        """
        self._delete_image_on_docker_host(base_url, image_name, image_version)

        client = Client(base_url=base_url)
        fileobj = open(build_file, 'rb')
        image_complete_name = '%s:%s' % (image_name, image_version)
        try:
            response = [line for line in client.build(
                fileobj=fileobj,
                custom_context=True,
                dockerfile=dockerfile,
                rm=True,
                tag=image_complete_name)]
        except APIError as error:
            logger.debug(error)
            logger.error('Cannot locate specified Dockerfile: %s.' %
                (self.dockerfile))
            fileobj.close()
            return None
        except Exception as error:
            logger.debug(error)
            logger.error('Build image %s failed.' % image_complete_name)
            fileobj.close()
            return None
        fileobj.close()

        return self._get_image_token(base_url, image_complete_name)

    def _get_image_token(self, base_url, image_complete_name):
        """
        """
        client = Client(base_url=base_url)
        try:
            token = client.inspect_image(image_complete_name).get('Id', None)
        except Exception:
            logger.error('Can\'t get the token of image %s on docker host %s' %
                (image_complete_name, base_url))
            return None
        return token class ImageDestroyer(object):
    """
    ImageDestroyer is to destroy image instance by multiple threading.
    """
    image = None

    def __init__(self, image):
        self.image = image

    def destroy_image_instance(self):
        deleting_thread = Thread(target=self._destroy_image_instance)
        deleting_thread.start()

    def _destroy_image_instance(self):
        self._update_image_status(status='deleting')

        # TODO: delete the image instance

        self._delete_image_instance_on_all_hosts()
        self._delete_image_metadata()

    def _update_image_status(self, status):
        self.image.status = status
        self.image.save()

    def _delete_image_metadata(self):
        self.image.delete()

    def _delete_image_instance_on_all_hosts(self):
        """
        Delete image instance on all hosts.
        """
        image_name = self._get_image_name()
        image_version = self.image.version
        scheduler = DockerSchedulerFactory.get_scheduler()
        hosts = scheduler.get_docker_hosts()
        for host in hosts:
            base_url = self._get_docker_host_base_url(host)
            self._delete_image_on_docker_host(base_url, image_name,
                image_version)

    def _get_image_name(self):
        """
        Returns the complete name of the image.
        """
        return '{}/{}/{}-{}'.format(settings.IMAGE_REGISTRY,
            self.image.user.username,
            self.image.project.name, self.image.name)

    def _delete_image_on_docker_host(self, base_url, image_name, image_version):
        """
        Delete image from docker host if exists image called
        image_name:image_version.
        """
        image_complete_name = '%s:%s' %(image_name, image_version)
        client = Client(base_url=base_url)
        try:
            client.remove_image(image=image_complete_name, force=True)
        except Exception:
            logger.info('There is no image called %s on docker host %s' %
                (image_complete_name, base_url))
            return None

        logger.info('Image %s on docker host %s has been deleted.' %
                (image_complete_name, base_url))

    def _get_docker_host_base_url(self, host):
        """
        Returns the base url of docker host.
        """
        return 'tcp://%s:%s' % (host, str(settings.DOCKER_PORT)) class ImageCloner(object):
    """
    ImageCloner is to clone a public image into private project.
    """
    public_image = None
    private_image = None

    def __init__(self, puid, prid):
        self.public_image = Image.objects.get(id=puid)
        self.private_image = Image.objects.get(id=prid)

    def clone_image(self):
        """
        Clone public image into private project on docker host, and then repush
        to registry.
        """
        clone_thread = Thread(target=self._clone_public_image())
        clone_thread.start()

    def _clone_public_image(self):
        public_image_name = self._get_image_name(self.public_image)
        private_image_name = self._get_image_name(self.private_image)

        logger.debug("public image: {}:{}".format(public_image_name,
            self.public_image.version))
        logger.debug("private image: {}:{}".format(private_image_name,
            self.private_image.version))

        base_url = self._get_docker_host_base_url()
        if not base_url:
            logger.error("Clone public image {}:{} to {}:{} failed.".format(
                public_image_name, self.public_image.version,
                private_image_name, self.private_image.version))
            return None

        self._delete_image_on_docker_host(base_url, public_image_name,
            self.public_image.version)
        self._delete_image_on_docker_host(base_url, private_image_name,
            self.private_image.version)

        try:
            self._pull_image_to_docker_host(base_url, public_image_name,
                self.public_image.version)
        except Exception:
            logger.error("Pull image {}:{} from registry failed.".format(
                public_image_name, self.public_image.version))
            self._update_image_status(status="failed")
            return None

        token = self._tag_image_with_new_name(base_url,
            public_image_name, self.public_image.version,
            private_image_name, self.private_image.version)
        if not token:
            logger.error("Clone public image {}:{} to {}:{} failed.".format(
                public_image_name, self.public_image.version,
                private_image_name, self.private_image.version))
            self._update_image_status(status="failed")
            return None

        digest = self._push_image_to_registry(base_url,
            private_image_name, self.private_image.version, token)
        if not digest:
            logger.error("Clone public image {}:{} to {}:{} failed.".format(
                public_image_name, self.public_image.version,
                private_image_name, self.private_image.version))
            self._update_image_status(status="failed")
            return None

        logger.info("Clone public image {}:{} to {}:{} successfully.".format(
                public_image_name, self.public_image.version,
                private_image_name, self.private_image.version))

        self._update_image_status(status="active", digest=digest, token=token)

    def _get_image_name(self, image):
        """
        Returns the complete name of the image in registry.
        """
        return '{}/{}/{}-{}'.format(settings.IMAGE_REGISTRY,
            image.user.username, image.project.name, image.name)

    def _get_docker_host_base_url(self):
        """
        Returns the optimal base url of docker host.
        """
        docker_host = get_optimal_docker_host()
        if not docker_host:
            logger.error("there is no available active docker host.")
            self._update_image_status(status="failed")
            return None
        return 'tcp://%s:%s' % (docker_host, str(settings.DOCKER_PORT))

    def _update_image_status(self, status, digest=None, token=None):
        """
        Update image metadata after clone the image.
        """
        self.private_image.status = status
        if digest:
            self.private_image.digest = digest
        if token:
            self.private_image.token = token
        self.private_image.save()

    def _delete_image_on_docker_host(self, base_url, image_name, image_version):
        """
        Delete image from docker host if exists image called
        image_name:image_version.
        """
        image_complete_name = '%s:%s' %(image_name, image_version)
        client = Client(base_url=base_url)
        try:
            client.remove_image(image=image_complete_name, force=True)
        except Exception:
            logger.info('There is no image called %s on docker host %s' %
                (image_complete_name, base_url))
            return None

        logger.info('Image %s on docker host %s has been deleted.' %
                (image_complete_name, base_url))

    def _pull_image_to_docker_host(self, base_url, image_name, image_version):
        """
        Pull image from private registry to docker host.
        """
        client = Client(base_url=base_url)
        response = [line for line in client.pull(repository=image_name,
                tag=image_version, stream=True)]

    def _tag_image_with_new_name(self, base_url, old_image_name,
            old_image_version, image_name, image_version):
        """
        Docker tag old_image_name:old_image_version image_name:image_version.
        """
        client = Client(base_url=base_url)
        old_image = "{}:{}".format(old_image_name, old_image_version)
        try:
            response = client.tag(image=old_image, repository=image_name,
                tag=image_version)
        except Exception as e:
            logger.debug(e)
            response = False
        if not response:
            logger.info("Tag image {} to {}:{} failed.".format(old_image,
                image_name, image_version))
            return None

        image_token = self._get_image_token_on_docker_host(base_url,
            image_name, image_version)

        self._delete_image_on_docker_host(base_url, old_image_name,
            old_image_version)

        return image_token

    def _push_image_to_registry(self, base_url, image_name, image_version,
        image_token):
        """
        Push image from docker host to private registry.

        Returns the sha256 digest of the image.
        """
        image_complete_name = '%s:%s' %(image_name, image_version)

        client = Client(base_url=base_url)
        try:
            response = [res for res in client.push(image_complete_name,
                stream=True)]
        except Exception:
            logger.error('Push image %s to registry failed.' %
                image_complete_name)
            return None

        try:
            digest = fetch_digest_from_response(response[-1])
        except Exception:
            logger.error('Parse the digest response error.')
            return None

        return digest

    def _get_image_token_on_docker_host(self, base_url, image_name,
        image_version):
        """
        Given the image name and version, return the token of the image on the
        docker host.
        """
        image_complete_name = '%s:%s' %(image_name, image_version)

        logger.debug(image_complete_name)

        client = Client(base_url=base_url)
        try:
            images = client.images()
        except Exception as e:
            logger.debug(e)
            logger.debug("Communicate with docker host {} failed.".format(
                base_url))
            return None

        tokens = [image['Id'] for image in images
            if image_complete_name in image['RepoTags']]

        if not tokens:
            logger.info("The docker host {} has no image {}:{}".format(base_url,
                image_name, image_version))
            return None
        return tokens[0] class ApplicationBuilder(object):
    """
    ApplicationBuilder is a builder to create an appliction from an image. You
    should offer many necessary arguments.

    Parameters:
    image_name: the image name for the container.
    tcp_ports: a dict, the tcp ports of the containers. For example: {
        "http": 80, "https": 443}
    udp_ports: a dict, the udp ports of the containers.
    commands: the commands which the container runs when start.
    envs: a dict for example: {"MYSQL_HOST": "localhost", "PORT": "3306"}
    volumes: a dict, for example: [{"volume": id, "mount_path": path}]
    """
    namespace = None
    application = None
    image_name = None
    tcp_ports = None
    udp_ports = None
    commands = None
    args = None
    envs = None
    is_public = False
    volumes = None

    def __init__(self, application, image_name, tcp_ports=None,
        udp_ports=None, commands=None, args=None, envs=None, is_public=False,
        volumes=None, min_replicas=None, max_replicas=None, cpu_target=None):
        self.kubeclient = KubeClient("http://{}:{}{}".format(settings.MASTER_IP,
            settings.K8S_PORT, settings.K8S_API_PATH))

        self.application = application
        self.namespace = self.application.image.project.name
        self.application_name = get_application_instance_name(self.application)
        self.image_name = image_name
        self.tcp_ports = tcp_ports
        self.udp_ports = udp_ports
        self.commands = commands
        self.args = args
        self.envs = envs
        self.is_public = is_public
        self.volumes = volumes

        # Compute the resource limit of the application
        resource_limit = self.application.resource_limit
        self.cpu = str(resource_limit.cpu) + resource_limit.cpu_unit
        self.memory = str(resource_limit.memory) + resource_limit.memory_unit

        # autoscaler
        if self.application.is_autoscaler:
            self.min_replicas = int(min_replicas)
            self.max_replicas = int(max_replicas)
            self.cpu_target = int(cpu_target)
            logger.debug(str(self.min_replicas) + " " + str(self.max_replicas)
                + " " + str(self.cpu_target))

    def create_application(self):
        """
        Create application by multiple threading.
        """
        creating_thread = Thread(target=self._create_application)
        creating_thread.start()

    def _create_application(self):
        """
        First create a replicationcontroller, then create a service, update
        database at last.
        """
        logger.info('Create an application {} in namespace {} by image {}.'
            .format(self.application_name, self.namespace, self.image_name))

        if not self._create_controller():
            logger.debug('Create controller {} failed.'.format(
                self.application_name))
            logger.info('Create an application {} in namespace {} failed.'
            .format(self.application_name, self.namespace, self.image_name))

            self._update_application_metadata(status='error')
            return None

        if self.tcp_ports or self.udp_ports:
            if not self._create_service():
                logger.debug('Create service {} failed.'.format(
                    self.application_name))
                logger.info('Create an application {} in namespace {} failed.'
                .format(self.application_name, self.namespace, self.image_name))

                self._update_application_metadata(status='error')
                return None
            internal_ip, ports = self._get_service_ip_and_ports()
        else:
            internal_ip = None
            ports = None

        # update metadata
        if self.is_public:
            external_ip = get_optimal_external_ip(namespace=self.namespace,
                app_name=self.application_name)
            self._update_application_metadata(status='active',
                internal_ip=internal_ip,
                external_ip=external_ip
            )
        else:
            self._update_application_metadata(status='active',
                internal_ip=internal_ip
            )

        # mount volume
        self._mount_volume_onto_application()

        # create port metadata
        self._create_ports_metadata(ports)

        # create environment metadata
        self._create_environments_metadata()

        # create autoscaler
        if self.application.is_autoscaler:
            scaler_builder = AutoScalerBuilder(
                application=self.application,
                min_replicas=self.min_replicas,
                max_replicas=self.max_replicas,
                cpu_target=self.cpu_target
            )
            scaler_builder.create_autoscaler()

    def _create_controller(self):
        """
        Create a replicationcontroller by provided image.
        """
        return self.kubeclient.create_controller(namespace=self.namespace,
            name=self.application_name,
            image_name=self.image_name,
            cpu=self.cpu,
            memory=self.memory,
            replicas=self.application.replicas,
            tcp_ports=self.tcp_ports,
            udp_ports=self.udp_ports,
            commands=self.commands,
            args=self.args,
            envs=self.envs,
            volumes=self._get_volume_names_and_path()
        )

    def _create_service(self):
        """
        Create a service on the replicationcontroller.
        """
        return self.kubeclient.create_service(namespace=self.namespace,
            name=self.application_name,
            tcp_ports=self.tcp_ports,
            udp_ports=self.udp_ports,
            is_public=self.is_public,
            session_affinity=self.application.session_affinity
        )

    def _get_service_ip_and_ports(self):
        """
        Get internal_ip and ports of the service.
        """
        response = self.kubeclient.get_service_details(self.namespace,
            self.application_name)
        return (response['spec']['clusterIP'], response['spec']['ports'])

    def _update_application_metadata(self, status=None, internal_ip=None,
        external_ip=None):
        """
        Update the application metadata.
        """
        if status:
            self.application.status = status
        if internal_ip:
            self.application.internal_ip = internal_ip
        if external_ip:
            self.application.external_ip = external_ip

        self.application.save()

    def _create_ports_metadata(self, ports):
        """
        Create ports metadata for application.
        """
        if not ports:
            return None

        for port_dict in ports:
            port = Port(app=self.application,
                name=port_dict['name'],
                protocol=port_dict['protocol'],
                external_port=port_dict.get('nodePort', None),
                internal_port=port_dict['port']
            )
            port.save()

    def _create_environments_metadata(self):
        """
        Create environment metadata for application.
        """
        if not self.envs:
            return None

        for name, value in self.envs.items():
            env = Environment(app=self.application,
                name=name, value=value)
            env.save()

    def _mount_volume_onto_application(self):
        """
        Edit the metadata, Mount volumes onto this application.
        """
        if not self.volumes:
            return None
        for volume_item in self.volumes:
            volume = Volume.objects.get(id=volume_item['volume'])
            logger.debug("mount volume {} onto application {}.".format(
                volume.name, self.application_name))
            volume.app = self.application
            volume.mount_path = volume_item['mount_path']
            volume.save()

    def _get_volume_names_and_path(self):
        """
        Return the volume names from volume ids. The true volume instance name
        is "project_name-volume_name".
        """
        if not self.volumes:
            return None
        volume_name_path = {}
        for volume_item in self.volumes:
            volume = Volume.objects.get(id=int(volume_item['volume']))
            volume_name = "{}-{}".format(self.namespace, volume.name)
            volume_name_path[volume_name] = volume_item['mount_path']
        logger.debug(volume_name_path)
        return volume_name_path class ApplicationDestroyer(object):
    """
    ApplicationDestroyer is to destroy application instance, including controller
    and service.
    """
    application = None

    def __init__(self, application):
        self.application = application
        self.application_name = get_application_instance_name(self.application)
        self.namespace = self.application.image.project.name
        self.service_name = self.application_name
        self.controller_name = self.application_name

        self.kubeclient = KubeClient("http://{}:{}{}".format(settings.MASTER_IP,
            settings.K8S_PORT, settings.K8S_API_PATH))

    def destroy_application_instance(self):
        """
        Destroy application instance by multiple threading.
        """
        deleting_thread = Thread(target=self._destroy_application_instance)
        deleting_thread.start()

    def _destroy_application_instance(self):
        self._update_application_status(status='deleting')

        if not self._destroy_service_instance():
            logger.debug("Delete service {} failed.".format(self.service_name))

        if not self._destroy_controller_instance():
            logger.debug("Delete controller {} failed.".format(
                self.controller_name))

        if not self._destroy_pods_instance():
            logger.debug("Delete pods with label 'app={}' failed.".format(
                self.controller_name))

        self._update_application_status(status='deleted')
        self._umount_volume()

        # delete autoscaler
        if self.application.is_autoscaler:
            autoscaler_destroyer = AutoScalerDestroyer(self.application)
            autoscaler_destroyer.delete_autoscaler()

        self._delete_application_metadata()
        self._delete_port_metadata()
        self._delete_environments()

    def _destroy_service_instance(self):
        return self.kubeclient.delete_service(namespace=self.namespace,
                name=self.service_name)

    def _destroy_controller_instance(self):
        return self.kubeclient.delete_controller(namespace=self.namespace,
                name=self.controller_name)

    def _destroy_pods_instance(self):
        pods = self.kubeclient.list_pods(namespace=self.namespace,
            label="app={}".format(self.controller_name))
        logger.debug(pods)

        res = True
        for pod in pods:
            flag = self.kubeclient.delete_pod(namespace=self.namespace,
                name=pod)
            if not flag:
                res = False
        return res

    def _update_application_status(self, status):
        self.application.status = status
        self.application.save()

    def _delete_application_metadata(self):
        logger.debug("delete application medatada.")
        self.application.delete()

    def _delete_port_metadata(self):
        logger.debug("delete port metadata.")
        ports = Port.objects.filter(app=self.application)
        for port in ports:
            logger.debug("delete port {} metadata.".format(port.name))
            port.delete()

    def _delete_environments(self):
        envs = Environment.objects.filter(app=self.application)
        for env in envs:
            env.delete()

    def _umount_volume(self):
        """
        Umount volumes which mounted on application.
        """
        volumes = Volume.objects.filter(app=self.application)
        for volume in volumes:
            logger.debug("umount volume {}-{} from application {}.".format(
                volume.project.name, volume.name, self.application_name))
            volume.app = None
            volume.mount_path = None
            volume.save() class VolumeBuilder(object):
    """
    VolumeBuilder is a builder to create an volume for persistent data storagy.

    """
    volume = None

    def __init__(self, volume):
        self.kubeclient = KubeClient("http://{}:{}{}".format(settings.MASTER_IP,
            settings.K8S_PORT, settings.K8S_API_PATH))

        self.volume = volume
        self.namespace = self.volume.project.name
        self.project_name = self.volume.project.name
        self.volume_name = self.volume.name
        self.capacity = str(self.volume.capacity) + self.volume.capacity_unit
        self.nfs_path = get_volume_nfs_dir(settings.NFS_BASE_DIR,
            self.namespace, self.volume.name)
        self.nfs_server = settings.NFS_IP

    def create_volume(self):
        """
        Create volume by multiple threading.
        """
        creating_thread = Thread(target=self._create_volume)
        creating_thread.start()

    def _create_volume(self):
        """
        First create a persistentvolume, then create a persistentvolumeclaim.
        """
        logger.info('User {} create a volume {} in project {}.'.format(
            self.volume.user.username, self.volume.name, self.project_name))

        # create volume dir on nfs server
        if self._create_volume_dir_on_nfs():
            logger.info("Create dir {} on nfs server {} successfully.".format(
                self.nfs_path, self.nfs_server))
        else:
            logger.info("Create dir {} on nfs server {} failed.".format(
                self.nfs_path, self.nfs_server))
            self._update_volume_status(status='error')
            return None

        # create persistentvolume
        if self._create_persistentvolume():
            logger.info("Create persistentvolume {}-{} successfully.".format(
                self.namespace, self.volume_name))
        else:
            logger.info("Create persistentvolume {}-{} failed.".format(
                self.namespace, self.volume_name))
            self._update_volume_status(status='error')
            return None

        # create persistentvolumeclaim
        if self._create_persistentvolumeclaim():
            logger.info("Create persistentvolumeclaim {} successfully.".format(
                self.volume_name))
        else:
            logger.info("Create persistentvolumeclaim {} failed.".format(
                self.volume_name))
            self._update_volume_status(status='error')
            return None

        self._update_volume_status(status='active')

    def _create_volume_dir_on_nfs(self):
        """
        Create direction on nfs server to store volume data.
        """
        client = NFSLocalClient()
        client.removedir(self.nfs_path)
        client.makedir(self.nfs_path)
        return True

    def _create_persistentvolume(self):
        return self.kubeclient.create_persistentvolume(
            namespace=self.namespace,
            name=self.volume_name,
            capacity=self.capacity,
            nfs_path=self.nfs_path,
            nfs_server=self.nfs_server
            )

    def _create_persistentvolumeclaim(self):
        return self.kubeclient.create_persistentvolumeclaim(
            namespace=self.namespace,
            name=self.volume_name,
            capacity=self.capacity
            )

    def _update_volume_status(self, status):
        self.volume.status = status
        self.volume.save() class VolumeDestroyer(object):
    """
    VolumeDestroyer is to destroy volume instance, including volume direction,
    persistentvolume and persistentvolumeclaim.
    """
    volume = None

    def __init__(self, volume):
        self.kubeclient = KubeClient("http://{}:{}{}".format(settings.MASTER_IP,
            settings.K8S_PORT, settings.K8S_API_PATH))

        self.volume = volume
        self.namespace = self.volume.project.name
        self.project_name = self.volume.project.name
        self.volume_name = self.volume.name
        self.capacity = str(self.volume.capacity) + self.volume.capacity_unit
        self.nfs_path = get_volume_nfs_dir(settings.NFS_BASE_DIR,
            self.namespace, self.volume.name)
        self.nfs_server = settings.NFS_IP

    def destroy_volume(self):
        """
        Destroy volume instance by multiple threading.
        """
        deleting_thread = Thread(target=self._destroy_volume_instance)
        deleting_thread.start()

    def _destroy_volume_instance(self):
        logger.info('User {} delete volume {} in project {}.'.format(
            self.volume.user.username, self.volume.name, self.project_name))

        self._update_volume_status(status='deleting')

        if not self._remove_volume_dir_on_nfs():
            logger.debug("remove direction {} on nfs server failed.".format(
                self.nfs_path))
        else:
            logger.info("remove direction {} on nfs server successfully."
                .format(self.nfs_path))

        if not self._delete_persistentvolumeclaim():
            logger.debug("delete persistentvolumeclaim {} failed.".format(
                self.volume_name))
        else:
            logger.debug("delete persistentvolumeclaim {} successfully."
                .format(self.volume_name))

        if not self._delete_persistentvolume():
            logger.debug("delete persistentvolume {}-{} failed.".format(
                self.namespace, self.volume_name))
        else:
            logger.debug("delete persistentvolume {}-{} successfully.".format(
                self.namespace, self.volume_name))

        # self._update_volume_status(status='deleted')
        self._delete_volume_metadata()

    def _remove_volume_dir_on_nfs(self):
        """
        Remove direction on nfs server.
        """
        client = NFSLocalClient()
        client.removedir(self.nfs_path)
        return True

    def _delete_persistentvolumeclaim(self):
        return self.kubeclient.delete_persistentvolumeclaim(
            namespace=self.namespace, name=self.volume_name)

    def _delete_persistentvolume(self):
        return self.kubeclient.delete_persistentvolume(
            namespace=self.namespace, name=self.volume_name)

    def _update_volume_status(self, status):
        self.volume.status = status
        self.volume.save()

    def _delete_volume_metadata(self):
        logger.debug("delete volume {} metadata.".format(self.volume_name))
        self.volume.delete() class KubeClient(object):
    """
    Kubernetes simple python client.
    API: http://kubernetes.io/third_party/swagger-ui/
    """
    _base_url = None
    def __init__(self, base_url):
        self._base_url = self.add_slash(base_url)

    @classmethod
    def add_slash(self, url):
        """
        Promote that the base url ends with '/'.
        """
        if url.endswith('/'):
            return url
        return url + '/'

    @property
    def base_url(self):
        return self._base_url

    def _send_request(self, method, path, label=None, query=None, body=None):
        """
        Send requests to k8s server and get the response.
        Returns a response dict.

        Parameters:
        query: str, "app=name"
        """
        url = self._base_url + path
        if label:
            url = '{}?labelSelector={}'.format(url, label)
        if query:
            url = url + '?' + query
        kwargs = {}
        if body:
            kwargs['data'] = json.dumps(body)

        try:
            res = getattr(requests, method.lower())(url, **kwargs)
        except Exception as error:
            logger.error(error)
            logger.error(res)
            return None

        try:
            response = json.loads(res.text)
            return response
        except Exception as error:
            return res.text

    def list_nodes(self):
        """
        List all nodes.
        """
        res = self._send_request('GET', 'nodes')
        nodes = []
        for item in res.get('items'):
            nodes.append(item['metadata']['name'])
        return nodes

    def list_namespces(self):
        """
        List all namespaces.
        """
        res = self._send_request('GET', 'namespaces')
        namespaces = []
        for item in res.get('items'):
            namespaces.append(item['metadata']['name'])
        return namespaces

    def create_namespace(self, name):
        """
        Create namespace called name.
        """
        namespace = Namespace(name)

        response = self._send_request('POST', 'namespaces', body=namespace.body)
        return self._is_creating_deleting_successful(response)

    def delete_namespace(self, name):
        """
        Delete namespace called name.
        """
        response = self._send_request('DELETE', 'namespaces/{}'.format(name))
        return self._is_creating_deleting_successful(response)

    def create_persistentvolume(self, namespace, name, capacity, nfs_path,
        nfs_server):
        """
        Create persistentvolume called namespace-name.
        """
        volume_name = namespace + '-' + name
        volume = PersistentVolume(volume_name, capacity, nfs_path, nfs_server)
        response = self._send_request('POST', 'persistentvolumes',
            body=volume.body)
        return self._is_creating_deleting_successful(response)

    def delete_persistentvolume(self, namespace, name):
        """
        Delete persistentvolume called namespace-name.
        """
        volume_name = namespace + '-' + name
        response = self._send_request('DELETE', 'persistentvolumes/{}'.format(
            volume_name))
        return self._is_creating_deleting_successful(response)

    def create_persistentvolumeclaim(self, namespace, name, capacity):
        """
        Create persistentvolumeclaim called name.
        """
        volume_name = namespace + '-' + name
        volumeclaim = PersistentVolumeClaim(volume_name, capacity)
        response = self._send_request('POST',
            'namespaces/{}/persistentvolumeclaims'.format(namespace),
            body=volumeclaim.body)
        return self._is_creating_deleting_successful(response)

    def delete_persistentvolumeclaim(self, namespace, name):
        """
        Delete persistentvolumeclaim called name.
        """
        volume_name = namespace + '-' + name
        response = self._send_request('DELETE',
            'namespaces/{}/persistentvolumeclaims/{}'.format(namespace,
                volume_name))
        return self._is_creating_deleting_successful(response)

    def list_controllers(self, namespace):
        """
        List all replicationcontroller in the namespace.
        """
        path = 'namespaces/{}/replicationcontrollers'.format(namespace)
        res = self._send_request('GET', path)
        controllers = []
        for item in res.get('items'):
            controllers.append(item['metadata']['name'])
        return controllers

    def create_controller(self, namespace, name, image_name, cpu, memory,
        replicas=1, tcp_ports=None, udp_ports=None, commands=None, args=None,
        envs=None, volumes=None):
        """
        Create a replicationcontroller.
        """
        controller = Controller(name, image_name, cpu, memory, replicas,
            tcp_ports, udp_ports, commands, args, envs, volumes)
        path = 'namespaces/{}/replicationcontrollers'.format(namespace)

        # logger.debug(controller.body)
        response = self._send_request('POST', path, body=controller.body)
        return self._is_creating_deleting_successful(response)

    def delete_controller(self, namespace, name):
        """
        Delete a replicationcontroller.
        """
        path = 'namespaces/{}/replicationcontrollers/{}'.format(namespace, name)
        response = self._send_request('DELETE', path)
        return self._is_creating_deleting_successful(response)

    def list_pods(self, namespace, label=None):
        """
        List pods by label.

        Parameters:
        label: str, "app=name"
        """
        path = 'namespaces/{}/pods/'.format(namespace)
        response = self._send_request('GET', path, label=label)
        # logger.debug(response)
        pods = []
        for item in response.get('items'):
            pods.append(item['metadata']['name'])
        return pods

    def list_host_ips(self, namespace, label=None):
        """
        List all host ips for a controller.

        Parameters:
        label: str, "app=name"
        """
        path = 'namespaces/{}/pods/'.format(namespace)
        response = self._send_request('GET', path, label=label)
        # logger.debug(response)
        hosts = set()
        for pod in response.get('items', []):
            hosts.add(pod['spec']['nodeName'])
        return list(hosts)

    def delete_pod(self, namespace, name):
        """
        Delete a pod.
        """
        path = 'namespaces/{}/pods/{}'.format(namespace, name)
        response = self._send_request('DELETE', path)
        return self._is_creating_deleting_successful(response)

    def list_services(self, namespace):
        """
        List all services in the namespace.
        """
        path = 'namespaces/{}/services'.format(namespace)
        res = self._send_request('GET', path)
        services = []
        for item in res.get('items'):
            services.append(item['metadata']['name'])
        return services

    def create_service(self, namespace, name, tcp_ports=None, udp_ports=None,
        is_public=False, session_affinity=False):
        """
        Create a service in namespace.
        """
        service = Service(name, tcp_ports, udp_ports, is_public,
            session_affinity)
        path = 'namespaces/{}/services'.format(namespace)

        # logger.debug(service.body)
        response = self._send_request('POST', path, body=service.body)
        return self._is_creating_deleting_successful(response)

    def delete_service(self, namespace, name):
        """
        Delete a service.
        """
        path = 'namespaces/{}/services/{}'.format(namespace, name)
        response = self._send_request('DELETE', path)
        return self._is_creating_deleting_successful(response)

    def get_service_details(self, namespace, name):
        """
        Get the details of a service.
        """
        path = 'namespaces/{}/services/{}'.format(namespace, name)
        response = self._send_request('GET', path)
        return response

    def _is_creating_deleting_successful(self, response):
        """
        Check the response to determinate whether creating and resource
        successfully.
        """
        status = response['status']
        if isinstance(status, str) and status == 'Failure':
            logger.debug(response['message'])
            return False
        return True

    def get_logs_of_pod(self, namespace, pod_name, tail_line):
        """
        Return the tail tail_line lines of logs of pod named pod_name.
        """
        path = 'namespaces/{}/pods/{}/log'.format(namespace, pod_name)
        query = "tailLines=" + str(tail_line)
        response = self._send_request('GET', path, query=query)
        return response.split('\n')

    def create_autoscaler(self, namespace, name, minReplicas=-1, maxReplicas=-1,
        cpu_target=-1):
        """
        Create an autoscaler name in namespace namespace.
        """
        scaler = AutoScaler(name, minReplicas, maxReplicas, cpu_target)
        path = 'namespaces/{}/horizontalpodautoscalers'.format(namespace)

        # logger.debug(scaler.body)
        response = self._send_request('POST', path, body=scaler.body)
        return self._is_creating_deleting_successful(response)

    def delete_autoscaler(self, namespace, name):
        """
        Delete the autoscaler name.
        """
        path = 'namespaces/{}/horizontalpodautoscalers/{}'.format(namespace,
            name)
        response = self._send_request('DELETE', path)
        return self._is_creating_deleting_successful(response) class NFSLocalClient(object):
    """
    NFSLocalClient is a manager to manage the nfs direction.
    """

    def __init__(self):
        pass

    def makedir(self, path):
        """
        Make direction on nfs server.
        """
        if not os.path.exists(path):
            os.makedirs(path)

    def removedir(self, path):
        """
        Remove direction on nfs server.
        """
        if os.path.exists(path):
            shutil.rmtree(path)

    def copy_file_to_remote(self, localfile, remotefile):
        """
        Copy localfile to remote nfs server, like scp command.
        """
        self.makedir(os.path.dirname(remotefile))
        os.system("cp {} {}".format(localfile, remotefile))

    def copy_file_to_local(self, remotefile, localfile):
        """
        Copy remote file to local filesystem, like scp command.
        """
        self.makedir(os.path.dirname(localfile))
        os.system("cp {} {}".format(remotefile, localfile))

    def tar_and_copy_to_local(self, remotedir, localfile):
        """
        Tar remotedir as a package, and copy to local.
        """
        tar_name = os.path.basename(localfile)
        os.system("cd {} && tar -cf {} *".format(remotedir, tar_name))
        self.makedir(os.path.dirname(localfile))
        remotefile = os.path.join(remotedir, tar_name)
        os.system("mv {} {}".format(remotefile, localfile))

    def copy_file_to_remote_and_untar(self, localfile, remotedir):
        """
        Copy local tar file to remote, and untar into the remote dir.
        """
        tar_name = os.path.basename(localfile)
        remotefile = os.path.join(remotedir, tar_name)
        self.copy_file_to_remote(localfile, remotefile)
        command = "cd {} && tar -xf {} && rm {}".format(remotedir, tar_name,
            remotefile)
        os.system(command) def remove_file_from_disk(filename):
    """
    Delete the file from disk.
    """
    if os.path.exists(filename):
        os.remove(filename) def get_application_instance_name(application):
    """
    Return the name of the application instance.
    """
    return "{}-{}".format(application.image.project.name, application.name) class DockerSchedulerFactory(object):
    """
    Factory of DockerScheduler, to use the only one scheduler allways.
    """
    _scheduler = None

    @classmethod
    def get_scheduler(self):
        if not self._scheduler:
            self._scheduler = DockerScheduler(settings.MASTER_IP,
                settings.ETCD_PORT)
        return self._scheduler
import logging
import os
import json
from django.contrib.auth.models import AnonymousUser
from django.core import serializers
from django.conf import settings
from django.http import StreamingHttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import (require_POST, require_GET)
from rest_framework import viewsets, status
from rest_framework.response import Response
from rest_framework.permissions import IsAdminUser, IsAuthenticated
from rest_framework.exceptions import (PermissionDenied, ValidationError,
    ParseError, NotFound)
from rest_framework.decorators import api_view
from rest_framework.settings import api_settings
from restapi.serializers import (UserSerializer, ProjectSerializer,
    ImageSerializer, ApplicationSerializer, PortSerializer,
    ResourceLimitSerializer, VolumeSerializer, AutoScalerSerializer,
    EnvironmentSerializer)
from backend.models import (MyUser, Project, Image, Application, Port,
    ResourceLimit, Volume, AutoScaler, Environment)
from restapi.utils import (save_upload_file_to_disk, is_image_or_dockerfile, get_upload_image_filename, get_ports_by_protocol,
    get_upload_volume_filename, get_volume_direction_on_nfs,
    big_file_iterator, check_member_in_project)
from backend.image import (ImageBuilder, ImageDestroyer, ImageCloner)
from backend.application import (ApplicationBuilder, ApplicationDestroyer)
from backend.volume import (VolumeBuilder, VolumeDestroyer)
from backend.kubernetes.k8sclient import KubeClient
from backend.nfs import NFSLocalClient
from backend.utils import (remove_file_from_disk, get_application_instance_name)
from backend.schedule import DockerSchedulerFactory
            old_image_name = request.data.get('old_image_name',
                image['name'])
            old_image_version = request.data.get('old_image_version',
                image['version'])

        filename = get_upload_image_filename(image, request.user)

        save_upload_file_to_disk(request.FILES['file'], filename)

        # create a true image instance, and upload into private registry
        builder = ImageBuilder(
            build_file=filename,
            is_image=is_image,
            dockerfile=dockerfile,
            image_id=image['id'],
            old_image_name=old_image_name,
            old_image_version=old_image_version
            )
        builder.create_image()

        return response

    def destroy(self, request, *args, **kwargs):
        """
        Destroy an image instance.
        """
        image = self.get_object()

        logger.info("user %s deletes image: %s/%s:%s" % (
            request.user.username,
            image.user.username,
            image.name,
            image.version))

        destroyer = ImageDestroyer(image)
        destroyer.destroy_image_instance()

        return Response(status=status.HTTP_204_NO_CONTENT)


    def list_public_images(self, request, *args, **kwargs):
        """
        List all public images.
        """
        publics = Image.objects.filter(is_public=True)
        data = [self.get_serializer(public).data for public in publics]

        return Response(data, status=status.HTTP_200_OK)

    def get_public_image(self, request, *args, **kwargs):
        """
        Get the requested public image.
        """
        puid = kwargs['puid']
        public = Image.objects.get(id=puid)

        return Response(self.get_serializer(public).data)


    def delete_public_image(self, request, *args, **kwargs):
        """
        Delete public image.
        """
        pass

    def add_public_image_to_project(self, request, *args, **kwargs):
        """
        Add one public image into a project of user.
        """
        puid = kwargs['puid']
        public_image = Image.objects.get(id=puid)

        pid = request.POST['pid']
        name = request.POST['name']
        version = request.POST['version']

        data = {
            'user': request.user.id,
            'project': pid,
            'name': name,
            'version': version,
            'desc': public_image.desc,
            'is_public': False,
        }
        # print(data)

        # create image metadata
        serializer = self.get_serializer(data=data)
        serializer.is_valid(raise_exception=True)

        images = Image.objects.filter(project__id=pid, name=name,
            version=version)
        if images:
            raise ValidationError(detail="Already has an image called {}:{}."
                .format(name, version))

        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        response = Response(serializer.data, status=status.HTTP_201_CREATED,
            headers=headers)

        image = serializer.data

        # clone public image into project
        cloner = ImageCloner(puid, image['id'])
        cloner.clone_image()

        return response

    def get_image_username(self, request, *args, **kwargs):
        """
        Get the username of this image.
        """
        image = self.get_object()
        username = image.user.username

        return Response(username, status=status.HTTP_200_OK)


class ApplicationViewSet(viewsets.ModelViewSet):

    serializer_class = ApplicationSerializer