def host_data(mocker):
    mocker.patch.object(platform, 'system', return_value='Linux')
    mocker.patch('os.getloadavg',
                 return_value=(1.60693359375, 1.73193359375, 1.79248046875))

    mocker.patch.object(CpuCollector,
                        '_get_cpuinfo_data',
                        return_value=cpuinfo_data())

    mocker.patch.object(MemoryCollector,
                        '_get_meminfo_data',
                        return_value=meminfo_data())

    mocker.patch.object(CadvisorAPIClient, 'get_containers',
                        return_value=cadvisor_stats_data())

    mocker.patch.object(Client, 'version',
                        return_value=docker_client_version_data())

    host = HostInfo(docker_client())
    data = host.collect_data()

    assert isinstance(data, dict)
    os.getloadavg.assert_called_once_with()
    CpuCollector._get_cpuinfo_data.assert_called_once_with()
    MemoryCollector._get_meminfo_data.assert_called_once_with()
    CadvisorAPIClient.get_containers.assert_called_with()
    Client.version.assert_called_once_with()

    return data
def host_data(mocker):
    mocker.patch.object(platform, 'system', return_value='Linux')
    mocker.patch('os.getloadavg',
                 return_value=(1.60693359375, 1.73193359375, 1.79248046875))

    mocker.patch.object(CpuCollector,
                        '_get_cpuinfo_data',
                        return_value=cpuinfo_data())

    mocker.patch.object(MemoryCollector,
                        '_get_meminfo_data',
                        return_value=meminfo_data())

    mocker.patch.object(CadvisorAPIClient, 'get_containers',
                        return_value=cadvisor_stats_data())

    mocker.patch('cattle.utils.check_output',
                 return_value='Docker version 1.4.1, build 5bc2ff8')

    host = HostInfo()
    data = host.collect_data()

    assert isinstance(data, dict)
    os.getloadavg.assert_called_once_with()
    CpuCollector._get_cpuinfo_data.assert_called_once_with()
    MemoryCollector._get_meminfo_data.assert_called_once_with()
    CadvisorAPIClient.get_containers.assert_called_with()
    cattle.utils.check_output.assert_called_once_with(
        ['docker', '-v'])

    return data
Beispiel #3
0
def host_data(mocker):
    mocker.patch.object(platform, 'system', return_value='Linux')
    mocker.patch('os.getloadavg',
                 return_value=(1.60693359375, 1.73193359375, 1.79248046875))

    mocker.patch.object(CpuCollector,
                        '_get_cpuinfo_data',
                        return_value=cpuinfo_data())

    mocker.patch.object(MemoryCollector,
                        '_get_meminfo_data',
                        return_value=meminfo_data())

    mocker.patch.object(CadvisorAPIClient,
                        'get_containers',
                        return_value=cadvisor_stats_data())

    mocker.patch.object(Client,
                        'version',
                        return_value=docker_client_version_data())

    host = HostInfo(docker_client())
    data = host.collect_data()

    assert isinstance(data, dict)
    os.getloadavg.assert_called_once_with()
    CpuCollector._get_cpuinfo_data.assert_called_once_with()
    MemoryCollector._get_meminfo_data.assert_called_once_with()
    CadvisorAPIClient.get_containers.assert_called_with()
    Client.version.assert_called_once_with()

    return data
def full_mock_hostinfo_obj(mocker):
    mocker.patch.object(platform, 'system', return_value='Linux')
    mocker.patch.object(platform, 'release', return_value='3.19.0-28-generic')

    mocker.patch('os.getloadavg',
                 return_value=(1.60693359375, 1.73193359375, 1.79248046875))

    mocker.patch.object(CpuCollector,
                        '_get_cpuinfo_data',
                        return_value=cpuinfo_data())

    mocker.patch.object(MemoryCollector,
                        '_get_meminfo_data',
                        return_value=meminfo_data())

    mocker.patch.object(CadvisorAPIClient,
                        'get_containers',
                        return_value=cadvisor_stats_data())

    mocker.patch.object(CadvisorAPIClient,
                        'get_machine_stats',
                        return_value=cadvisor_machine_stats_data())

    mocker.patch.object(Client,
                        'version',
                        return_value=docker_client_version_data())

    mocker.patch.object(Client,
                        'info',
                        return_value=docker_devicemapper_override())

    return HostInfo(docker_client())
def no_cadvisor_non_intel_cpuinfo_mock(mocker):
    mocker.patch.object(platform, 'system', return_value='Linux')
    mocker.patch('os.getloadavg',
                 return_value=(1.60693359375, 1.73193359375, 1.79248046875))

    mocker.patch.object(CpuCollector,
                        '_get_cpuinfo_data',
                        return_value=non_intel_cpuinfo_data())

    mocker.patch.object(MemoryCollector,
                        '_get_meminfo_data',
                        return_value=meminfo_data())

    mocker.patch.object(CadvisorAPIClient, '_get', return_value=None)

    host = HostInfo()
    data = host.collect_data()

    return data
def no_cadvisor_host_data(mocker):
    mocker.patch.object(platform, 'system', return_value='Linux')
    mocker.patch('os.getloadavg',
                 return_value=(1.60693359375, 1.73193359375, 1.79248046875))

    mocker.patch.object(CpuCollector,
                        '_get_cpuinfo_data',
                        return_value=cpuinfo_data())

    mocker.patch.object(MemoryCollector,
                        '_get_meminfo_data',
                        return_value=meminfo_data())

    mocker.patch.object(CadvisorAPIClient, '_get',
                        return_value=None)

    host = HostInfo()
    data = host.collect_data()

    return data
def no_cadvisor_host_data(mocker):
    mocker.patch.object(platform, 'system', return_value='Linux')
    mocker.patch('os.getloadavg',
                 return_value=(1.60693359375, 1.73193359375, 1.79248046875))

    mocker.patch.object(CpuCollector,
                        '_get_cpuinfo_data',
                        return_value=cpuinfo_data())

    mocker.patch.object(MemoryCollector,
                        '_get_meminfo_data',
                        return_value=meminfo_data())

    mocker.patch.object(CadvisorAPIClient, '_get',
                        return_value=None)

    mocker.patch('cattle.utils.check_output',
                 return_value='Docker version 1.4.1, build 5bc2ff8')

    host = HostInfo()
    data = host.collect_data()

    return data
Beispiel #8
0
 def __init__(self):
     KindBasedMixin.__init__(self, kind='docker')
     BaseComputeDriver.__init__(self)
     self.host_info = HostInfo(docker_client())
     self.system_images = self.get_agent_images(docker_client())
