コード例 #1
0
class DockerWorker(Worker):
    def __init__(self, config, persistence, messaging):
        """
        Call super-class constructor for common configuration items and
        then do the docker-specific setup
        :return: -
        """
        super(DockerWorker, self).__init__(config=config,
                                           persistence=persistence,
                                           messaging=messaging)
        logging.debug('DockerWorker initialization')
        self.docker = AutoVersionClient(
            base_url=self.config['worker']['docker_url'],
            tls=_get_tls(config['worker']))
        self._image_ports = self._initialize_image()
        self._get_all_allocated_ports()

    def create_instance(self, message):
        instance = message.copy()
        logging.info('Creating instance id: %s', instance['id'])
        environment = self._create_instance_env()
        ports = self.port_manager.acquire_ports(len(self._image_ports))
        environment = _check_instance_env_port_injection(
            environment=environment,
            ports=ports)
        if ports:
            port_mapping = dict(zip(self._image_ports, ports))
            container = self.docker.create_container(self.worker['image'],
                                                     environment=environment,
                                                     ports=self._image_ports)
            self.docker.start(container, port_bindings=port_mapping)
            instance['container_id'] = container['Id']
            instance['environment'] = environment
            self._set_networking(instance=instance)
            self.local_persistence.update_instance_status(
                instance=instance,
                status=INSTANCE_STATUS.STARTING)
        else:
            self.local_persistence.update_instance_status(
                instance=instance,
                status=INSTANCE_STATUS.FAILED)

    def delete_instance(self, message):
        msg = message.copy()
        instance = self.local_persistence.get_instance(msg['id'])
        if instance['status'] == INSTANCE_STATUS.RUNNING:
            logging.info('Deleting instance id: %s', msg['id'])
            container = self._get_container(
                container_id=instance['container_id'])
            if not container:
                logging.debug('Container does not exist, not stopping it')
                return
            free_ports = self._get_container_ports(instance['container_id'])
            self.docker.kill(container)
            self.docker.remove_container(container)
            self.port_manager.release_ports(free_ports)
            self.local_persistence.update_instance_status(
                instance=instance,
                status=INSTANCE_STATUS.DELETED)
        else:
            self.local_persistence.update_instance_status(
                instance=instance,
                status=INSTANCE_STATUS.DELETED)

    def _initialize_image(self):
        """
        download a docker image and get the ports that we have to link
        :return: list of ports(str)
        """
        logging.info('Initializing image %s', self.config['worker']['image'])
        self.worker['image'] = self.config['worker']['image']
        tmp = self.worker['image'].split(':')
        if len(tmp) == 2:
            self.docker.import_image(image=tmp[0], tag=tmp[1])
        else:
            self.docker.import_image(image=tmp)
        logging.debug('Extracting ports from image')
        ports = []
        docker_image = self.docker.inspect_image(self.worker['image'])
        for port in docker_image[u'ContainerConfig'][u'ExposedPorts'].keys():
            ports.append(port.split('/')[0])
        return ports

    def _get_all_allocated_ports(self):
        """
        get all containers, that have not been stopped, they may have been
        started from outside of the workers scope.
        :return: array with ports to use, None if not enough ports available
        """
        used_ports = set()
        containers = self.docker.containers()
        for container in containers:
            for port in self._get_container_ports(container['Id']):
                used_ports.add(port)
        self.port_manager.update_used_ports(used_ports)

    def _get_container(self, container_id):
        """
        get docker-py s container description
        :param container_id: string, id for the container
        :return: dict, containing the container
        """
        try:
            return self.docker.inspect_container({'Id': container_id})
        except docker.errors.APIError:
            logging.debug('Not able to get container %s', container_id)
            return None

    def _get_container_networking(self, container_id):
        """
        return a dict with the container networking, using the appropriate
        worker ip
        :param container_id: id of the container
        :return: dict with the Ports section of the container representation
        """
        try:
            networking = self._get_container(
                container_id)['NetworkSettings']['Ports']
            if 'ip' in self.config['worker'].keys():
                for port in networking:
                    for index, unused in enumerate(networking[port]):
                        networking[port][index][u'HostIp'] = \
                            unicode(self.config['worker']['ip'])
            return networking
        except TypeError:
            logging.error('Cannot get ports for container_id %s', container_id)
            return None

    def _get_container_ports(self, container_id):
        """
        return a list of the concrete container ports that are used
        :param container_id: id of the container
        :return: list of integers
        """
        networking = self._get_container_networking(container_id)
        ports = list()
        if networking:
            for port in networking.keys():
                for index, unused in enumerate(networking[port]):
                    ports.append(int(networking[port][index][u'HostPort']))
        return ports

    def _publish_updates(self):
        instances = self.local_persistence.get_instances()
        for instance_id in instances.keys():
            if instances[instance_id]['status'] is INSTANCE_STATUS.DELETED:
                continue
            elif instances[instance_id]['status'] is INSTANCE_STATUS.FAILED:
                self.local_persistence.publish_instance(instance_id)
                continue

            container_id = instances[instance_id]['container_id']
            container = self._get_container(container_id)
            if not container_id or not container or not _is_running(container):
                instances[instance_id].pop('connection', None)
                instances[instance_id].pop('urls', None)
                self.local_persistence.update_instance_status(
                    instances[instance_id],
                    INSTANCE_STATUS.STOPPED)
                continue
            elif _is_running(container):
                self._set_networking(instances[instance_id])
                self.local_persistence.update_instance_status(
                    instances[instance_id],
                    INSTANCE_STATUS.RUNNING)
            else:
                logging.error("error while publishing updates")
        self._get_all_allocated_ports()
        self._update_worker_status()

    def _update_worker_status(self):
        number_required_ports = len(self._image_ports)
        if self.port_manager.enough_ports_left(number_required_ports):
            self.worker['available'] = True
            self.worker['status'] = 'Worker available'
        else:
            self.worker['available'] = False
            self.worker['status'] = 'Worker unavailable, ' \
                                    'to many resources in use'

    def _set_networking(self, instance):
        instance['connection'] = \
            self._get_container_networking(instance['container_id'])
        instance['urls'] = self.url_builder.build(instance['connection'])