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
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
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())
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
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()
def __init__(self): KindBasedMixin.__init__(self, kind='docker') BaseComputeDriver.__init__(self) self.host_info = HostInfo()
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))
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
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