Beispiel #9
0
class DockerCompute(KindBasedMixin, BaseComputeDriver):
    def __init__(self):
        KindBasedMixin.__init__(self, kind='docker')
        BaseComputeDriver.__init__(self)
        self.host_info = HostInfo(docker_client())
        self.system_images = self.get_agent_images(docker_client())

    def get_agent_images(self, client):
        images = client.images(filters={'label': SYSTEM_LABEL})
        system_images = {}
        for i in images:
            try:
                label_val = i['Labels'][SYSTEM_LABEL]
                for l in i['RepoTags']:
                    system_images[l] = label_val
                    if l.endswith(':latest'):
                        alias = l[:-7]
                        system_images[alias] = label_val
            except KeyError:
                pass
        return system_images

    @staticmethod
    def get_container_by(client, func):
        containers = client.containers(all=True, trunc=False)
        containers = filter(func, containers)

        if len(containers) > 0:
            return containers[0]

        return None

    @staticmethod
    def find_first(containers, func):
        containers = filter(func, containers)

        if len(containers) > 0:
            return containers[0]

        return None

    def on_ping(self, ping, pong):
        if not DockerConfig.docker_enabled():
            return

        self._add_resources(ping, pong)
        self._add_instances(ping, pong)

    def _add_instances(self, ping, pong):
        if not utils.ping_include_instances(ping):
            return

        utils.ping_add_resources(pong, {
            'type': 'hostUuid',
            'uuid': DockerConfig.docker_uuid()
        })

        containers = []
        running, nonrunning = self._get_all_containers_by_state()

        for key, container in running.iteritems():
            self.add_container('running', container, containers)

        for key, container in nonrunning.iteritems():
            self.add_container('stopped', container, containers)

        utils.ping_add_resources(pong, *containers)
        utils.ping_set_option(pong, 'instances', True)

    def add_container(self, state, container, containers):
        try:
            labels = container['Labels']
        except KeyError:
            labels = []

        container_data = {
            'type': 'instance',
            'uuid': self._get_uuid(container),
            'state': state,
            'systemContainer': self._get_sys_container(container),
            'dockerId': container['Id'],
            'image': container['Image'],
            'labels': labels,
            'created': container['Created'],
        }
        containers.append(container_data)

    def _get_all_containers_by_state(self):
        client = docker_client()

        nonrunning_containers = {}
        for c in client.containers(all=True):
            # Blank status only wait to distinguish created from stopped
            if c['Status'] != '' and c['Status'] != 'Created':
                nonrunning_containers[c['Id']] = c

        running_containers = {}
        for c in client.containers(all=False):
            running_containers[c['Id']] = c
            del nonrunning_containers[c['Id']]

        return running_containers, nonrunning_containers

    def _get_sys_container(self, container):
        try:
            image = container['Image']
            if image in self.system_images:
                return self.system_images[image]
        except (TypeError, KeyError):
            pass

        try:
            return container['Labels']['io.rancher.container.system']
        except (TypeError, KeyError):
            pass

    def _get_uuid(self, container):
        try:
            uuid = container['Labels'][UUID_LABEL]
            if uuid:
                return uuid
        except (TypeError, KeyError):
            pass

        names = container['Names']
        if not names:
            # No name?? Make one up
            return 'no-uuid-%s' % container['Id']

        if names[0].startswith('/'):
            return names[0][1:]
        else:
            return names[0]

    def _determine_state(self, container):
        status = container['Status']
        if status == '' or (status is not None and
                            status.lower() == 'created'):
            return 'created'
        elif 'Up ' in status:
            return 'running'
        elif 'Exited ' in status:
            return 'stopped'
        else:
            # Unknown. Assume running and state should sync up eventually.
            return 'running'

    def _get_host_labels(self):
        try:
            return self.host_info.host_labels()
        except:
            log.exception("Error getting host labels")
            return {}

    def _get_host_create_labels(self):
        labels = Config.labels()
        if labels:
            return labels
        return {}

    def _add_resources(self, ping, pong):
        if not utils.ping_include_resources(ping):
            return

        stats = None
        if utils.ping_include_stats(ping):
            try:
                stats = self.host_info.collect_data()
            except:
                log.exception("Error getting host info stats")

        physical_host = Config.physical_host()

        compute = {
            'type': 'host',
            'kind': 'docker',
            'name': Config.hostname(),
            'createLabels': self._get_host_create_labels(),
            'labels': self._get_host_labels(),
            'physicalHostUuid': physical_host['uuid'],
            'uuid': DockerConfig.docker_uuid(),
            'info': stats
        }

        pool = {
            'type': 'storagePool',
            'kind': 'docker',
            'name': compute['name'] + ' Storage Pool',
            'hostUuid': compute['uuid'],
            'uuid': compute['uuid'] + '-pool'
        }

        resolved_ip = socket.gethostbyname(DockerConfig.docker_host_ip())

        ip = {
            'type': 'ipAddress',
            'uuid': resolved_ip,
            'address': resolved_ip,
            'hostUuid': compute['uuid'],
        }

        proxy = Config.host_proxy()
        if proxy is not None:
            compute['apiProxy'] = proxy

        utils.ping_add_resources(pong, physical_host, compute, pool, ip)

    def inspect(self, container):
        return docker_client().inspect_container(container)

    @staticmethod
    def _name_filter(name, container):
        names = container.get('Names')
        if names is None:
            return False
        found = False
        for n in names:
            if n.endswith(name):
                found = True
                break
        return found

    @staticmethod
    def _id_filter(id, container):
        container_id = container.get('Id')
        return id == container_id

    @staticmethod
    def _agent_id_filter(id, container):
        try:
            return container['Labels']['io.rancher.container.agent_id'] == id
        except (TypeError, KeyError, AttributeError):
            pass

    def get_container(self, client, instance, by_agent=False):
        if instance is None:
            return None

        # First look for UUID label directly
        labeled_containers = client.containers(all=True, trunc=False, filters={
            'label': '{}={}'.format(UUID_LABEL, instance.uuid)})
        if len(labeled_containers) > 0:
            return labeled_containers[0]

        # Next look by UUID using fallback method
        container_list = client.containers(all=True, trunc=False)
        container = self.find_first(container_list,
                                    lambda x: self._get_uuid(x) ==
                                    instance.uuid)
        if container:
            return container

        if hasattr(instance, 'externalId') and instance.externalId:
            container = self.find_first(container_list,
                                        lambda x: self._id_filter(
                                            instance.externalId, x))

        if container:
            return container

        if by_agent and hasattr(instance, 'agentId') and instance.agentId:
            container = self.find_first(container_list,
                                        lambda x: self._agent_id_filter(
                                            str(instance.agentId), x))

        return container

    def _is_instance_active(self, instance, host):
        if is_no_op(instance):
            return True

        client = docker_client()
        container = self.get_container(client, instance)
        return _is_running(client, container)

    @staticmethod
    def _setup_legacy_command(create_config, instance, command):
        # This can be removed shortly once cattle removes
        # commandArgs
        if command is None or len(command.strip()) == 0:
            return None

        command_args = []
        try:
            command_args = instance.data.fields.commandArgs
        except (KeyError, AttributeError):
            pass

        if command_args is not None and len(command_args) > 0:
            command = [command]
            command.extend(command_args)

        if command is not None:
            create_config['command'] = command

    @staticmethod
    def _setup_command(create_config, instance):
        command = ""
        try:
            command = instance.data.fields.command
        except (KeyError, AttributeError):
            return None

        if isinstance(command, basestring):
            DockerCompute._setup_legacy_command(create_config, instance,
                                                command)
        else:
            if command is not None:
                create_config['command'] = command

    @staticmethod
    def _setup_links(start_config, instance):
        links = {}

        if 'instanceLinks' not in instance:
            return

        for link in instance.instanceLinks:
            if link.targetInstanceId is not None:
                links[link.targetInstance.uuid] = link.linkName

        start_config['links'] = links

    @staticmethod
    def _setup_ports(create_config, instance, start_config):
        ports = []
        bindings = {}
        try:
            for port in instance.ports:
                ports.append((port.privatePort, port.protocol))
                if port.publicPort is not None:
                    bind = '{0}/{1}'.format(port.privatePort, port.protocol)
                    bindings[bind] = ('', port.publicPort)
        except (AttributeError, KeyError):
            pass

        if len(ports) > 0:
            create_config['ports'] = ports

        if len(bindings) > 0:
            start_config['port_bindings'] = bindings

    def _record_state(self, client, instance, docker_id=None):
        if docker_id is None:
            container = self.get_container(client, instance)
            if container is not None:
                docker_id = container['Id']

        if docker_id is None:
            return

        cont_dir = Config.container_state_dir()

        tmp_file_path = path.join(cont_dir, 'tmp-%s' % docker_id)
        if path.exists(tmp_file_path):
            remove(tmp_file_path)

        file_path = path.join(cont_dir, docker_id)
        if path.exists(file_path):
            remove(file_path)

        if not path.exists(cont_dir):
            makedirs(cont_dir)

        with open(tmp_file_path, 'w') as outfile:
            marshaller = get_type(MARSHALLER)
            data = marshaller.to_string(instance)
            outfile.write(data)

        rename(tmp_file_path, file_path)

    def purge_state(self, client, instance):
        container = self.get_container(client, instance)
        if container is None:
            return

        docker_id = container['Id']
        cont_dir = Config.container_state_dir()
        files = [path.join(cont_dir, 'tmp-%s' % docker_id),
                 path.join(cont_dir, docker_id)]

        for f in files:
            if path.exists(f):
                remove(f)

    def instance_activate(self, req=None, instanceHostMap=None,
                          processData=None, **kw):
        instance, host = \
            BaseComputeDriver.get_instance_host_from_map(self, instanceHostMap)

        progress = Progress(req)
        client = docker_client()
        if instance is not None:
            instance.processData = processData

        with lock(instance):
            if self._is_instance_active(instance, host):
                self._record_state(client, instance)
                return self._reply(req, self.
                                   _get_response_data(req, instanceHostMap))

            self._do_instance_activate(instance, host, progress)

            data = self._get_response_data(req, instanceHostMap)

            return self._reply(req, data)

    def _do_instance_activate(self, instance, host, progress):
        if is_no_op(instance):
            return

        client = docker_client()

        image_tag = self._get_image_tag(instance)

        name = instance.uuid
        if instance.name and re.match(r'^[a-zA-Z0-9][a-zA-Z0-9_.-]+$',
                                      instance.name):
            try:
                client.inspect_container('r-{}'.format(instance.name))
            except NotFound:
                name = 'r-{}'.format(instance.name)

        create_config = {
            'name': name,
            'detach': True
        }

        start_config = {
            'publish_all_ports': False,
            'privileged': self._is_true(instance, 'privileged'),
            'read_only': self._is_true(instance, 'readOnly'),
        }

        # These _setup_simple_config_fields calls should happen before all
        # other config because they stomp over config fields that other
        # setup methods might append to. Example: the environment field
        self._setup_simple_config_fields(create_config, instance,
                                         CREATE_CONFIG_FIELDS)

        self._setup_simple_config_fields(start_config, instance,
                                         START_CONFIG_FIELDS)

        add_label(create_config, {UUID_LABEL: instance.uuid})
        if instance.name:
            add_label(create_config,
                      {'io.rancher.container.name': instance.name})

        self._setup_logging(start_config, instance)

        self._setup_hostname(create_config, instance)

        self._setup_command(create_config, instance)

        self._setup_ports(create_config, instance, start_config)

        self._setup_volumes(create_config, instance, start_config, client)

        self._setup_links(start_config, instance)

        self._setup_networking(instance, host, create_config, start_config)

        self._flag_system_container(instance, create_config)

        self._setup_proxy(instance, create_config)

        setup_cattle_config_url(instance, create_config)

        create_config['host_config'] = \
            client.create_host_config(**start_config)

        container = self._create_container(client, create_config,
                                           image_tag, instance, name,
                                           progress)
        container_id = container['Id']

        log.info('Starting docker container [%s] docker id [%s] %s', name,
                 container_id, start_config)

        client.start(container_id)

        self._record_state(client, instance, docker_id=container['Id'])

    def _create_container(self, client, create_config, image_tag, instance,
                          name, progress):
        container = self.get_container(client, instance)
        if container is None:
            log.info('Creating docker container [%s] from config %s', name,
                     create_config)

            labels = create_config['labels']
            if labels.get('io.rancher.container.pull_image', None) == 'always':
                self._do_instance_pull(JsonObject({
                    'image': instance.image,
                    'tag': None,
                    'mode': 'all',
                    'complete': False,
                }), progress)
            try:
                del create_config['name']
                command = ''
                try:
                    command = create_config['command']
                    del create_config['command']
                except KeyError:
                    pass
                config = client.create_container_config(image_tag,
                                                        command,
                                                        **create_config)
                try:
                    id = instance.data
                    config['VolumeDriver'] = id.fields['volumeDriver']
                except (KeyError, AttributeError):
                    pass
                container = client.create_container_from_config(config, name)
            except APIError as e:
                if e.message.response.status_code == 404:
                    pull_image(instance.image, progress)
                    container = client.create_container_from_config(config,
                                                                    name)
                else:
                    raise
        return container

    def _flag_system_container(self, instance, create_config):
        try:
            if instance.systemContainer:
                add_label(create_config, {
                    'io.rancher.container.system': instance.systemContainer})
        except (KeyError, AttributeError):
            pass

    def _setup_proxy(self, instance, create_config):
        try:
            if instance.systemContainer:
                if 'environment' not in create_config:
                    create_config['environment'] = {}

                for i in ['http_proxy', 'https_proxy', 'NO_PROXY']:
                    try:
                        create_config['environment'][i] = environ[i]
                    except KeyError:
                        pass
        except (KeyError, AttributeError):
            pass

    def _setup_simple_config_fields(self, config, instance, fields):
        for src, dest in fields:
            try:
                src_obj = instance.data.fields[src]
                config[dest] = JsonObject.unwrap(src_obj)
            except (KeyError, AttributeError):
                pass

    def _setup_volumes(self, create_config, instance, start_config, client):
        try:
            volumes = instance.data.fields['dataVolumes']
            volumes_map = {}
            binds_map = {}
            if volumes is not None and len(volumes) > 0:
                for i in volumes:
                    parts = i.split(':', 3)
                    if len(parts) == 1:
                        volumes_map[parts[0]] = {}
                    else:
                        read_only = len(parts) == 3 and parts[2] == 'ro'
                        bind = {'bind': parts[1], 'ro': read_only}
                        binds_map[parts[0]] = bind
                create_config['volumes'] = volumes_map
                start_config['binds'] = binds_map
        except (KeyError, AttributeError):
            pass

        try:
            containers = []
            for vfc in instance['dataVolumesFromContainers']:
                container = self.get_container(client, vfc)
                if container:
                    containers.append(container['Id'])
            if containers:
                start_config['volumes_from'] = containers
        except KeyError:
            pass

        try:
            for v in instance['volumesFromDataVolumeMounts']:
                if not DockerPool.is_volume_active(v):
                    DockerPool.do_volume_activate(v)
        except KeyError:
            pass

    def _get_image_tag(self, instance):
        try:
            return instance.image.data.dockerImage.fullName
        except (KeyError, AttributeError):
            raise Exception('Can not start container with no image')

    def _setup_logging(self, start_config, instance):
        try:
            if start_config.get('log_config', None) is None:
                return

            type = start_config['log_config']['driver']
            del start_config['log_config']['driver']
            start_config['log_config']['type'] = type
        except (KeyError, AttributeError):
            pass

        for i in ['type', 'config']:
            bad = True
            try:
                obj = start_config['log_config'][i]
                if obj is not None:
                    bad = False
                    start_config['log_config'][i] = JsonObject.unwrap(obj)
            except (KeyError, AttributeError):
                pass
            if bad and 'log_config' in start_config:
                del start_config['log_config']

    def _setup_hostname(self, create_config, instance):
        try:
            create_config['hostname'] = instance.hostname
        except (KeyError, AttributeError):
            pass

    def _setup_networking(self, instance, host, create_config, start_config):
        client = docker_client()

        ports_supported = setup_network_mode(instance, self, client,
                                             create_config, start_config)
        setup_mac_and_ip(instance, create_config, set_mac=ports_supported)
        setup_ports(instance, create_config, start_config, ports_supported)
        setup_links(instance, create_config, start_config)
        setup_ipsec(instance, host, create_config, start_config)

    def _is_true(self, instance, key):
        try:
            return instance.data.fields[key] is True
        except (KeyError, AttributeError):
            return False

    def _get_instance_host_map_data(self, obj):
        client = docker_client()
        inspect = None
        docker_mounts = None
        existing = self.get_container(client, obj.instance)
        docker_ports = {}
        docker_ip = None

        if existing is not None:
            inspect = client.inspect_container(existing['Id'])
            docker_mounts = self._get_mount_data(obj.host, existing['Id'])
            docker_ip = inspect['NetworkSettings']['IPAddress']
            if existing.get('Ports') is not None:
                for port in existing['Ports']:
                    if 'PublicPort' in port and 'PrivatePort' not in port:
                        # Remove after docker 0.12/1.0 is released
                        private_port = '{0}/{1}'.format(port['PublicPort'],
                                                        port['Type'])
                        docker_ports[private_port] = None
                    elif 'PublicPort' in port:
                        private_port = '{0}/{1}'.format(port['PrivatePort'],
                                                        port['Type'])
                        docker_ports[private_port] = str(port['PublicPort'])
                    else:
                        private_port = '{0}/{1}'.format(port['PrivatePort'],
                                                        port['Type'])
                        docker_ports[private_port] = None

        update = {
            'instance': {
                '+data': {
                    'dockerContainer': existing,
                    'dockerInspect': inspect,
                    '+fields': {
                        'dockerHostIp': DockerConfig.docker_host_ip(),
                        'dockerPorts': docker_ports,
                        'dockerIp': docker_ip
                    }
                }
            }
        }
        if existing is not None:
            update['instance']['externalId'] = existing['Id']

        if docker_mounts is not None:
            update['instance']['+data']['dockerMounts'] = docker_mounts

        return update

    def _get_mount_data(self, host, container_id):
        try:
            client = docker_client(version='1.21')
            inspect = client.inspect_container(container_id)
            return inspect['Mounts']
        except (KeyError, APIError):
            pass

    def _is_instance_inactive(self, instance, host):
        if is_no_op(instance):
            return True

        c = docker_client()
        container = self.get_container(c, instance)

        return _is_stopped(c, container)

    def _do_instance_deactivate(self, instance, host, progress):
        if is_no_op(instance):
            return

        c = docker_client()
        timeout = 10

        try:
            timeout = int(instance.processData.timeout)
        except (TypeError, KeyError, AttributeError):
            pass

        container = self.get_container(c, instance)

        c.stop(container['Id'], timeout=timeout)

        container = self.get_container(c, instance)
        if not _is_stopped(c, container):
            c.kill(container['Id'])

        container = self.get_container(c, instance)
        if not _is_stopped(c, container):
            raise Exception('Failed to stop container {0}'
                            .format(instance.uuid))

    def _do_instance_force_stop(self, instanceForceStop):
        try:
            docker_client().stop(instanceForceStop['id'])
        except APIError as e:
            if e.message.response.status_code != 404:
                raise e

    def _is_instance_removed(self, instance, host):
        client = docker_client()
        container = self.get_container(client, instance)
        return container is None

    def _do_instance_remove(self, instance, host, progress):
        client = docker_client()
        container = self.get_container(client, instance)
        if container is None:
            return

        remove_container(client, container)

    def _do_instance_pull(self, pull_info, progress):
        client = docker_client()

        image = pull_info.image.data.dockerImage
        try:
            existing = client.inspect_image(image.fullName)
        except APIError:
            existing = None

        if pull_info.mode == 'cached' and existing is None:
            return existing

        if pull_info.complete:
            if existing is not None:
                client.remove_image(image.fullName + pull_info.tag)
            return

        DockerPool.image_pull(pull_info.image, progress)

        if pull_info.tag is not None:
            image_info = DockerPool.parse_repo_tag(image.fullName)
            client.tag(image.fullName, image_info['repo'],
                       image_info['tag'] + pull_info.tag, force=True)
        return client.inspect_image(image.fullName)

    def _do_instance_inspect(self, instanceInspectRequest):
        client = docker_client()
        container = None
        try:
            container_id = instanceInspectRequest.id
            container = self.get_container_by(client,
                                              lambda x: self._id_filter(
                                                  container_id, x))
        except (KeyError, AttributeError):
            pass

        if not container:
            try:
                name = '/{0}'.format(instanceInspectRequest.name)
                container = self.get_container_by(client,
                                                  lambda x: self._name_filter(
                                                      name, x))
            except (KeyError, AttributeError):
                pass

        if container:
            inspect = client.inspect_container(container)
            return inspect
Beispiel #10
0
 def __init__(self):
     KindBasedMixin.__init__(self, kind='docker')
     BaseComputeDriver.__init__(self)
     self.host_info = HostInfo(docker_client())
     self.system_images = self.get_agent_images(docker_client())
Beispiel #11
0
class DockerCompute(KindBasedMixin, BaseComputeDriver):
    def __init__(self):
        KindBasedMixin.__init__(self, kind='docker')
        BaseComputeDriver.__init__(self)
        self.host_info = HostInfo(docker_client())
        self.system_images = self.get_agent_images(docker_client())

    def get_agent_images(self, client):
        images = client.images(filters={'label': SYSTEM_LABEL})
        system_images = {}
        for i in images:
            try:
                label_val = i['Labels'][SYSTEM_LABEL]
                for l in i['RepoTags']:
                    system_images[l] = label_val
                    if l.endswith(':latest'):
                        alias = l[:-7]
                        system_images[alias] = label_val
            except KeyError:
                pass
        return system_images

    @staticmethod
    def get_container_by(client, func):
        containers = client.containers(all=True, trunc=False)
        containers = filter(func, containers)

        if len(containers) > 0:
            return containers[0]

        return None

    @staticmethod
    def find_first(containers, func):
        containers = filter(func, containers)

        if len(containers) > 0:
            return containers[0]

        return None

    def on_ping(self, ping, pong):
        if not DockerConfig.docker_enabled():
            return

        self._add_resources(ping, pong)
        self._add_instances(ping, pong)

    def _add_instances(self, ping, pong):
        if not utils.ping_include_instances(ping):
            return

        utils.ping_add_resources(pong, {
            'type': 'hostUuid',
            'uuid': DockerConfig.docker_uuid()
        })

        containers = []
        running, nonrunning = self._get_all_containers_by_state()

        for key, container in running.iteritems():
            self.add_container('running', container, containers)

        for key, container in nonrunning.iteritems():
            self.add_container('stopped', container, containers)

        utils.ping_add_resources(pong, *containers)
        utils.ping_set_option(pong, 'instances', True)

    def add_container(self, state, container, containers):
        try:
            labels = container['Labels']
        except KeyError:
            labels = []

        container_data = {
            'type': 'instance',
            'uuid': self._get_uuid(container),
            'state': state,
            'systemContainer': self._get_sys_container(container),
            'dockerId': container['Id'],
            'image': container['Image'],
            'labels': labels,
            'created': container['Created'],
        }
        containers.append(container_data)

    def _get_all_containers_by_state(self):
        client = docker_client(timeout=2)

        nonrunning_containers = {}
        for c in client.containers(all=True):
            # Blank status only wait to distinguish created from stopped
            if c['Status'] != '' and c['Status'] != 'Created':
                nonrunning_containers[c['Id']] = c

        running_containers = {}
        for c in client.containers(all=False):
            running_containers[c['Id']] = c
            del nonrunning_containers[c['Id']]

        return running_containers, nonrunning_containers

    def _get_sys_container(self, container):
        try:
            image = container['Image']
            if image in self.system_images:
                return self.system_images[image]
        except (TypeError, KeyError):
            pass

        try:
            return container['Labels']['io.rancher.container.system']
        except (TypeError, KeyError):
            pass

    def _get_uuid(self, container):
        try:
            uuid = container['Labels'][UUID_LABEL]
            if uuid:
                return uuid
        except (TypeError, KeyError):
            pass

        names = container['Names']
        if not names:
            # No name?? Make one up
            return 'no-uuid-%s' % container['Id']

        if names[0].startswith('/'):
            return names[0][1:]
        else:
            return names[0]

    def _determine_state(self, container):
        status = container['Status']
        if status == '' or (status is not None
                            and status.lower() == 'created'):
            return 'created'
        elif 'Up ' in status:
            return 'running'
        elif 'Exited ' in status:
            return 'stopped'
        else:
            # Unknown. Assume running and state should sync up eventually.
            return 'running'

    def _get_host_labels(self):
        try:
            return self.host_info.host_labels()
        except:
            log.exception("Error getting host labels")
            return {}

    def _get_host_create_labels(self):
        labels = Config.labels()
        if labels:
            return labels
        return {}

    def _add_resources(self, ping, pong):
        if not utils.ping_include_resources(ping):
            return

        stats = None
        if utils.ping_include_stats(ping):
            try:
                stats = self.host_info.collect_data()
            except:
                log.exception("Error getting host info stats")

        physical_host = Config.physical_host()

        compute = {
            'type': 'host',
            'kind': 'docker',
            'hostname': Config.hostname(),
            'createLabels': self._get_host_create_labels(),
            'labels': self._get_host_labels(),
            'physicalHostUuid': physical_host['uuid'],
            'uuid': DockerConfig.docker_uuid(),
            'info': stats
        }

        pool = {
            'type': 'storagePool',
            'kind': 'docker',
            'name': compute['hostname'] + ' Storage Pool',
            'hostUuid': compute['uuid'],
            'uuid': compute['uuid'] + '-pool'
        }

        resolved_ip = socket.gethostbyname(DockerConfig.docker_host_ip())

        ip = {
            'type': 'ipAddress',
            'uuid': resolved_ip,
            'address': resolved_ip,
            'hostUuid': compute['uuid'],
        }

        proxy = Config.host_proxy()
        if proxy is not None:
            compute['apiProxy'] = proxy

        utils.ping_add_resources(pong, physical_host, compute, pool, ip)

    def inspect(self, container):
        return docker_client().inspect_container(container)

    @staticmethod
    def _name_filter(name, container):
        names = container.get('Names')
        if names is None:
            return False
        found = False
        for n in names:
            if n.endswith(name):
                found = True
                break
        return found

    @staticmethod
    def _id_filter(id, container):
        container_id = container.get('Id')
        return id == container_id

    @staticmethod
    def _agent_id_filter(id, container):
        try:
            return container['Labels']['io.rancher.container.agent_id'] == id
        except (TypeError, KeyError, AttributeError):
            pass

    def get_container(self, client, instance, by_agent=False):
        if instance is None:
            return None

        # First look for UUID label directly
        labeled_containers = client.containers(
            all=True,
            trunc=False,
            filters={'label': '{}={}'.format(UUID_LABEL, instance.uuid)})
        if len(labeled_containers) > 0:
            return labeled_containers[0]

        # Next look by UUID using fallback method
        container_list = client.containers(all=True, trunc=False)
        container = self.find_first(
            container_list, lambda x: self._get_uuid(x) == instance.uuid)
        if container:
            return container

        if hasattr(instance, 'externalId') and instance.externalId:
            container = self.find_first(
                container_list,
                lambda x: self._id_filter(instance.externalId, x))

        if container:
            return container

        if by_agent and hasattr(instance, 'agentId') and instance.agentId:
            container = self.find_first(
                container_list,
                lambda x: self._agent_id_filter(str(instance.agentId), x))

        return container

    def _is_instance_active(self, instance, host):
        if is_no_op(instance):
            return True

        client = docker_client()
        container = self.get_container(client, instance)
        return _is_running(client, container)

    @staticmethod
    def _setup_legacy_command(create_config, instance, command):
        # This can be removed shortly once cattle removes
        # commandArgs
        if command is None or len(command.strip()) == 0:
            return None

        command_args = []
        try:
            command_args = instance.data.fields.commandArgs
        except (KeyError, AttributeError):
            pass

        if command_args is not None and len(command_args) > 0:
            command = [command]
            command.extend(command_args)

        if command is not None:
            create_config['command'] = command

    @staticmethod
    def _setup_command(create_config, instance):
        command = ""
        try:
            command = instance.data.fields.command
        except (KeyError, AttributeError):
            return None

        if isinstance(command, basestring):
            DockerCompute._setup_legacy_command(create_config, instance,
                                                command)
        else:
            if command is not None:
                create_config['command'] = command

    @staticmethod
    def _setup_dns_search(config, instance):
        try:
            if instance.systemContainer:
                return
        except (KeyError, AttributeError):
            pass
        # if only rancher search is specified,
        # prepend search with params read from the system
        all_rancher = True
        try:
            dns_search = config['dns_search']
            if dns_search is None or len(dns_search) == 0:
                return
            for search in dns_search:
                if search.endswith('rancher.internal'):
                    continue
                all_rancher = False
                break
        except KeyError:
            return

        if not all_rancher:
            return
        # read host's resolv.conf
        with open('/etc/resolv.conf', 'r') as f:
            for line in f:
                # in case multiple search lines
                # respect the last one
                s = []
                if line.startswith('search'):
                    s = line.split()[1:]
                for search in s[::-1]:
                    if search not in dns_search:
                        dns_search.insert(0, search)

    @staticmethod
    def _setup_links(start_config, instance):
        links = {}

        if 'instanceLinks' not in instance:
            return

        for link in instance.instanceLinks:
            if link.targetInstanceId is not None:
                links[link.targetInstance.uuid] = link.linkName

        start_config['links'] = links

    @staticmethod
    def _setup_ports(create_config, instance, start_config):
        ports = []
        bindings = {}
        try:
            for port in instance.ports:
                ports.append((port.privatePort, port.protocol))
                if port.publicPort is not None:
                    bind = '{0}/{1}'.format(port.privatePort, port.protocol)
                    bind_addr = ''
                    try:
                        if port.data.fields['bindAddress'] is not None:
                            bind_addr = port.data.fields['bindAddress']
                    except (AttributeError, KeyError):
                        pass

                    host_bind = (bind_addr, port.publicPort)
                    if bind not in bindings:
                        bindings[bind] = [host_bind]
                    else:
                        bindings[bind].append(host_bind)

        except (AttributeError, KeyError):
            pass

        if len(ports) > 0:
            create_config['ports'] = ports

        if len(bindings) > 0:
            start_config['port_bindings'] = bindings

    def _record_state(self, client, instance, docker_id=None):
        if docker_id is None:
            container = self.get_container(client, instance)
            if container is not None:
                docker_id = container['Id']

        if docker_id is None:
            return

        cont_dir = Config.container_state_dir()

        tmp_file_path = path.join(cont_dir, 'tmp-%s' % docker_id)
        if path.exists(tmp_file_path):
            remove(tmp_file_path)

        file_path = path.join(cont_dir, docker_id)
        if path.exists(file_path):
            remove(file_path)

        if not path.exists(cont_dir):
            makedirs(cont_dir)

        with open(tmp_file_path, 'w') as outfile:
            marshaller = get_type(MARSHALLER)
            data = marshaller.to_string(instance)
            outfile.write(data)

        rename(tmp_file_path, file_path)

    def purge_state(self, client, instance):
        container = self.get_container(client, instance)
        if container is None:
            return

        docker_id = container['Id']
        cont_dir = Config.container_state_dir()
        files = [
            path.join(cont_dir, 'tmp-%s' % docker_id),
            path.join(cont_dir, docker_id)
        ]

        for f in files:
            if path.exists(f):
                remove(f)

    def instance_activate(self,
                          req=None,
                          instanceHostMap=None,
                          processData=None,
                          **kw):
        instance, host = \
            BaseComputeDriver.get_instance_host_from_map(self, instanceHostMap)

        progress = Progress(req)
        client = docker_client()
        if instance is not None:
            instance.processData = processData

        with lock(instance):
            if self._is_instance_active(instance, host):
                self._record_state(client, instance)
                return self._reply(
                    req, self._get_response_data(req, instanceHostMap))

            self._do_instance_activate(instance, host, progress)

            data = self._get_response_data(req, instanceHostMap)

            return self._reply(req, data)

    def _do_instance_activate(self, instance, host, progress):
        if is_no_op(instance):
            return

        client = docker_client()

        image_tag = self._get_image_tag(instance)

        name = instance.uuid
        if instance.name and re.match(r'^[a-zA-Z0-9][a-zA-Z0-9_.-]+$',
                                      instance.name):
            try:
                client.inspect_container('r-{}'.format(instance.name))
            except NotFound:
                name = 'r-{}'.format(instance.name)

        create_config = {'name': name, 'detach': True}

        start_config = {
            'publish_all_ports': False,
            'privileged': self._is_true(instance, 'privileged'),
            'read_only': self._is_true(instance, 'readOnly'),
        }

        # These _setup_simple_config_fields calls should happen before all
        # other config because they stomp over config fields that other
        # setup methods might append to. Example: the environment field
        self._setup_simple_config_fields(create_config, instance,
                                         CREATE_CONFIG_FIELDS)

        self._setup_simple_config_fields(start_config, instance,
                                         START_CONFIG_FIELDS)

        add_label(create_config, {UUID_LABEL: instance.uuid})
        if instance.name:
            add_label(create_config,
                      {'io.rancher.container.name': instance.name})
        self._setup_dns_search(start_config, instance)

        self._setup_logging(start_config, instance)

        self._setup_hostname(create_config, instance)

        self._setup_command(create_config, instance)

        self._setup_ports(create_config, instance, start_config)

        self._setup_volumes(create_config, instance, start_config, client)

        self._setup_links(start_config, instance)

        self._setup_networking(instance, host, create_config, start_config)

        self._flag_system_container(instance, create_config)

        self._setup_proxy(instance, create_config)

        setup_cattle_config_url(instance, create_config)

        create_config['host_config'] = \
            client.create_host_config(**start_config)

        self._setup_device_options(create_config['host_config'], instance)

        container = self.get_container(client, instance)
        created = False
        if container is None:
            container = self._create_container(client, create_config,
                                               image_tag, instance, name,
                                               progress)
            created = True

        container_id = container['Id']

        log.info('Starting docker container [%s] docker id [%s] %s', name,
                 container_id, start_config)

        try:
            client.start(container_id)
        except Exception as e:
            if created:
                remove_container(client, container)
            raise e

        self._record_state(client, instance, docker_id=container['Id'])

    def _create_container(self, client, create_config, image_tag, instance,
                          name, progress):
        log.info('Creating docker container [%s] from config %s', name,
                 create_config)

        labels = create_config['labels']
        if labels.get('io.rancher.container.pull_image', None) == 'always':
            self._do_instance_pull(
                JsonObject({
                    'image': instance.image,
                    'tag': None,
                    'mode': 'all',
                    'complete': False,
                }), progress)
        try:
            del create_config['name']
            command = ''
            try:
                command = create_config['command']
                del create_config['command']
            except KeyError:
                pass
            config = client.create_container_config(image_tag, command,
                                                    **create_config)
            try:
                id = instance.data
                config['VolumeDriver'] = id.fields['volumeDriver']
            except (KeyError, AttributeError):
                pass
            container = client.create_container_from_config(config, name)
        except APIError as e:
            if e.message.response.status_code == 404:
                pull_image(instance.image, progress)
                container = client.create_container_from_config(config, name)
            else:
                raise
        return container

    def _flag_system_container(self, instance, create_config):
        try:
            if instance.systemContainer:
                add_label(
                    create_config,
                    {'io.rancher.container.system': instance.systemContainer})
        except (KeyError, AttributeError):
            pass

    def _setup_proxy(self, instance, create_config):
        try:
            if instance.systemContainer:
                if 'environment' not in create_config:
                    create_config['environment'] = {}

                for i in ['http_proxy', 'https_proxy', 'NO_PROXY']:
                    try:
                        create_config['environment'][i] = environ[i]
                    except KeyError:
                        pass
        except (KeyError, AttributeError):
            pass

    def _setup_simple_config_fields(self, config, instance, fields):
        for src, dest in fields:
            try:
                src_obj = instance.data.fields[src]
                config[dest] = JsonObject.unwrap(src_obj)
            except (KeyError, AttributeError):
                pass

    def _setup_volumes(self, create_config, instance, start_config, client):
        try:
            volumes = instance.data.fields['dataVolumes']
            volumes_map = {}
            binds_map = {}
            if volumes is not None and len(volumes) > 0:
                for i in volumes:
                    parts = i.split(':', 3)
                    if len(parts) == 1:
                        volumes_map[parts[0]] = {}
                    else:
                        if len(parts) == 3:
                            mode = parts[2]
                        else:
                            mode = 'rw'
                        bind = {'bind': parts[1], 'mode': mode}
                        binds_map[parts[0]] = bind
                create_config['volumes'] = volumes_map
                start_config['binds'] = binds_map
        except (KeyError, AttributeError):
            pass

        try:
            containers = []
            for vfc in instance['dataVolumesFromContainers']:
                container = self.get_container(client, vfc)
                if container:
                    containers.append(container['Id'])
            if containers:
                start_config['volumes_from'] = containers
        except KeyError:
            pass

        try:
            for v in instance['volumesFromDataVolumeMounts']:
                if not DockerPool.is_volume_active(v):
                    DockerPool.do_volume_activate(v)
        except KeyError:
            pass

    def _get_image_tag(self, instance):
        try:
            return instance.image.data.dockerImage.fullName
        except (KeyError, AttributeError):
            raise Exception('Can not start container with no image')

    def _setup_logging(self, start_config, instance):
        try:
            if start_config.get('log_config', None) is None:
                return

            type = start_config['log_config']['driver']
            del start_config['log_config']['driver']
            start_config['log_config']['type'] = type
        except (KeyError, AttributeError):
            pass

        for i in ['type', 'config']:
            bad = True
            try:
                obj = start_config['log_config'][i]
                if obj is not None:
                    bad = False
                    start_config['log_config'][i] = JsonObject.unwrap(obj)
            except (KeyError, AttributeError):
                pass
            if bad and 'log_config' in start_config:
                del start_config['log_config']

    def _setup_hostname(self, create_config, instance):
        try:
            create_config['hostname'] = instance.hostname
        except (KeyError, AttributeError):
            pass

    def _setup_device_options(self, config, instance):
        option_configs = \
            [('readIops', [], 'BlkioDeviceReadIOps', 'Rate'),
             ('writeIops', [], 'BlkioDeviceWriteIOps', 'Rate'),
             ('readBps', [], 'BlkioDeviceReadBps', 'Rate'),
             ('writeBps', [], 'BlkioDeviceWriteBps', 'Rate'),
             ('weight', [], 'BlkioWeightDevice', 'Weight')]

        try:
            device_options = instance.data.fields['blkioDeviceOptions']
        except (KeyError, AttributeError):
            return

        for dev, options in device_options.iteritems():
            if dev == 'DEFAULT_DISK':
                dev = self.host_info.get_default_disk()
                if not dev:
                    log.warn(
                        "Couldn't find default device. Not setting"
                        "device options: %s", options)
                    continue
            for k, dev_list, _, field in option_configs:
                if k in options and options[k] is not None:
                    value = options[k]
                    dev_list.append({'Path': dev, field: value})

        for _, dev_list, docker_field, _ in option_configs:
            if len(dev_list):
                config[docker_field] = dev_list

    def _setup_networking(self, instance, host, create_config, start_config):
        client = docker_client()

        ports_supported, hostname_supported = setup_network_mode(
            instance, self, client, create_config, start_config)
        setup_mac_and_ip(instance,
                         create_config,
                         set_mac=ports_supported,
                         set_hostname=hostname_supported)
        setup_ports(instance, create_config, start_config, ports_supported)
        setup_links(instance, create_config, start_config)
        setup_ipsec(instance, host, create_config, start_config)
        setup_dns(instance)

    def _is_true(self, instance, key):
        try:
            return instance.data.fields[key] is True
        except (KeyError, AttributeError):
            return False

    def _get_instance_host_map_data(self, obj):
        client = docker_client()
        inspect = None
        docker_mounts = None
        existing = self.get_container(client, obj.instance)
        docker_ports = []
        docker_ip = None

        if existing is not None:
            inspect = client.inspect_container(existing['Id'])
            docker_mounts = self._get_mount_data(obj.host, existing['Id'])
            docker_ip = inspect['NetworkSettings']['IPAddress']
            if existing.get('Ports') is not None:
                for port in existing['Ports']:
                    private_port = '{0}/{1}'.format(port['PrivatePort'],
                                                    port['Type'])
                    port_spec = private_port

                    bind_addr = ''
                    if 'IP' in port:
                        bind_addr = '%s:' % port['IP']

                    public_port = ''
                    if 'PublicPort' in port:
                        public_port = '%s:' % port['PublicPort']
                    elif 'IP' in port:
                        public_port = ':'

                    port_spec = bind_addr + public_port + port_spec
                    docker_ports.append(port_spec)

        update = {
            'instance': {
                '+data': {
                    'dockerContainer': existing,
                    'dockerInspect': inspect,
                    '+fields': {
                        'dockerHostIp': DockerConfig.docker_host_ip(),
                        'dockerPorts': docker_ports,
                        'dockerIp': docker_ip
                    }
                }
            }
        }
        if existing is not None:
            update['instance']['externalId'] = existing['Id']

        if docker_mounts is not None:
            update['instance']['+data']['dockerMounts'] = docker_mounts

        return update

    def _get_mount_data(self, host, container_id):
        try:
            client = docker_client(version='1.21')
            inspect = client.inspect_container(container_id)
            return inspect['Mounts']
        except (KeyError, APIError):
            pass

    def _is_instance_inactive(self, instance, host):
        if is_no_op(instance):
            return True

        c = docker_client()
        container = self.get_container(c, instance)

        return _is_stopped(c, container)

    def _do_instance_deactivate(self, instance, host, progress):
        if is_no_op(instance):
            return

        c = docker_client()
        timeout = 10

        try:
            timeout = int(instance.processData.timeout)
        except (TypeError, KeyError, AttributeError):
            pass

        container = self.get_container(c, instance)

        c.stop(container['Id'], timeout=timeout)

        container = self.get_container(c, instance)
        if not _is_stopped(c, container):
            c.kill(container['Id'])

        container = self.get_container(c, instance)
        if not _is_stopped(c, container):
            raise Exception('Failed to stop container {0}'.format(
                instance.uuid))

    def _do_instance_force_stop(self, instanceForceStop):
        try:
            docker_client().stop(instanceForceStop['id'])
        except APIError as e:
            if e.message.response.status_code != 404:
                raise e

    def _is_instance_removed(self, instance, host):
        client = docker_client()
        container = self.get_container(client, instance)
        return container is None

    def _do_instance_remove(self, instance, host, progress):
        client = docker_client()
        container = self.get_container(client, instance)
        if container is None:
            return

        remove_container(client, container)

    def _do_instance_pull(self, pull_info, progress):
        client = docker_client()

        image = pull_info.image.data.dockerImage
        try:
            existing = client.inspect_image(image.fullName)
        except APIError:
            existing = None

        if pull_info.mode == 'cached' and existing is None:
            return existing

        if pull_info.complete:
            if existing is not None:
                client.remove_image(image.fullName + pull_info.tag)
            return

        DockerPool.image_pull(pull_info.image, progress)

        if pull_info.tag is not None:
            image_info = DockerPool.parse_repo_tag(image.fullName)
            client.tag(image.fullName,
                       image_info['repo'],
                       image_info['tag'] + pull_info.tag,
                       force=True)
        return client.inspect_image(image.fullName)

    def _do_instance_inspect(self, instanceInspectRequest):
        client = docker_client()
        container = None
        try:
            container_id = instanceInspectRequest.id
            container = self.get_container_by(
                client, lambda x: self._id_filter(container_id, x))
        except (KeyError, AttributeError):
            pass

        if not container:
            try:
                name = '/{0}'.format(instanceInspectRequest.name)
                container = self.get_container_by(
                    client, lambda x: self._name_filter(name, x))
            except (KeyError, AttributeError):
                pass

        if container:
            inspect = client.inspect_container(container)
            return inspect
def host_data_non_linux(mocker):
    mocker.patch.object(platform, 'system', return_value='notLinux')
    return HostInfo().collect_data()
Beispiel #13
0
 def __init__(self):
     KindBasedMixin.__init__(self, kind='docker')
     BaseComputeDriver.__init__(self)
     self.host_info = HostInfo()
Beispiel #14
0
class DockerCompute(KindBasedMixin, BaseComputeDriver):
    def __init__(self):
        KindBasedMixin.__init__(self, kind='docker')
        BaseComputeDriver.__init__(self)
        self.host_info = HostInfo()

    @staticmethod
    def get_container_by(client, func):
        containers = client.containers(all=True, trunc=False)
        containers = filter(func, containers)

        if len(containers) > 0:
            return containers[0]

        return None

    def on_ping(self, ping, pong):
        if not DockerConfig.docker_enabled():
            return

        self._add_resources(ping, pong)
        self._add_instances(ping, pong)

    def _add_instances(self, ping, pong):
        if not utils.ping_include_instances(ping):
            return

        containers = []
        for c in docker_client().containers():
            names = c.get('Names')
            if names is None:
                continue

            for name in names:
                if name.startswith('/'):
                    name = name[1:]
                    containers.append({
                        'type': 'instance',
                        'uuid': name,
                        'state': 'running',
                        'dockerId': c.get('Id')
                    })

        utils.ping_add_resources(pong, *containers)
        utils.ping_set_option(pong, 'instances', True)

    def _add_resources(self, ping, pong):
        if not utils.ping_include_resources(ping):
            return

        stats = None
        if utils.ping_include_stats(ping):
            try:
                stats = self.host_info.collect_data()
            except:
                log.exception("Error geting host info stats")

        physical_host = Config.physical_host()

        compute = {
            'type': 'host',
            'kind': 'docker',
            'name': Config.hostname(),
            'physicalHostUuid': physical_host['uuid'],
            'uuid': DockerConfig.docker_uuid(),
            'info': stats
        }

        pool = {
            'type': 'storagePool',
            'kind': 'docker',
            'name': compute['name'] + ' Storage Pool',
            'hostUuid': compute['uuid'],
            'uuid': compute['uuid'] + '-pool'
        }

        ip = {
            'type': 'ipAddress',
            'uuid': DockerConfig.docker_host_ip(),
            'address': DockerConfig.docker_host_ip(),
            'hostUuid': compute['uuid'],
        }

        proxy = Config.host_proxy()
        if proxy is not None:
            compute['apiProxy'] = proxy

        utils.ping_add_resources(pong, physical_host, compute, pool, ip)

    def inspect(self, container):
        return docker_client().inspect_container(container)

    @staticmethod
    def _name_filter(name, container):
        names = container.get('Names')
        if names is None:
            return False
        found = False
        for n in names:
            if n.endswith(name):
                found = True
                break
        return found

    @staticmethod
    def _id_filter(id, container):
        container_id = container.get('Id')
        return id == container_id

    def get_container(self, client, instance):
        name = '/{0}'.format(instance.uuid)
        container = self.get_container_by(client,
                                          lambda x: self._name_filter(name, x))
        if container:
            return container

        if hasattr(instance, 'externalId') and instance.externalId:
            return self.get_container_by(client, lambda x: self._id_filter(
                instance.externalId, x))

    def _is_instance_active(self, instance, host):
        if is_no_op(instance):
            return True

        client = self._get_docker_client(host)
        container = self.get_container(client, instance)
        return _is_running(client, container)

    @staticmethod
    def _get_docker_client(host):
        cluster_connection = None
        tls_config = None
        try:
            cluster_connection = host['clusterConnection']
            if cluster_connection.startswith('https'):
                try:
                    account_id = host['accountId']
                    ca_crt = host['caCrt']
                    client_crt = host['clientCrt']
                    client_key = host['clientKey']

                    client_certs_dir = Config.client_certs_dir()
                    acct_client_cert_dir = \
                        path.join(client_certs_dir, str(account_id))
                    if not path.exists(acct_client_cert_dir):
                        log.debug('Creating client cert directory: %s',
                                  acct_client_cert_dir)
                        makedirs(acct_client_cert_dir)
                    if ca_crt:
                        log.debug('Writing cert auth')
                        with open(path.join(acct_client_cert_dir, 'ca.crt'),
                                  'w') as f:
                            f.write(ca_crt)
                    if client_crt:
                        log.debug('Writing client cert')
                        with open(path.join(acct_client_cert_dir,
                                            'client.crt'),
                                  'w') as f:
                            f.write(client_crt)
                    if client_key:
                        log.debug('Writing client key')
                        with open(path.join(acct_client_cert_dir,
                                            'client.key'),
                                  'w') as f:
                            f.write(client_key)
                    if ca_crt and client_crt and client_key:
                        tls_config = tls.TLSConfig(
                            client_cert=(
                                path.join(acct_client_cert_dir, 'client.crt'),
                                path.join(acct_client_cert_dir, 'client.key')
                            ),
                            verify=path.join(acct_client_cert_dir, 'ca.crt'),
                            assert_hostname=False
                        )
                except (KeyError, AttributeError) as e:
                    raise Exception(
                        'Unable to process cert/keys for cluster',
                        cluster_connection,
                        e)
        except (KeyError, AttributeError):
            pass

        return docker_client(
            base_url_override=cluster_connection,
            tls_config=tls_config)

    @staticmethod
    def _setup_legacy_command(create_config, instance, command):
        # This can be removed shortly once cattle removes
        # commandArgs
        if command is None or len(command.strip()) == 0:
            return None

        command_args = []
        try:
            command_args = instance.data.fields.commandArgs
        except (KeyError, AttributeError):
            pass

        if command_args is not None and len(command_args) > 0:
            command = [command]
            command.extend(command_args)

        if command is not None:
            create_config['command'] = command

    @staticmethod
    def _setup_command(create_config, instance):
        command = ""
        try:
            command = instance.data.fields.command
        except (KeyError, AttributeError):
            return None

        if isinstance(command, basestring):
            DockerCompute._setup_legacy_command(create_config, instance,
                                                command)
        else:
            if command is not None:
                create_config['command'] = command

    @staticmethod
    def _setup_links(start_config, instance):
        links = {}

        if 'instanceLinks' not in instance:
            return

        for link in instance.instanceLinks:
            if link.targetInstanceId is not None:
                links[link.targetInstance.uuid] = link.linkName

        start_config['links'] = links

    @staticmethod
    def _setup_ports(create_config, instance):
        ports = []
        try:
            for port in instance.ports:
                ports.append((port.privatePort, port.protocol))
        except (AttributeError, KeyError):
            pass

        if len(ports) > 0:
            create_config['ports'] = ports

    def _record_state(self, client, instance, docker_id=None):
        if docker_id is None:
            container = self.get_container(client, instance)
            if container is not None:
                docker_id = container['Id']

        if docker_id is None:
            return

        cont_dir = Config.container_state_dir()

        tmp_file_path = path.join(cont_dir, 'tmp-%s' % docker_id)
        if path.exists(tmp_file_path):
            remove(tmp_file_path)

        file_path = path.join(cont_dir, docker_id)
        if path.exists(file_path):
            remove(file_path)

        if not path.exists(cont_dir):
            makedirs(cont_dir)

        with open(tmp_file_path, 'w') as outfile:
            marshaller = get_type(MARSHALLER)
            data = marshaller.to_string(instance)
            outfile.write(data)

        rename(tmp_file_path, file_path)

    def purge_state(self, client, instance):
        container = self.get_container(client, instance)
        if container is None:
            return

        docker_id = container['Id']
        cont_dir = Config.container_state_dir()
        files = [path.join(cont_dir, 'tmp-%s' % docker_id),
                 path.join(cont_dir, docker_id)]

        for f in files:
            if path.exists(f):
                remove(f)

    def instance_activate(self, req=None, instanceHostMap=None,
                          processData=None, **kw):
        instance, host = \
            BaseComputeDriver.get_instance_host_from_map(self, instanceHostMap)

        progress = Progress(req)
        client = self._get_docker_client(host)
        if instance is not None:
            instance.processData = processData

        with lock(instance):
            if self._is_instance_active(instance, host):
                self._record_state(client, instance)
                return self._reply(req, self.
                                   _get_response_data(req, instanceHostMap))

            self._do_instance_activate(instance, host, progress)

            data = self._get_response_data(req, instanceHostMap)

            return self._reply(req, data)

    def _do_instance_activate(self, instance, host, progress):
        if is_no_op(instance):
            return

        client = self._get_docker_client(host)

        try:
            image_tag = instance.image.data.dockerImage.fullName
        except KeyError:
            raise Exception('Can not start container with no image')

        name = instance.uuid

        create_config = {
            'name': name,
            'detach': True
        }

        start_config = {
            'publish_all_ports': False,
            'privileged': self._is_privileged(instance)
        }

        # These _setup_simple_config_fields calls should happen before all
        # other config because they stomp over config fields that other
        # setup methods might append to. Example: the environment field
        self._setup_simple_config_fields(create_config, instance,
                                         CREATE_CONFIG_FIELDS)

        self._setup_simple_config_fields(start_config, instance,
                                         START_CONFIG_FIELDS)

        add_label(create_config, RANCHER_UUID=instance.uuid)

        self._setup_hostname(create_config, instance)

        self._setup_command(create_config, instance)

        self._setup_ports(create_config, instance)

        self._setup_volumes(create_config, instance, start_config, client)

        self._setup_restart_policy(instance, start_config)

        self._setup_links(start_config, instance)

        self._setup_networking(instance, host, create_config, start_config)

        setup_cattle_config_url(instance, create_config)

        container = self._create_container(client, create_config,
                                           image_tag, instance, name,
                                           progress)
        container_id = container['Id']

        log.info('Starting docker container [%s] docker id [%s] %s', name,
                 container_id, start_config)

        client.start(container_id, **start_config)

        self._record_state(client, instance, docker_id=container['Id'])

    def _create_container(self, client, create_config, image_tag, instance,
                          name, progress):
        container = self.get_container(client, instance)
        if container is None:
            log.info('Creating docker container [%s] from config %s', name,
                     create_config)

            try:
                container = client.create_container(image_tag, **create_config)
            except APIError as e:
                if e.message.response.status_code == 404:
                    pull_image(instance.image, progress)
                    container = client.create_container(image_tag,
                                                        **create_config)
                else:
                    raise
        return container

    def _setup_simple_config_fields(self, config, instance, fields):
        for src, dest in fields:
            try:
                config[dest] = instance.data.fields[src]
            except (KeyError, AttributeError):
                pass

    def _setup_volumes(self, create_config, instance, start_config, client):
        try:
            volumes = instance.data.fields['dataVolumes']
            volumes_map = {}
            binds_map = {}
            if volumes is not None and len(volumes) > 0:
                for i in volumes:
                    parts = i.split(':', 3)
                    if len(parts) == 1:
                        volumes_map[parts[0]] = {}
                    else:
                        read_only = len(parts) == 3 and parts[2] == 'ro'
                        bind = {'bind': parts[1], 'ro': read_only}
                        binds_map[parts[0]] = bind
                create_config['volumes'] = volumes_map
                start_config['binds'] = binds_map
        except (KeyError, AttributeError):
            pass

        volmgr.update_managed_volume(instance, create_config, start_config)
        try:
            containers = []
            for vfc in instance['dataVolumesFromContainers']:
                container = self.get_container(client, vfc)
                if container:
                    containers.append(container['Id'])
            if containers:
                start_config['volumes_from'] = containers
        except KeyError:
            pass

    def _setup_restart_policy(self, instance, start_config):
        try:
            restart_policy = instance.data.fields['restartPolicy']
            refactored_res_policy = {}
            for res_pol_key in restart_policy.keys():
                refactored_res_policy[_to_upper_case(res_pol_key)] = \
                    restart_policy[res_pol_key]
            start_config['restart_policy'] = refactored_res_policy
        except (KeyError, AttributeError):
            pass

    def _setup_hostname(self, create_config, instance):
        try:
            create_config['hostname'] = instance.hostname
        except (KeyError, AttributeError):
            pass

    def _setup_networking(self, instance, host, create_config, start_config):
        setup_mac_and_ip(instance, create_config)
        setup_ports(instance, create_config, start_config)
        setup_links(instance, create_config, start_config)
        setup_ipsec(instance, host, create_config, start_config)

    def _is_privileged(self, instance):
        try:
            return instance.data.fields['privileged']
        except (KeyError, AttributeError):
            return False

    def _get_instance_host_map_data(self, obj):
        client = self._get_docker_client(obj.host)
        inspect = None
        existing = self.get_container(client, obj.instance)
        docker_ports = {}
        docker_ip = None

        if existing is not None:
            inspect = client.inspect_container(existing['Id'])
            docker_ip = inspect['NetworkSettings']['IPAddress']
            if existing.get('Ports') is not None:
                for port in existing['Ports']:
                    if 'PublicPort' in port and 'PrivatePort' not in port:
                        # Remove after docker 0.12/1.0 is released
                        private_port = '{0}/{1}'.format(port['PublicPort'],
                                                        port['Type'])
                        docker_ports[private_port] = None
                    elif 'PublicPort' in port:
                        private_port = '{0}/{1}'.format(port['PrivatePort'],
                                                        port['Type'])
                        docker_ports[private_port] = str(port['PublicPort'])
                    else:
                        private_port = '{0}/{1}'.format(port['PrivatePort'],
                                                        port['Type'])
                        docker_ports[private_port] = None

        update = {
            'instance': {
                '+data': {
                    'dockerContainer': existing,
                    'dockerInspect': inspect,
                    '+fields': {
                        'dockerHostIp': DockerConfig.docker_host_ip(),
                        'dockerPorts': docker_ports,
                        'dockerIp': docker_ip
                    }
                }
            }
        }
        if existing is not None:
            update['instance']['externalId'] = existing['Id']

        return update

    def _is_instance_inactive(self, instance, host):
        if is_no_op(instance):
            return True

        c = self._get_docker_client(host)
        container = self.get_container(c, instance)

        return _is_stopped(c, container)

    def _do_instance_deactivate(self, instance, host, progress):
        if is_no_op(instance):
            return

        c = self._get_docker_client(host)
        timeout = 10

        try:
            timeout = int(instance.processData.timeout)
        except (TypeError, KeyError, AttributeError):
            pass

        container = self.get_container(c, instance)

        c.stop(container['Id'], timeout=timeout)

        container = self.get_container(c, instance)
        if not _is_stopped(c, container):
            c.kill(container['Id'])

        container = self.get_container(c, instance)
        if not _is_stopped(c, container):
            raise Exception('Failed to stop container {0}'
                            .format(instance.uuid))
Beispiel #15
0
class DockerCompute(KindBasedMixin, BaseComputeDriver):
    def __init__(self):
        KindBasedMixin.__init__(self, kind='docker')
        BaseComputeDriver.__init__(self)
        self.host_info = HostInfo(docker_client())
        self.system_images = self.get_agent_images(docker_client())

    def get_agent_images(self, client):
        images = client.images(filters={'label': SYSTEM_LABEL})
        system_images = {}
        for i in images:
            try:
                label_val = i['Labels'][SYSTEM_LABEL]
                for l in i['RepoTags']:
                    system_images[l] = label_val
            except KeyError:
                pass
        return system_images

    @staticmethod
    def get_container_by(client, func):
        containers = client.containers(all=True, trunc=False)
        containers = filter(func, containers)

        if len(containers) > 0:
            return containers[0]

        return None

    def on_ping(self, ping, pong):
        if not DockerConfig.docker_enabled():
            return

        self._add_resources(ping, pong)
        self._add_instances(ping, pong)

    def _add_instances(self, ping, pong):
        if not utils.ping_include_instances(ping):
            return

        utils.ping_add_resources(pong, {
            'type': 'hostUuid',
            'uuid': DockerConfig.docker_uuid()
        })

        containers = []
        running, nonrunning = self._get_all_containers_by_state()

        for key, container in running.iteritems():
            self.add_container('running', container, containers)

        for key, container in nonrunning.iteritems():
            self.add_container('stopped', container, containers)

        utils.ping_add_resources(pong, *containers)
        utils.ping_set_option(pong, 'instances', True)

    def add_container(self, state, container, containers):
        try:
            labels = container['Labels']
        except KeyError:
            labels = []

        container_data = {
            'type': 'instance',
            'uuid': self._get_uuid(container),
            'state': state,
            'systemContainer': self._get_sys_container(container),
            'dockerId': container['Id'],
            'image': container['Image'],
            'labels': labels,
            'created': container['Created'],
        }
        containers.append(container_data)

    def _get_all_containers_by_state(self):
        client = docker_client()

        nonrunning_containers = {}
        for c in client.containers(all=True):
            # Blank status only wait to distinguish created from stopped
            if c['Status'] != '' and c['Status'] != 'Created':
                nonrunning_containers[c['Id']] = c

        running_containers = {}
        for c in client.containers(all=False):
            running_containers[c['Id']] = c
            del nonrunning_containers[c['Id']]

        return running_containers, nonrunning_containers

    def _get_sys_container(self, container):
        try:
            image = container['Image']
            if image in self.system_images:
                return self.system_images[image]
        except (TypeError, KeyError):
            pass

        try:
            return container['Labels']['io.rancher.container.system']
        except (TypeError, KeyError):
            pass

    def _get_uuid(self, container):
        try:
            uuid = container['Labels']['io.rancher.container.uuid']
            if uuid:
                return uuid
        except (TypeError, KeyError):
            pass

        names = container['Names']
        if not names:
            # No name?? Make one up
            return 'no-uuid-%s' % container['Id']

        if names[0].startswith('/'):
            return names[0][1:]
        else:
            return names[0]

    def _determine_state(self, container):
        status = container['Status']
        if status == '' or (status is not None
                            and status.lower() == 'created'):
            return 'created'
        elif 'Up ' in status:
            return 'running'
        elif 'Exited ' in status:
            return 'stopped'
        else:
            # Unknown. Assume running and state should sync up eventually.
            return 'running'

    def _add_resources(self, ping, pong):
        if not utils.ping_include_resources(ping):
            return

        stats = None
        if utils.ping_include_stats(ping):
            try:
                stats = self.host_info.collect_data()
            except:
                log.exception("Error geting host info stats")

        physical_host = Config.physical_host()

        compute = {
            'type': 'host',
            'kind': 'docker',
            'name': Config.hostname(),
            'labels': Config.labels(),
            'physicalHostUuid': physical_host['uuid'],
            'uuid': DockerConfig.docker_uuid(),
            'info': stats
        }

        pool = {
            'type': 'storagePool',
            'kind': 'docker',
            'name': compute['name'] + ' Storage Pool',
            'hostUuid': compute['uuid'],
            'uuid': compute['uuid'] + '-pool'
        }

        ip = {
            'type': 'ipAddress',
            'uuid': DockerConfig.docker_host_ip(),
            'address': DockerConfig.docker_host_ip(),
            'hostUuid': compute['uuid'],
        }

        proxy = Config.host_proxy()
        if proxy is not None:
            compute['apiProxy'] = proxy

        utils.ping_add_resources(pong, physical_host, compute, pool, ip)

    def inspect(self, container):
        return docker_client().inspect_container(container)

    @staticmethod
    def _name_filter(name, container):
        names = container.get('Names')
        if names is None:
            return False
        found = False
        for n in names:
            if n.endswith(name):
                found = True
                break
        return found

    @staticmethod
    def _id_filter(id, container):
        container_id = container.get('Id')
        return id == container_id

    def get_container(self, client, instance):
        if instance is None:
            return None

        name = '/{0}'.format(instance.uuid)
        container = self.get_container_by(client,
                                          lambda x: self._name_filter(name, x))
        if container:
            return container

        if hasattr(instance, 'externalId') and instance.externalId:
            return self.get_container_by(
                client, lambda x: self._id_filter(instance.externalId, x))

    def _is_instance_active(self, instance, host):
        if is_no_op(instance):
            return True

        client = self._get_docker_client(host)
        container = self.get_container(client, instance)
        return _is_running(client, container)

    @staticmethod
    def _get_docker_client(host):
        cluster_connection = None
        tls_config = None
        try:
            cluster_connection = host['clusterConnection']
            if cluster_connection.startswith('https'):
                try:
                    account_id = host['accountId']
                    ca_crt = host['caCrt']
                    client_crt = host['clientCrt']
                    client_key = host['clientKey']

                    client_certs_dir = Config.client_certs_dir()
                    acct_client_cert_dir = \
                        path.join(client_certs_dir, str(account_id))
                    if not path.exists(acct_client_cert_dir):
                        log.debug('Creating client cert directory: %s',
                                  acct_client_cert_dir)
                        makedirs(acct_client_cert_dir)
                    if ca_crt:
                        log.debug('Writing cert auth')
                        with open(path.join(acct_client_cert_dir, 'ca.crt'),
                                  'w') as f:
                            f.write(ca_crt)
                    if client_crt:
                        log.debug('Writing client cert')
                        with open(
                                path.join(acct_client_cert_dir, 'client.crt'),
                                'w') as f:
                            f.write(client_crt)
                    if client_key:
                        log.debug('Writing client key')
                        with open(
                                path.join(acct_client_cert_dir, 'client.key'),
                                'w') as f:
                            f.write(client_key)
                    if ca_crt and client_crt and client_key:
                        tls_config = tls.TLSConfig(
                            client_cert=(path.join(acct_client_cert_dir,
                                                   'client.crt'),
                                         path.join(acct_client_cert_dir,
                                                   'client.key')),
                            verify=path.join(acct_client_cert_dir, 'ca.crt'),
                            assert_hostname=False)
                except (KeyError, AttributeError) as e:
                    raise Exception('Unable to process cert/keys for cluster',
                                    cluster_connection, e)
        except (KeyError, AttributeError):
            pass

        return docker_client(base_url_override=cluster_connection,
                             tls_config=tls_config)

    @staticmethod
    def _setup_legacy_command(create_config, instance, command):
        # This can be removed shortly once cattle removes
        # commandArgs
        if command is None or len(command.strip()) == 0:
            return None

        command_args = []
        try:
            command_args = instance.data.fields.commandArgs
        except (KeyError, AttributeError):
            pass

        if command_args is not None and len(command_args) > 0:
            command = [command]
            command.extend(command_args)

        if command is not None:
            create_config['command'] = command

    @staticmethod
    def _setup_command(create_config, instance):
        command = ""
        try:
            command = instance.data.fields.command
        except (KeyError, AttributeError):
            return None

        if isinstance(command, basestring):
            DockerCompute._setup_legacy_command(create_config, instance,
                                                command)
        else:
            if command is not None:
                create_config['command'] = command

    @staticmethod
    def _setup_links(start_config, instance):
        links = {}

        if 'instanceLinks' not in instance:
            return

        for link in instance.instanceLinks:
            if link.targetInstanceId is not None:
                links[link.targetInstance.uuid] = link.linkName

        start_config['links'] = links

    @staticmethod
    def _setup_ports(create_config, instance):
        ports = []
        try:
            for port in instance.ports:
                ports.append((port.privatePort, port.protocol))
        except (AttributeError, KeyError):
            pass

        if len(ports) > 0:
            create_config['ports'] = ports

    def _record_state(self, client, instance, docker_id=None):
        if docker_id is None:
            container = self.get_container(client, instance)
            if container is not None:
                docker_id = container['Id']

        if docker_id is None:
            return

        cont_dir = Config.container_state_dir()

        tmp_file_path = path.join(cont_dir, 'tmp-%s' % docker_id)
        if path.exists(tmp_file_path):
            remove(tmp_file_path)

        file_path = path.join(cont_dir, docker_id)
        if path.exists(file_path):
            remove(file_path)

        if not path.exists(cont_dir):
            makedirs(cont_dir)

        with open(tmp_file_path, 'w') as outfile:
            marshaller = get_type(MARSHALLER)
            data = marshaller.to_string(instance)
            outfile.write(data)

        rename(tmp_file_path, file_path)

    def purge_state(self, client, instance):
        container = self.get_container(client, instance)
        if container is None:
            return

        docker_id = container['Id']
        cont_dir = Config.container_state_dir()
        files = [
            path.join(cont_dir, 'tmp-%s' % docker_id),
            path.join(cont_dir, docker_id)
        ]

        for f in files:
            if path.exists(f):
                remove(f)

    def instance_activate(self,
                          req=None,
                          instanceHostMap=None,
                          processData=None,
                          **kw):
        instance, host = \
            BaseComputeDriver.get_instance_host_from_map(self, instanceHostMap)

        progress = Progress(req)
        client = self._get_docker_client(host)
        if instance is not None:
            instance.processData = processData

        with lock(instance):
            if self._is_instance_active(instance, host):
                self._record_state(client, instance)
                return self._reply(
                    req, self._get_response_data(req, instanceHostMap))

            self._do_instance_activate(instance, host, progress)

            data = self._get_response_data(req, instanceHostMap)

            return self._reply(req, data)

    def _do_instance_activate(self, instance, host, progress):
        if is_no_op(instance):
            return

        client = self._get_docker_client(host)

        image_tag = self._get_image_tag(instance)

        name = instance.uuid

        create_config = {'name': name, 'detach': True}

        start_config = {
            'publish_all_ports': False,
            'privileged': self._is_true(instance, 'privileged'),
            'read_only': self._is_true(instance, 'readOnly'),
        }

        # These _setup_simple_config_fields calls should happen before all
        # other config because they stomp over config fields that other
        # setup methods might append to. Example: the environment field
        self._setup_simple_config_fields(create_config, instance,
                                         CREATE_CONFIG_FIELDS)

        self._setup_simple_config_fields(start_config, instance,
                                         START_CONFIG_FIELDS)

        add_label(create_config, {'io.rancher.container.uuid': instance.uuid})

        self._setup_logging(start_config, instance)

        self._setup_hostname(create_config, instance)

        self._setup_command(create_config, instance)

        self._setup_ports(create_config, instance)

        self._setup_volumes(create_config, instance, start_config, client)

        self._setup_links(start_config, instance)

        self._setup_networking(instance, host, create_config, start_config)

        self._flag_system_container(instance, create_config)

        self._setup_proxy(instance, create_config)

        setup_cattle_config_url(instance, create_config)

        create_config['host_config'] = create_host_config(**start_config)

        container = self._create_container(client, create_config, image_tag,
                                           instance, name, progress)
        container_id = container['Id']

        log.info('Starting docker container [%s] docker id [%s] %s', name,
                 container_id, start_config)

        client.start(container_id)

        self._record_state(client, instance, docker_id=container['Id'])

    def _create_container(self, client, create_config, image_tag, instance,
                          name, progress):
        container = self.get_container(client, instance)
        if container is None:
            log.info('Creating docker container [%s] from config %s', name,
                     create_config)

            try:
                container = client.create_container(image_tag, **create_config)
            except APIError as e:
                if e.message.response.status_code == 404:
                    pull_image(instance.image, progress)
                    container = client.create_container(
                        image_tag, **create_config)
                else:
                    raise
        return container

    def _flag_system_container(self, instance, create_config):
        try:
            if instance.systemContainer:
                add_label(
                    create_config,
                    {'io.rancher.container.system': instance.systemContainer})
        except (KeyError, AttributeError):
            pass

    def _setup_proxy(self, instance, create_config):
        try:
            if instance.systemContainer:
                if 'environment' not in create_config:
                    create_config['environment'] = {}

                for i in ['http_proxy', 'https_proxy', 'NO_PROXY']:
                    try:
                        create_config['environment'][i] = environ[i]
                    except KeyError:
                        pass
        except (KeyError, AttributeError):
            pass

    def _setup_simple_config_fields(self, config, instance, fields):
        for src, dest in fields:
            try:
                config[dest] = instance.data.fields[src]
            except (KeyError, AttributeError):
                pass

    def _setup_volumes(self, create_config, instance, start_config, client):
        try:
            volumes = instance.data.fields['dataVolumes']
            volumes_map = {}
            binds_map = {}
            if volumes is not None and len(volumes) > 0:
                for i in volumes:
                    parts = i.split(':', 3)
                    if len(parts) == 1:
                        volumes_map[parts[0]] = {}
                    else:
                        read_only = len(parts) == 3 and parts[2] == 'ro'
                        bind = {'bind': parts[1], 'ro': read_only}
                        binds_map[parts[0]] = bind
                create_config['volumes'] = volumes_map
                start_config['binds'] = binds_map
        except (KeyError, AttributeError):
            pass

        volmgr.update_managed_volume(instance, create_config, start_config)
        try:
            containers = []
            for vfc in instance['dataVolumesFromContainers']:
                container = self.get_container(client, vfc)
                if container:
                    containers.append(container['Id'])
            if containers:
                start_config['volumes_from'] = containers
        except KeyError:
            pass

    def _get_image_tag(self, instance):
        try:
            return instance.image.data.dockerImage.fullName
        except (KeyError, AttributeError):
            raise Exception('Can not start container with no image')

    def _setup_logging(self, start_config, instance):
        try:
            if start_config.get('log_config', None) is None:
                return

            type = start_config['log_config']['driver']
            del start_config['log_config']['driver']
            start_config['log_config']['type'] = type
        except (KeyError, AttributeError):
            pass

        for i in ['type', 'config']:
            bad = True
            try:
                if start_config['log_config'][i] is not None:
                    bad = False
            except (KeyError, AttributeError):
                pass
            if bad and 'log_config' in start_config:
                del start_config['log_config']

    def _setup_hostname(self, create_config, instance):
        try:
            create_config['hostname'] = instance.hostname
        except (KeyError, AttributeError):
            pass

    def _setup_networking(self, instance, host, create_config, start_config):
        client = self._get_docker_client(host)

        setup_network_mode(instance, self, client, create_config, start_config)
        setup_mac_and_ip(instance, create_config)
        setup_ports(instance, create_config, start_config)
        setup_links(instance, create_config, start_config)
        setup_ipsec(instance, host, create_config, start_config)

    def _is_true(self, instance, key):
        try:
            return instance.data.fields[key] is True
        except (KeyError, AttributeError):
            return False

    def _get_instance_host_map_data(self, obj):
        client = self._get_docker_client(obj.host)
        inspect = None
        existing = self.get_container(client, obj.instance)
        docker_ports = {}
        docker_ip = None

        if existing is not None:
            inspect = client.inspect_container(existing['Id'])
            docker_ip = inspect['NetworkSettings']['IPAddress']
            if existing.get('Ports') is not None:
                for port in existing['Ports']:
                    if 'PublicPort' in port and 'PrivatePort' not in port:
                        # Remove after docker 0.12/1.0 is released
                        private_port = '{0}/{1}'.format(
                            port['PublicPort'], port['Type'])
                        docker_ports[private_port] = None
                    elif 'PublicPort' in port:
                        private_port = '{0}/{1}'.format(
                            port['PrivatePort'], port['Type'])
                        docker_ports[private_port] = str(port['PublicPort'])
                    else:
                        private_port = '{0}/{1}'.format(
                            port['PrivatePort'], port['Type'])
                        docker_ports[private_port] = None

        update = {
            'instance': {
                '+data': {
                    'dockerContainer': existing,
                    'dockerInspect': inspect,
                    '+fields': {
                        'dockerHostIp': DockerConfig.docker_host_ip(),
                        'dockerPorts': docker_ports,
                        'dockerIp': docker_ip
                    }
                }
            }
        }
        if existing is not None:
            update['instance']['externalId'] = existing['Id']

        return update

    def _is_instance_inactive(self, instance, host):
        if is_no_op(instance):
            return True

        c = self._get_docker_client(host)
        container = self.get_container(c, instance)

        return _is_stopped(c, container)

    def _do_instance_deactivate(self, instance, host, progress):
        if is_no_op(instance):
            return

        c = self._get_docker_client(host)
        timeout = 10

        try:
            timeout = int(instance.processData.timeout)
        except (TypeError, KeyError, AttributeError):
            pass

        container = self.get_container(c, instance)

        c.stop(container['Id'], timeout=timeout)

        container = self.get_container(c, instance)
        if not _is_stopped(c, container):
            c.kill(container['Id'])

        container = self.get_container(c, instance)
        if not _is_stopped(c, container):
            raise Exception('Failed to stop container {0}'.format(
                instance.uuid))

    def _do_instance_force_stop(self, instanceForceStop):
        try:
            docker_client().stop(instanceForceStop['id'])
        except APIError as e:
            if e.message.response.status_code != 404:
                raise e

    def _is_instance_removed(self, instance, host):
        client = self._get_docker_client(host)
        container = self.get_container(client, instance)
        return container is None

    def _do_instance_remove(self, instance, host, progress):
        client = self._get_docker_client(host)
        container = self.get_container(client, instance)
        if container is None:
            return

        remove_container(client, container)

    def _do_instance_inspect(self, instanceInspectRequest):
        client = docker_client()
        container = None
        try:
            container_id = instanceInspectRequest.id
            container = self.get_container_by(
                client, lambda x: self._id_filter(container_id, x))
        except (KeyError, AttributeError):
            pass

        if not container:
            try:
                name = '/{0}'.format(instanceInspectRequest.name)
                container = self.get_container_by(
                    client, lambda x: self._name_filter(name, x))
            except (KeyError, AttributeError):
                pass

        if container:
            inspect = client.inspect_container(container)
            return inspect
Beispiel #16
0
class DockerCompute(KindBasedMixin, BaseComputeDriver):
    def __init__(self):
        KindBasedMixin.__init__(self, kind="docker")
        BaseComputeDriver.__init__(self)
        self.host_info = HostInfo(docker_client())
        self.system_images = self.get_agent_images(docker_client())

    def get_agent_images(self, client):
        images = client.images(filters={"label": SYSTEM_LABEL})
        system_images = {}
        for i in images:
            try:
                label_val = i["Labels"][SYSTEM_LABEL]
                for l in i["RepoTags"]:
                    system_images[l] = label_val
            except KeyError:
                pass
        return system_images

    @staticmethod
    def get_container_by(client, func):
        containers = client.containers(all=True, trunc=False)
        containers = filter(func, containers)

        if len(containers) > 0:
            return containers[0]

        return None

    def on_ping(self, ping, pong):
        if not DockerConfig.docker_enabled():
            return

        self._add_resources(ping, pong)
        self._add_instances(ping, pong)

    def _add_instances(self, ping, pong):
        if not utils.ping_include_instances(ping):
            return

        utils.ping_add_resources(pong, {"type": "hostUuid", "uuid": DockerConfig.docker_uuid()})

        containers = []
        running, nonrunning = self._get_all_containers_by_state()

        for key, container in running.iteritems():
            self.add_container("running", container, containers)

        for key, container in nonrunning.iteritems():
            self.add_container("stopped", container, containers)

        utils.ping_add_resources(pong, *containers)
        utils.ping_set_option(pong, "instances", True)

    def add_container(self, state, container, containers):
        try:
            labels = container["Labels"]
        except KeyError:
            labels = []

        container_data = {
            "type": "instance",
            "uuid": self._get_uuid(container),
            "state": state,
            "systemContainer": self._get_sys_container(container),
            "dockerId": container["Id"],
            "image": container["Image"],
            "labels": labels,
            "created": container["Created"],
        }
        containers.append(container_data)

    def _get_all_containers_by_state(self):
        client = docker_client()

        nonrunning_containers = {}
        for c in client.containers(all=True):
            # Blank status only wait to distinguish created from stopped
            if c["Status"] != "" and c["Status"] != "Created":
                nonrunning_containers[c["Id"]] = c

        running_containers = {}
        for c in client.containers(all=False):
            running_containers[c["Id"]] = c
            del nonrunning_containers[c["Id"]]

        return running_containers, nonrunning_containers

    def _get_sys_container(self, container):
        try:
            image = container["Image"]
            if image in self.system_images:
                return self.system_images[image]
        except (TypeError, KeyError):
            pass

        try:
            return container["Labels"]["io.rancher.container.system"]
        except (TypeError, KeyError):
            pass

    def _get_uuid(self, container):
        try:
            uuid = container["Labels"]["io.rancher.container.uuid"]
            if uuid:
                return uuid
        except (TypeError, KeyError):
            pass

        names = container["Names"]
        if not names:
            # No name?? Make one up
            return "no-uuid-%s" % container["Id"]

        if names[0].startswith("/"):
            return names[0][1:]
        else:
            return names[0]

    def _determine_state(self, container):
        status = container["Status"]
        if status == "" or (status is not None and status.lower() == "created"):
            return "created"
        elif "Up " in status:
            return "running"
        elif "Exited " in status:
            return "stopped"
        else:
            # Unknown. Assume running and state should sync up eventually.
            return "running"

    def _add_resources(self, ping, pong):
        if not utils.ping_include_resources(ping):
            return

        stats = None
        if utils.ping_include_stats(ping):
            try:
                stats = self.host_info.collect_data()
            except:
                log.exception("Error geting host info stats")

        physical_host = Config.physical_host()

        compute = {
            "type": "host",
            "kind": "docker",
            "name": Config.hostname(),
            "labels": Config.labels(),
            "physicalHostUuid": physical_host["uuid"],
            "uuid": DockerConfig.docker_uuid(),
            "info": stats,
        }

        pool = {
            "type": "storagePool",
            "kind": "docker",
            "name": compute["name"] + " Storage Pool",
            "hostUuid": compute["uuid"],
            "uuid": compute["uuid"] + "-pool",
        }

        ip = {
            "type": "ipAddress",
            "uuid": DockerConfig.docker_host_ip(),
            "address": DockerConfig.docker_host_ip(),
            "hostUuid": compute["uuid"],
        }

        proxy = Config.host_proxy()
        if proxy is not None:
            compute["apiProxy"] = proxy

        utils.ping_add_resources(pong, physical_host, compute, pool, ip)

    def inspect(self, container):
        return docker_client().inspect_container(container)

    @staticmethod
    def _name_filter(name, container):
        names = container.get("Names")
        if names is None:
            return False
        found = False
        for n in names:
            if n.endswith(name):
                found = True
                break
        return found

    @staticmethod
    def _id_filter(id, container):
        container_id = container.get("Id")
        return id == container_id

    def get_container(self, client, instance):
        if instance is None:
            return None

        name = "/{0}".format(instance.uuid)
        container = self.get_container_by(client, lambda x: self._name_filter(name, x))
        if container:
            return container

        if hasattr(instance, "externalId") and instance.externalId:
            return self.get_container_by(client, lambda x: self._id_filter(instance.externalId, x))

    def _is_instance_active(self, instance, host):
        if is_no_op(instance):
            return True

        client = self._get_docker_client(host)
        container = self.get_container(client, instance)
        return _is_running(client, container)

    @staticmethod
    def _get_docker_client(host):
        cluster_connection = None
        tls_config = None
        try:
            cluster_connection = host["clusterConnection"]
            if cluster_connection.startswith("https"):
                try:
                    account_id = host["accountId"]
                    ca_crt = host["caCrt"]
                    client_crt = host["clientCrt"]
                    client_key = host["clientKey"]

                    client_certs_dir = Config.client_certs_dir()
                    acct_client_cert_dir = path.join(client_certs_dir, str(account_id))
                    if not path.exists(acct_client_cert_dir):
                        log.debug("Creating client cert directory: %s", acct_client_cert_dir)
                        makedirs(acct_client_cert_dir)
                    if ca_crt:
                        log.debug("Writing cert auth")
                        with open(path.join(acct_client_cert_dir, "ca.crt"), "w") as f:
                            f.write(ca_crt)
                    if client_crt:
                        log.debug("Writing client cert")
                        with open(path.join(acct_client_cert_dir, "client.crt"), "w") as f:
                            f.write(client_crt)
                    if client_key:
                        log.debug("Writing client key")
                        with open(path.join(acct_client_cert_dir, "client.key"), "w") as f:
                            f.write(client_key)
                    if ca_crt and client_crt and client_key:
                        tls_config = tls.TLSConfig(
                            client_cert=(
                                path.join(acct_client_cert_dir, "client.crt"),
                                path.join(acct_client_cert_dir, "client.key"),
                            ),
                            verify=path.join(acct_client_cert_dir, "ca.crt"),
                            assert_hostname=False,
                        )
                except (KeyError, AttributeError) as e:
                    raise Exception("Unable to process cert/keys for cluster", cluster_connection, e)
        except (KeyError, AttributeError):
            pass

        return docker_client(base_url_override=cluster_connection, tls_config=tls_config)

    @staticmethod
    def _setup_legacy_command(create_config, instance, command):
        # This can be removed shortly once cattle removes
        # commandArgs
        if command is None or len(command.strip()) == 0:
            return None

        command_args = []
        try:
            command_args = instance.data.fields.commandArgs
        except (KeyError, AttributeError):
            pass

        if command_args is not None and len(command_args) > 0:
            command = [command]
            command.extend(command_args)

        if command is not None:
            create_config["command"] = command

    @staticmethod
    def _setup_command(create_config, instance):
        command = ""
        try:
            command = instance.data.fields.command
        except (KeyError, AttributeError):
            return None

        if isinstance(command, basestring):
            DockerCompute._setup_legacy_command(create_config, instance, command)
        else:
            if command is not None:
                create_config["command"] = command

    @staticmethod
    def _setup_links(start_config, instance):
        links = {}

        if "instanceLinks" not in instance:
            return

        for link in instance.instanceLinks:
            if link.targetInstanceId is not None:
                links[link.targetInstance.uuid] = link.linkName

        start_config["links"] = links

    @staticmethod
    def _setup_ports(create_config, instance):
        ports = []
        try:
            for port in instance.ports:
                ports.append((port.privatePort, port.protocol))
        except (AttributeError, KeyError):
            pass

        if len(ports) > 0:
            create_config["ports"] = ports

    def _record_state(self, client, instance, docker_id=None):
        if docker_id is None:
            container = self.get_container(client, instance)
            if container is not None:
                docker_id = container["Id"]

        if docker_id is None:
            return

        cont_dir = Config.container_state_dir()

        tmp_file_path = path.join(cont_dir, "tmp-%s" % docker_id)
        if path.exists(tmp_file_path):
            remove(tmp_file_path)

        file_path = path.join(cont_dir, docker_id)
        if path.exists(file_path):
            remove(file_path)

        if not path.exists(cont_dir):
            makedirs(cont_dir)

        with open(tmp_file_path, "w") as outfile:
            marshaller = get_type(MARSHALLER)
            data = marshaller.to_string(instance)
            outfile.write(data)

        rename(tmp_file_path, file_path)

    def purge_state(self, client, instance):
        container = self.get_container(client, instance)
        if container is None:
            return

        docker_id = container["Id"]
        cont_dir = Config.container_state_dir()
        files = [path.join(cont_dir, "tmp-%s" % docker_id), path.join(cont_dir, docker_id)]

        for f in files:
            if path.exists(f):
                remove(f)

    def instance_activate(self, req=None, instanceHostMap=None, processData=None, **kw):
        instance, host = BaseComputeDriver.get_instance_host_from_map(self, instanceHostMap)

        progress = Progress(req)
        client = self._get_docker_client(host)
        if instance is not None:
            instance.processData = processData

        with lock(instance):
            if self._is_instance_active(instance, host):
                self._record_state(client, instance)
                return self._reply(req, self._get_response_data(req, instanceHostMap))

            self._do_instance_activate(instance, host, progress)

            data = self._get_response_data(req, instanceHostMap)

            return self._reply(req, data)

    def _do_instance_activate(self, instance, host, progress):
        if is_no_op(instance):
            return

        client = self._get_docker_client(host)

        image_tag = self._get_image_tag(instance)

        name = instance.uuid

        create_config = {"name": name, "detach": True}

        start_config = {
            "publish_all_ports": False,
            "privileged": self._is_true(instance, "privileged"),
            "read_only": self._is_true(instance, "readOnly"),
        }

        # These _setup_simple_config_fields calls should happen before all
        # other config because they stomp over config fields that other
        # setup methods might append to. Example: the environment field
        self._setup_simple_config_fields(create_config, instance, CREATE_CONFIG_FIELDS)

        self._setup_simple_config_fields(start_config, instance, START_CONFIG_FIELDS)

        add_label(create_config, {"io.rancher.container.uuid": instance.uuid})

        self._setup_logging(start_config, instance)

        self._setup_hostname(create_config, instance)

        self._setup_command(create_config, instance)

        self._setup_ports(create_config, instance)

        self._setup_volumes(create_config, instance, start_config, client)

        self._setup_links(start_config, instance)

        self._setup_networking(instance, host, create_config, start_config)

        self._flag_system_container(instance, create_config)

        self._setup_proxy(instance, create_config)

        setup_cattle_config_url(instance, create_config)

        create_config["host_config"] = create_host_config(**start_config)

        container = self._create_container(client, create_config, image_tag, instance, name, progress)
        container_id = container["Id"]

        log.info("Starting docker container [%s] docker id [%s] %s", name, container_id, start_config)

        client.start(container_id)

        self._record_state(client, instance, docker_id=container["Id"])

    def _create_container(self, client, create_config, image_tag, instance, name, progress):
        container = self.get_container(client, instance)
        if container is None:
            log.info("Creating docker container [%s] from config %s", name, create_config)

            try:
                container = client.create_container(image_tag, **create_config)
            except APIError as e:
                if e.message.response.status_code == 404:
                    pull_image(instance.image, progress)
                    container = client.create_container(image_tag, **create_config)
                else:
                    raise
        return container

    def _flag_system_container(self, instance, create_config):
        try:
            if instance.systemContainer:
                add_label(create_config, {"io.rancher.container.system": instance.systemContainer})
        except (KeyError, AttributeError):
            pass

    def _setup_proxy(self, instance, create_config):
        try:
            if instance.systemContainer:
                if "environment" not in create_config:
                    create_config["environment"] = {}

                for i in ["http_proxy", "https_proxy", "NO_PROXY"]:
                    try:
                        create_config["environment"][i] = environ[i]
                    except KeyError:
                        pass
        except (KeyError, AttributeError):
            pass

    def _setup_simple_config_fields(self, config, instance, fields):
        for src, dest in fields:
            try:
                config[dest] = instance.data.fields[src]
            except (KeyError, AttributeError):
                pass

    def _setup_volumes(self, create_config, instance, start_config, client):
        try:
            volumes = instance.data.fields["dataVolumes"]
            volumes_map = {}
            binds_map = {}
            if volumes is not None and len(volumes) > 0:
                for i in volumes:
                    parts = i.split(":", 3)
                    if len(parts) == 1:
                        volumes_map[parts[0]] = {}
                    else:
                        read_only = len(parts) == 3 and parts[2] == "ro"
                        bind = {"bind": parts[1], "ro": read_only}
                        binds_map[parts[0]] = bind
                create_config["volumes"] = volumes_map
                start_config["binds"] = binds_map
        except (KeyError, AttributeError):
            pass

        volmgr.update_managed_volume(instance, create_config, start_config)
        try:
            containers = []
            for vfc in instance["dataVolumesFromContainers"]:
                container = self.get_container(client, vfc)
                if container:
                    containers.append(container["Id"])
            if containers:
                start_config["volumes_from"] = containers
        except KeyError:
            pass

    def _get_image_tag(self, instance):
        try:
            return instance.image.data.dockerImage.fullName
        except (KeyError, AttributeError):
            raise Exception("Can not start container with no image")

    def _setup_logging(self, start_config, instance):
        try:
            if start_config.get("log_config", None) is None:
                return

            type = start_config["log_config"]["driver"]
            del start_config["log_config"]["driver"]
            start_config["log_config"]["type"] = type
        except (KeyError, AttributeError):
            pass

        for i in ["type", "config"]:
            bad = True
            try:
                if start_config["log_config"][i] is not None:
                    bad = False
            except (KeyError, AttributeError):
                pass
            if bad and "log_config" in start_config:
                del start_config["log_config"]

    def _setup_hostname(self, create_config, instance):
        try:
            create_config["hostname"] = instance.hostname
        except (KeyError, AttributeError):
            pass

    def _setup_networking(self, instance, host, create_config, start_config):
        client = self._get_docker_client(host)

        setup_network_mode(instance, self, client, create_config, start_config)
        setup_mac_and_ip(instance, create_config)
        setup_ports(instance, create_config, start_config)
        setup_links(instance, create_config, start_config)
        setup_ipsec(instance, host, create_config, start_config)

    def _is_true(self, instance, key):
        try:
            return instance.data.fields[key] is True
        except (KeyError, AttributeError):
            return False

    def _get_instance_host_map_data(self, obj):
        client = self._get_docker_client(obj.host)
        inspect = None
        existing = self.get_container(client, obj.instance)
        docker_ports = {}
        docker_ip = None

        if existing is not None:
            inspect = client.inspect_container(existing["Id"])
            docker_ip = inspect["NetworkSettings"]["IPAddress"]
            if existing.get("Ports") is not None:
                for port in existing["Ports"]:
                    if "PublicPort" in port and "PrivatePort" not in port:
                        # Remove after docker 0.12/1.0 is released
                        private_port = "{0}/{1}".format(port["PublicPort"], port["Type"])
                        docker_ports[private_port] = None
                    elif "PublicPort" in port:
                        private_port = "{0}/{1}".format(port["PrivatePort"], port["Type"])
                        docker_ports[private_port] = str(port["PublicPort"])
                    else:
                        private_port = "{0}/{1}".format(port["PrivatePort"], port["Type"])
                        docker_ports[private_port] = None

        update = {
            "instance": {
                "+data": {
                    "dockerContainer": existing,
                    "dockerInspect": inspect,
                    "+fields": {
                        "dockerHostIp": DockerConfig.docker_host_ip(),
                        "dockerPorts": docker_ports,
                        "dockerIp": docker_ip,
                    },
                }
            }
        }
        if existing is not None:
            update["instance"]["externalId"] = existing["Id"]

        return update

    def _is_instance_inactive(self, instance, host):
        if is_no_op(instance):
            return True

        c = self._get_docker_client(host)
        container = self.get_container(c, instance)

        return _is_stopped(c, container)

    def _do_instance_deactivate(self, instance, host, progress):
        if is_no_op(instance):
            return

        c = self._get_docker_client(host)
        timeout = 10

        try:
            timeout = int(instance.processData.timeout)
        except (TypeError, KeyError, AttributeError):
            pass

        container = self.get_container(c, instance)

        c.stop(container["Id"], timeout=timeout)

        container = self.get_container(c, instance)
        if not _is_stopped(c, container):
            c.kill(container["Id"])

        container = self.get_container(c, instance)
        if not _is_stopped(c, container):
            raise Exception("Failed to stop container {0}".format(instance.uuid))

    def _do_instance_force_stop(self, instanceForceStop):
        try:
            docker_client().stop(instanceForceStop["id"])
        except APIError as e:
            if e.message.response.status_code != 404:
                raise e

    def _is_instance_removed(self, instance, host):
        client = self._get_docker_client(host)
        container = self.get_container(client, instance)
        return container is None

    def _do_instance_remove(self, instance, host, progress):
        client = self._get_docker_client(host)
        container = self.get_container(client, instance)
        if container is None:
            return

        remove_container(client, container)

    def _do_instance_inspect(self, instanceInspectRequest):
        client = docker_client()
        container = None
        try:
            container_id = instanceInspectRequest.id
            container = self.get_container_by(client, lambda x: self._id_filter(container_id, x))
        except (KeyError, AttributeError):
            pass

        if not container:
            try:
                name = "/{0}".format(instanceInspectRequest.name)
                container = self.get_container_by(client, lambda x: self._name_filter(name, x))
            except (KeyError, AttributeError):
                pass

        if container:
            inspect = client.inspect_container(container)
            return inspect