"Fossies" - the Fresh Open Source Software Archive

Member "zun-4.0.2/zun/container/docker/driver.py" (1 Feb 2021, 58251 Bytes) of package /linux/misc/openstack/zun-4.0.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "driver.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 4.0.1_vs_4.0.2.

    1 # Licensed under the Apache License, Version 2.0 (the "License");
    2 # you may not use this file except in compliance with the License.
    3 # You may obtain a copy of the License at
    4 #
    5 #    http://www.apache.org/licenses/LICENSE-2.0
    6 #
    7 # Unless required by applicable law or agreed to in writing, software
    8 # distributed under the License is distributed on an "AS IS" BASIS,
    9 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
   10 # implied.
   11 # See the License for the specific language governing permissions and
   12 # limitations under the License.
   13 
   14 import datetime
   15 import errno
   16 import eventlet
   17 import functools
   18 import types
   19 
   20 from docker import errors
   21 from neutronclient.common import exceptions as n_exc
   22 from oslo_log import log as logging
   23 from oslo_utils import timeutils
   24 from oslo_utils import uuidutils
   25 import psutil
   26 import six
   27 import tenacity
   28 
   29 from zun.common import consts
   30 from zun.common import exception
   31 from zun.common.i18n import _
   32 from zun.common import utils
   33 from zun.common.utils import check_container_id
   34 from zun.compute import container_actions
   35 import zun.conf
   36 from zun.container.docker import host
   37 from zun.container.docker import utils as docker_utils
   38 from zun.container import driver
   39 from zun.image import driver as img_driver
   40 from zun.network import network as zun_network
   41 from zun import objects
   42 from zun.volume import driver as vol_driver
   43 
   44 
   45 CONF = zun.conf.CONF
   46 LOG = logging.getLogger(__name__)
   47 ATTACH_FLAG = "/attach/ws?logs=0&stream=1&stdin=1&stdout=1&stderr=1"
   48 
   49 
   50 def is_not_found(e):
   51     return '404' in str(e)
   52 
   53 
   54 def is_not_connected(e):
   55     # Test the following exception:
   56     #
   57     #   500 Server Error: Internal Server Error ("container XXX is not
   58     #   connected to the network XXX")
   59     #
   60     # Note(hongbin): Docker should response a 4xx instead of 500. This looks
   61     # like a bug from docker side: https://github.com/moby/moby/issues/35888
   62     return ' is not connected to the network ' in str(e)
   63 
   64 
   65 def is_conflict(e):
   66     conflict_infos = ['not running', 'not paused', 'paused']
   67     for info in conflict_infos:
   68         if info in str(e):
   69             return True
   70     return False
   71 
   72 
   73 def handle_not_found(e, context, container, do_not_raise=False):
   74     if container.status == consts.DELETING:
   75         return
   76 
   77     if container.auto_remove:
   78         container.status = consts.DELETED
   79     else:
   80         container.status = consts.ERROR
   81         container.status_reason = six.text_type(e)
   82     container.save(context)
   83     if do_not_raise:
   84         return
   85 
   86     raise exception.Conflict(message=_(
   87         "Cannot act on container in '%s' state") % container.status)
   88 
   89 
   90 def wrap_docker_error(function):
   91 
   92     @functools.wraps(function)
   93     def decorated_function(*args, **kwargs):
   94         context = args[1]
   95         container = args[2]
   96         try:
   97             return function(*args, **kwargs)
   98         except exception.DockerError as e:
   99             if is_not_found(e):
  100                 handle_not_found(e, context, container)
  101             if is_conflict(e):
  102                 raise exception.Conflict(_("%s") % str(e))
  103             raise
  104 
  105     return decorated_function
  106 
  107 
  108 class DockerDriver(driver.ContainerDriver):
  109     """Implementation of container drivers for Docker."""
  110 
  111     # TODO(hongbin): define a list of capabilities of this driver.
  112     capabilities = {}
  113 
  114     def __init__(self):
  115         super(DockerDriver, self).__init__()
  116         self._host = host.Host()
  117         self._get_host_storage_info()
  118         self.image_drivers = {}
  119         for driver_name in CONF.image_driver_list:
  120             driver = img_driver.load_image_driver(driver_name)
  121             self.image_drivers[driver_name] = driver
  122         self.volume_drivers = {}
  123         for driver_name in CONF.volume.driver_list:
  124             driver = vol_driver.driver(driver_name)
  125             self.volume_drivers[driver_name] = driver
  126 
  127     def _get_host_storage_info(self):
  128         host_info = self.get_host_info()
  129         self.docker_root_dir = host_info['docker_root_dir']
  130         storage_info = self._host.get_storage_info()
  131         self.base_device_size = storage_info['default_base_size']
  132         self.support_disk_quota = self._host.check_supported_disk_quota(
  133             host_info)
  134 
  135     def load_image(self, image_path=None):
  136         with docker_utils.docker_client() as docker:
  137             if image_path:
  138                 with open(image_path, 'rb') as fd:
  139                     LOG.debug('Loading local image %s into docker', image_path)
  140                     docker.load_image(fd)
  141 
  142     def inspect_image(self, image):
  143         with docker_utils.docker_client() as docker:
  144             LOG.debug('Inspecting image %s', image)
  145             return docker.inspect_image(image)
  146 
  147     def get_image(self, name):
  148         LOG.debug('Obtaining image %s', name)
  149         with docker_utils.docker_client() as docker:
  150             return docker.get_image(name)
  151 
  152     def delete_image(self, context, img_id, image_driver=None):
  153         image = self.inspect_image(img_id)['RepoTags'][0]
  154         if image_driver:
  155             image_driver_list = [image_driver.lower()]
  156         else:
  157             image_driver_list = CONF.image_driver_list
  158         for driver_name in image_driver_list:
  159             try:
  160                 image_driver = img_driver.load_image_driver(driver_name)
  161                 if driver_name == 'glance':
  162                     image_driver.delete_image_tar(context, image)
  163                 elif driver_name == 'docker':
  164                     image_driver.delete_image(context, img_id)
  165             except exception.ZunException:
  166                 LOG.exception('Unknown exception occurred while deleting '
  167                               'image %s', img_id)
  168 
  169     def delete_committed_image(self, context, img_id, image_driver):
  170         try:
  171             image_driver.delete_committed_image(context, img_id)
  172         except Exception as e:
  173             LOG.exception('Unknown exception occurred while '
  174                           'deleting image %s: %s',
  175                           img_id,
  176                           six.text_type(e))
  177             raise exception.ZunException(six.text_type(e))
  178 
  179     def images(self, repo, quiet=False):
  180         with docker_utils.docker_client() as docker:
  181             return docker.images(repo, quiet)
  182 
  183     def pull_image(self, context, repo, tag, image_pull_policy='always',
  184                    driver_name=None, registry=None):
  185         if driver_name is None:
  186             driver_name = CONF.default_image_driver
  187 
  188         try:
  189             image_driver = self.image_drivers[driver_name]
  190             image, image_loaded = image_driver.pull_image(
  191                 context, repo, tag, image_pull_policy, registry)
  192             if image:
  193                 image['driver'] = driver_name.split('.')[0]
  194         except exception.ZunException:
  195             raise
  196         except Exception as e:
  197             LOG.exception('Unknown exception occurred while loading '
  198                           'image: %s', six.text_type(e))
  199             raise exception.ZunException(six.text_type(e))
  200 
  201         return image, image_loaded
  202 
  203     def search_image(self, context, repo, tag, driver_name, exact_match):
  204         if driver_name is None:
  205             driver_name = CONF.default_image_driver
  206 
  207         try:
  208             image_driver = self.image_drivers[driver_name]
  209             return image_driver.search_image(context, repo, tag,
  210                                              exact_match)
  211         except exception.ZunException:
  212             raise
  213         except Exception as e:
  214             LOG.exception('Unknown exception occurred while searching '
  215                           'for image: %s', six.text_type(e))
  216             raise exception.ZunException(six.text_type(e))
  217 
  218     def create_image(self, context, image_name, image_driver):
  219         try:
  220             img = image_driver.create_image(context, image_name)
  221         except Exception as e:
  222             LOG.exception('Unknown exception occurred while creating '
  223                           'image: %s', six.text_type(e))
  224             raise exception.ZunException(six.text_type(e))
  225         return img
  226 
  227     def upload_image_data(self, context, image, image_tag, image_data,
  228                           image_driver):
  229         try:
  230             image_driver.update_image(context,
  231                                       image.id,
  232                                       tag=image_tag)
  233             # Image data has to match the image format.
  234             # contain format defaults to 'docker';
  235             # disk format defaults to 'qcow2'.
  236             img = image_driver.upload_image_data(context,
  237                                                  image.id,
  238                                                  image_data)
  239         except Exception as e:
  240             LOG.exception('Unknown exception occurred while uploading '
  241                           'image: %s', six.text_type(e))
  242             raise exception.ZunException(six.text_type(e))
  243         return img
  244 
  245     def read_tar_image(self, image):
  246         with docker_utils.docker_client() as docker:
  247             LOG.debug('Reading local tar image %s ', image['path'])
  248             try:
  249                 docker.read_tar_image(image)
  250             except Exception:
  251                 LOG.warning("Unable to read image data from tarfile")
  252 
  253     def create(self, context, container, image, requested_networks,
  254                requested_volumes):
  255         with docker_utils.docker_client() as docker:
  256             network_api = zun_network.api(context=context, docker_api=docker)
  257             name = container.name
  258             LOG.debug('Creating container with image %(image)s name %(name)s',
  259                       {'image': image['image'], 'name': name})
  260             self._provision_network(context, network_api, requested_networks)
  261             volmaps = requested_volumes.get(container.uuid, [])
  262             binds = self._get_binds(context, volmaps)
  263             kwargs = {
  264                 'name': self.get_container_name(container),
  265                 'command': container.command,
  266                 'environment': container.environment,
  267                 'working_dir': container.workdir,
  268                 'labels': container.labels,
  269                 'tty': container.tty,
  270                 'stdin_open': container.interactive,
  271                 'hostname': container.hostname,
  272             }
  273 
  274             if not self._is_runtime_supported():
  275                 if container.runtime:
  276                     raise exception.ZunException(_(
  277                         'Specifying runtime in Docker API is not supported'))
  278                 runtime = None
  279             else:
  280                 runtime = container.runtime or CONF.container_runtime
  281 
  282             host_config = {}
  283             host_config['privileged'] = container.privileged
  284             host_config['runtime'] = runtime
  285             host_config['binds'] = binds
  286             kwargs['volumes'] = [b['bind'] for b in binds.values()]
  287             self._process_exposed_ports(network_api.neutron_api, container,
  288                                         kwargs)
  289             self._process_networking_config(
  290                 context, container, requested_networks, host_config,
  291                 kwargs, docker)
  292             if container.auto_remove:
  293                 host_config['auto_remove'] = container.auto_remove
  294             if self._should_limit_memory(container):
  295                 host_config['mem_limit'] = str(container.memory) + 'M'
  296             if self._should_limit_cpu(container):
  297                 host_config['cpu_shares'] = int(1024 * container.cpu)
  298             if container.restart_policy:
  299                 count = int(container.restart_policy['MaximumRetryCount'])
  300                 name = container.restart_policy['Name']
  301                 host_config['restart_policy'] = {'Name': name,
  302                                                  'MaximumRetryCount': count}
  303 
  304             if container.disk:
  305                 disk_size = str(container.disk) + 'G'
  306                 host_config['storage_opt'] = {'size': disk_size}
  307             if container.cpu_policy == 'dedicated':
  308                 host_config['cpuset_cpus'] = container.cpuset.cpuset_cpus
  309                 host_config['cpuset_mems'] = str(container.cpuset.cpuset_mems)
  310             # The time unit in docker of heath checking is us, and the unit
  311             # of interval and timeout is seconds.
  312             if container.healthcheck:
  313                 healthcheck = {}
  314                 healthcheck['test'] = container.healthcheck.get('test', '')
  315                 interval = container.healthcheck.get('interval', 0)
  316                 healthcheck['interval'] = interval * 10 ** 9
  317                 healthcheck['retries'] = int(container.healthcheck.
  318                                              get('retries', 0))
  319                 timeout = container.healthcheck.get('timeout', 0)
  320                 healthcheck['timeout'] = timeout * 10 ** 9
  321                 kwargs['healthcheck'] = healthcheck
  322 
  323             kwargs['host_config'] = docker.create_host_config(**host_config)
  324             if image['tag']:
  325                 image_repo = image['repo'] + ":" + image['tag']
  326             else:
  327                 image_repo = image['repo']
  328             response = docker.create_container(image_repo, **kwargs)
  329             container.container_id = response['Id']
  330 
  331             addresses = self._setup_network_for_container(
  332                 context, container, requested_networks, network_api)
  333             container.addresses = addresses
  334 
  335             response = docker.inspect_container(container.container_id)
  336             self._populate_container(container, response, force=True)
  337             container.save(context)
  338             return container
  339 
  340     def _should_limit_memory(self, container):
  341         return (container.memory is not None and
  342                 not isinstance(container, objects.Capsule))
  343 
  344     def _should_limit_cpu(self, container):
  345         return (container.cpu is not None and
  346                 not isinstance(container, objects.Capsule))
  347 
  348     def _is_runtime_supported(self):
  349         return float(CONF.docker.docker_remote_api_version) >= 1.26
  350 
  351     def node_support_disk_quota(self):
  352         return self.support_disk_quota
  353 
  354     def get_host_default_base_size(self):
  355         return self.base_device_size
  356 
  357     def _process_exposed_ports(self, neutron_api, container, kwargs):
  358         exposed_ports = {}
  359         if isinstance(container, objects.Container):
  360             exposed_ports.update(container.exposed_ports or {})
  361         if isinstance(container, objects.Capsule):
  362             for container in (container.init_containers +
  363                               container.containers):
  364                 exposed_ports.update(container.exposed_ports or {})
  365 
  366         if not exposed_ports:
  367             return
  368 
  369         # process security group
  370         secgroup_name = self._get_secgorup_name(container.uuid)
  371         secgroup_id = neutron_api.create_security_group({'security_group': {
  372             "name": secgroup_name}})['security_group']['id']
  373         neutron_api.expose_ports(secgroup_id, exposed_ports)
  374         container.security_groups = [secgroup_id]
  375         # process kwargs on creating the docker container
  376         ports = []
  377         for port in exposed_ports:
  378             port, proto = port.split('/')
  379             ports.append((port, proto))
  380         kwargs['ports'] = ports
  381 
  382     def _process_networking_config(self, context, container,
  383                                    requested_networks, host_config,
  384                                    container_kwargs, docker):
  385         network_api = zun_network.api(context=context, docker_api=docker)
  386         # Process the first requested network at create time. The rest
  387         # will be processed after create.
  388         requested_network = requested_networks.pop()
  389         docker_net_name = self._get_docker_network_name(
  390             context, requested_network['network'])
  391         security_group_ids = utils.get_security_group_ids(
  392             context, container.security_groups)
  393         addresses, port = network_api.create_or_update_port(
  394             container, docker_net_name, requested_network, security_group_ids,
  395             set_binding_host=True)
  396         container.addresses = {requested_network['network']: addresses}
  397 
  398         ipv4_address = None
  399         ipv6_address = None
  400         for address in addresses:
  401             if address['version'] == 4:
  402                 ipv4_address = address['addr']
  403             if address['version'] == 6:
  404                 ipv6_address = address['addr']
  405 
  406         endpoint_config = docker.create_endpoint_config(
  407             ipv4_address=ipv4_address, ipv6_address=ipv6_address)
  408         network_config = docker.create_networking_config({
  409             docker_net_name: endpoint_config})
  410 
  411         host_config['network_mode'] = docker_net_name
  412         container_kwargs['networking_config'] = network_config
  413         container_kwargs['mac_address'] = port['mac_address']
  414 
  415     def _provision_network(self, context, network_api, requested_networks):
  416         for rq_network in requested_networks:
  417             self._get_or_create_docker_network(
  418                 context, network_api, rq_network['network'])
  419 
  420     def _get_secgorup_name(self, container_uuid):
  421         return consts.NAME_PREFIX + container_uuid
  422 
  423     def _get_binds(self, context, requested_volumes):
  424         binds = {}
  425         for volume in requested_volumes:
  426             volume_driver = self._get_volume_driver(volume)
  427             source, destination = volume_driver.bind_mount(context, volume)
  428             binds[source] = {'bind': destination}
  429         return binds
  430 
  431     def _setup_network_for_container(self, context, container,
  432                                      requested_networks, network_api):
  433         security_group_ids = utils.get_security_group_ids(
  434             context, container.security_groups)
  435         addresses = {}
  436         if container.addresses:
  437             addresses = container.addresses
  438         for network in requested_networks:
  439             if network['network'] in addresses:
  440                 # This network is already setup so skip it
  441                 continue
  442 
  443             docker_net_name = self._get_docker_network_name(
  444                 context, network['network'])
  445             addrs = network_api.connect_container_to_network(
  446                 container, docker_net_name, network,
  447                 security_groups=security_group_ids)
  448             addresses[network['network']] = addrs
  449 
  450         return addresses
  451 
  452     def delete(self, context, container, force):
  453         with docker_utils.docker_client() as docker:
  454             try:
  455                 network_api = zun_network.api(context=context,
  456                                               docker_api=docker)
  457                 self._cleanup_network_for_container(container, network_api)
  458                 self._cleanup_exposed_ports(network_api.neutron_api,
  459                                             container)
  460                 if container.container_id:
  461                     docker.remove_container(container.container_id,
  462                                             force=force)
  463             except errors.APIError as api_error:
  464                 if is_not_found(api_error):
  465                     return
  466                 if is_not_connected(api_error):
  467                     return
  468                 raise
  469 
  470     @wrap_docker_error
  471     def _cleanup_network_for_container(self, container, network_api):
  472         if not container.addresses:
  473             return
  474         for neutron_net in container.addresses:
  475             docker_net = neutron_net
  476             network_api.disconnect_container_from_network(
  477                 container, docker_net, neutron_network_id=neutron_net)
  478 
  479     def _cleanup_exposed_ports(self, neutron_api, container):
  480         exposed_ports = {}
  481         if isinstance(container, objects.Container):
  482             exposed_ports.update(container.exposed_ports or {})
  483         if isinstance(container, objects.Capsule):
  484             for container in (container.init_containers +
  485                               container.containers):
  486                 exposed_ports.update(container.exposed_ports or {})
  487         if not exposed_ports:
  488             return
  489 
  490         try:
  491             neutron_api.delete_security_group(container.security_groups[0])
  492         except n_exc.NeutronClientException:
  493             LOG.exception("Failed to delete security group")
  494 
  495     def check_container_exist(self, container):
  496         with docker_utils.docker_client() as docker:
  497             docker_containers = [c['Id']
  498                                  for c in docker.list_containers()]
  499             if container.container_id not in docker_containers:
  500                 return False
  501         return True
  502 
  503     def list(self, context):
  504         non_existent_containers = []
  505         with docker_utils.docker_client() as docker:
  506             docker_containers = docker.list_containers()
  507             id_to_container_map = {c['Id']: c
  508                                    for c in docker_containers}
  509             uuids = self._get_container_uuids(docker_containers)
  510 
  511         local_containers = self._get_local_containers(context, uuids)
  512         for container in local_containers:
  513             if container.status in (consts.CREATING, consts.DELETING,
  514                                     consts.DELETED):
  515                 # Skip populating db record since the container is in a
  516                 # unstable state.
  517                 continue
  518 
  519             container_id = container.container_id
  520             docker_container = id_to_container_map.get(container_id)
  521             if not container_id or not docker_container:
  522                 non_existent_containers.append(container)
  523                 continue
  524 
  525             self._populate_container(container, docker_container)
  526 
  527         return local_containers, non_existent_containers
  528 
  529     def heal_with_rebuilding_container(self, context, container, manager):
  530         if not container.container_id:
  531             return
  532 
  533         rebuild_status = utils.VALID_STATES['rebuild']
  534         try:
  535             if (container.auto_heal and
  536                     container.status in rebuild_status):
  537                 context.project_id = container.project_id
  538                 objects.ContainerAction.action_start(
  539                     context, container.uuid, container_actions.REBUILD,
  540                     want_result=False)
  541                 manager.container_rebuild(context, container)
  542             else:
  543                 LOG.warning("Container %s was recorded in DB but "
  544                             "missing in docker", container.uuid)
  545                 container.status = consts.ERROR
  546                 msg = "No such container: %s in docker" % \
  547                       (container.container_id)
  548                 container.status_reason = six.text_type(msg)
  549                 container.save(context)
  550         except Exception as e:
  551             LOG.warning("heal container with rebuilding failed, "
  552                         "err code: %s", e)
  553 
  554     def _get_container_uuids(self, containers):
  555         # The name of Docker container is of the form '/zun-<uuid>'
  556         name_prefix = '/' + consts.NAME_PREFIX
  557         uuids = [c['Names'][0].replace(name_prefix, '', 1)
  558                  for c in containers]
  559         return [u for u in uuids if uuidutils.is_uuid_like(u)]
  560 
  561     def _get_local_containers(self, context, uuids):
  562         host_containers = objects.Container.list_by_host(context, CONF.host)
  563         uuids = list(set(uuids) | set([c.uuid for c in host_containers]))
  564         containers = objects.Container.list(context,
  565                                             filters={'uuid': uuids})
  566         return containers
  567 
  568     def update_containers_states(self, context, containers, manager):
  569         local_containers, non_existent_containers = self.list(context)
  570         if not local_containers:
  571             return
  572 
  573         id_to_local_container_map = {container.container_id: container
  574                                      for container in local_containers
  575                                      if container.container_id}
  576         id_to_container_map = {container.container_id: container
  577                                for container in containers
  578                                if container.container_id}
  579 
  580         for cid in (six.viewkeys(id_to_container_map) &
  581                     six.viewkeys(id_to_local_container_map)):
  582             container = id_to_container_map[cid]
  583             # sync status
  584             local_container = id_to_local_container_map[cid]
  585             if container.status != local_container.status:
  586                 old_status = container.status
  587                 container.status = local_container.status
  588                 container.save(context)
  589                 LOG.info('Status of container %s changed from %s to %s',
  590                          container.uuid, old_status, container.status)
  591             # sync host
  592             # Note(kiennt): Current host.
  593             cur_host = CONF.host
  594             if container.host != cur_host:
  595                 old_host = container.host
  596                 container.host = cur_host
  597                 container.save(context)
  598                 LOG.info('Host of container %s changed from %s to %s',
  599                          container.uuid, old_host, container.host)
  600         for container in non_existent_containers:
  601             if container.host == CONF.host:
  602                 if container.auto_remove:
  603                     container.status = consts.DELETED
  604                     container.save(context)
  605                 else:
  606                     self.heal_with_rebuilding_container(context, container,
  607                                                         manager)
  608 
  609     def show(self, context, container):
  610         with docker_utils.docker_client() as docker:
  611             if container.container_id is None:
  612                 return container
  613 
  614             response = None
  615             try:
  616                 response = docker.inspect_container(container.container_id)
  617             except errors.APIError as api_error:
  618                 if is_not_found(api_error):
  619                     handle_not_found(api_error, context, container,
  620                                      do_not_raise=True)
  621                     return container
  622                 raise
  623 
  624             self._populate_container(container, response)
  625             return container
  626 
  627     def format_status_detail(self, status_time):
  628         try:
  629             st = datetime.datetime.strptime((status_time[:19]),
  630                                             '%Y-%m-%dT%H:%M:%S')
  631         except ValueError as e:
  632             LOG.exception("Error on parse {} : {}".format(status_time, e))
  633             return
  634 
  635         if st == datetime.datetime(1, 1, 1):
  636             # return empty string if the time is January 1, year 1, 00:00:00
  637             return ""
  638 
  639         delta = timeutils.utcnow() - st
  640         time_dict = {}
  641         time_dict['days'] = delta.days
  642         time_dict['hours'] = delta.seconds // 3600
  643         time_dict['minutes'] = (delta.seconds % 3600) // 60
  644         time_dict['seconds'] = delta.seconds
  645         if time_dict['days']:
  646             return '{} days'.format(time_dict['days'])
  647         if time_dict['hours']:
  648             return '{} hours'.format(time_dict['hours'])
  649         if time_dict['minutes']:
  650             return '{} mins'.format(time_dict['minutes'])
  651         if time_dict['seconds']:
  652             return '{} seconds'.format(time_dict['seconds'])
  653         return
  654 
  655     def _populate_container(self, container, response, force=False):
  656         state = response.get('State')
  657         self._populate_container_state(container, state, force)
  658 
  659         config = response.get('Config')
  660         if config:
  661             self._populate_hostname_and_ports(container, config)
  662             self._populate_command(container, config)
  663 
  664         hostconfig = response.get('HostConfig')
  665         if hostconfig:
  666             container.runtime = hostconfig.get('Runtime')
  667 
  668     def _populate_container_state(self, container, state, force):
  669         if container.task_state and not force:
  670             # NOTE(hongbin): we don't want to populate container state
  671             # if another thread is performing task on this container.
  672             # In this case, task_state will be assigned (which means there is a
  673             # task performing on this container) and 'force' will be set to
  674             # False. For example, if this method is called by create,
  675             # 'force' will be set to True to force refreshing the state.
  676             # If this method is called by list or show,
  677             # 'force' will be set to False, in which case we skip
  678             # refreshing the state if there is a task on this container.
  679             return
  680 
  681         if not state:
  682             LOG.warning('Receive unexpected state from docker: %s', state)
  683             container.status = consts.UNKNOWN
  684             container.status_reason = _("container state is missing")
  685             container.status_detail = None
  686         elif type(state) is dict:
  687             status_detail = ''
  688             if state.get('Error'):
  689                 if state.get('Status') in ('exited', 'removing'):
  690                     container.status = consts.STOPPED
  691                 else:
  692                     status = state.get('Status').capitalize()
  693                     if status in consts.CONTAINER_STATUSES:
  694                         container.status = status
  695                 status_detail = self.format_status_detail(
  696                     state.get('FinishedAt'))
  697                 container.status_detail = "Exited({}) {} ago " \
  698                     "(error)".format(state.get('ExitCode'), status_detail)
  699             elif state.get('Paused'):
  700                 container.status = consts.PAUSED
  701                 status_detail = self.format_status_detail(
  702                     state.get('StartedAt'))
  703                 container.status_detail = "Up {} (paused)".format(
  704                     status_detail)
  705             elif state.get('Restarting'):
  706                 container.status = consts.RESTARTING
  707                 container.status_detail = "Restarting"
  708             elif state.get('Running'):
  709                 container.status = consts.RUNNING
  710                 status_detail = self.format_status_detail(
  711                     state.get('StartedAt'))
  712                 container.status_detail = "Up {}".format(
  713                     status_detail)
  714             elif state.get('Dead'):
  715                 container.status = consts.DEAD
  716                 container.status_detail = "Dead"
  717             else:
  718                 started_at = self.format_status_detail(state.get('StartedAt'))
  719                 finished_at = self.format_status_detail(
  720                     state.get('FinishedAt'))
  721                 if started_at == "" and container.status == consts.CREATING:
  722                     container.status = consts.CREATED
  723                     container.status_detail = "Created"
  724                 elif (started_at == "" and
  725                         container.status in (consts.CREATED, consts.RESTARTING,
  726                                              consts.ERROR, consts.REBUILDING)):
  727                     pass
  728                 elif started_at != "" and finished_at == "":
  729                     LOG.warning('Receive unexpected state from docker: %s',
  730                                 state)
  731                     container.status = consts.UNKNOWN
  732                     container.status_reason = _("unexpected container state")
  733                     container.status_detail = ""
  734                 elif started_at != "" and finished_at != "":
  735                     container.status = consts.STOPPED
  736                     container.status_detail = "Exited({}) {} ago ".format(
  737                         state.get('ExitCode'), finished_at)
  738             if status_detail is None:
  739                 container.status_detail = None
  740         else:
  741             state = state.lower()
  742             if state == 'created' and container.status == consts.CREATING:
  743                 container.status = consts.CREATED
  744             elif (state == 'created' and
  745                     container.status in (consts.CREATED, consts.RESTARTING,
  746                                          consts.ERROR, consts.REBUILDING)):
  747                 pass
  748             elif state == 'paused':
  749                 container.status = consts.PAUSED
  750             elif state == 'running':
  751                 container.status = consts.RUNNING
  752             elif state == 'dead':
  753                 container.status = consts.DEAD
  754             elif state == 'restarting':
  755                 container.status = consts.RESTARTING
  756             elif state in ('exited', 'removing'):
  757                 container.status = consts.STOPPED
  758             else:
  759                 LOG.warning('Receive unexpected state from docker: %s', state)
  760                 container.status = consts.UNKNOWN
  761                 container.status_reason = _("unexpected container state")
  762             container.status_detail = None
  763 
  764     def _populate_command(self, container, config):
  765         command_list = config.get('Cmd')
  766         container.command = command_list
  767 
  768     def _populate_hostname_and_ports(self, container, config):
  769         # populate hostname only when container.hostname wasn't set
  770         if container.hostname is None:
  771             container.hostname = config.get('Hostname')
  772         # populate ports
  773         ports = []
  774         exposed_ports = config.get('ExposedPorts')
  775         if exposed_ports:
  776             for key in exposed_ports:
  777                 port = key.split('/')[0]
  778                 ports.append(int(port))
  779         container.ports = ports
  780 
  781     @check_container_id
  782     @wrap_docker_error
  783     def reboot(self, context, container, timeout):
  784         with docker_utils.docker_client() as docker:
  785             if timeout:
  786                 docker.restart(container.container_id,
  787                                timeout=int(timeout))
  788             else:
  789                 docker.restart(container.container_id)
  790             container.status = consts.RUNNING
  791             container.status_reason = None
  792             return container
  793 
  794     @check_container_id
  795     @wrap_docker_error
  796     def stop(self, context, container, timeout):
  797         with docker_utils.docker_client() as docker:
  798             if timeout:
  799                 docker.stop(container.container_id,
  800                             timeout=int(timeout))
  801             else:
  802                 docker.stop(container.container_id)
  803             container.status = consts.STOPPED
  804             container.status_reason = None
  805             return container
  806 
  807     @check_container_id
  808     @wrap_docker_error
  809     def start(self, context, container):
  810         with docker_utils.docker_client() as docker:
  811             docker.start(container.container_id)
  812             container.status = consts.RUNNING
  813             container.status_reason = None
  814             return container
  815 
  816     @check_container_id
  817     @wrap_docker_error
  818     def pause(self, context, container):
  819         with docker_utils.docker_client() as docker:
  820             docker.pause(container.container_id)
  821             container.status = consts.PAUSED
  822             container.status_reason = None
  823             return container
  824 
  825     @check_container_id
  826     @wrap_docker_error
  827     def unpause(self, context, container):
  828         with docker_utils.docker_client() as docker:
  829             docker.unpause(container.container_id)
  830             container.status = consts.RUNNING
  831             container.status_reason = None
  832             return container
  833 
  834     @check_container_id
  835     @wrap_docker_error
  836     def show_logs(self, context, container, stdout=True, stderr=True,
  837                   timestamps=False, tail='all', since=None):
  838         with docker_utils.docker_client() as docker:
  839             try:
  840                 tail = int(tail)
  841             except ValueError:
  842                 tail = 'all'
  843 
  844             if since is None or since == 'None':
  845                 return docker.logs(container.container_id, stdout, stderr,
  846                                    False, timestamps, tail, None)
  847             else:
  848                 try:
  849                     since = int(since)
  850                 except ValueError:
  851                     try:
  852                         since = datetime.datetime.strptime(
  853                             since, '%Y-%m-%d %H:%M:%S,%f')
  854                     except Exception:
  855                         raise
  856                 return docker.logs(container.container_id, stdout, stderr,
  857                                    False, timestamps, tail, since)
  858 
  859     @check_container_id
  860     @wrap_docker_error
  861     def execute_create(self, context, container, command, interactive=False):
  862         stdin = True if interactive else False
  863         tty = True if interactive else False
  864         with docker_utils.docker_client() as docker:
  865             create_res = docker.exec_create(
  866                 container.container_id, command, stdin=stdin, tty=tty)
  867             exec_id = create_res['Id']
  868             return exec_id
  869 
  870     def execute_run(self, exec_id, command):
  871         with docker_utils.docker_client() as docker:
  872             try:
  873                 with eventlet.Timeout(CONF.docker.execute_timeout):
  874                     output = docker.exec_start(exec_id, False, False, False)
  875             except eventlet.Timeout:
  876                 raise exception.Conflict(_(
  877                     "Timeout on executing command: %s") % command)
  878             inspect_res = docker.exec_inspect(exec_id)
  879             return output, inspect_res['ExitCode']
  880 
  881     def execute_resize(self, exec_id, height, width):
  882         height = int(height)
  883         width = int(width)
  884         with docker_utils.docker_client() as docker:
  885             try:
  886                 docker.exec_resize(exec_id, height=height, width=width)
  887             except errors.APIError as api_error:
  888                 if is_not_found(api_error):
  889                     raise exception.Invalid(_(
  890                         "no such exec instance: %s") % str(api_error))
  891                 raise
  892 
  893     @check_container_id
  894     @wrap_docker_error
  895     def kill(self, context, container, signal=None):
  896         with docker_utils.docker_client() as docker:
  897             if signal is None or signal == 'None':
  898                 docker.kill(container.container_id)
  899             else:
  900                 docker.kill(container.container_id, signal)
  901             container.status = consts.STOPPED
  902             container.status_reason = None
  903             return container
  904 
  905     @check_container_id
  906     @wrap_docker_error
  907     def update(self, context, container):
  908         patch = container.obj_get_changes()
  909 
  910         args = {}
  911         memory = patch.get('memory')
  912         if memory is not None:
  913             args['mem_limit'] = str(memory) + 'M'
  914             args['memswap_limit'] = CONF.default_memory_swap
  915         cpu = patch.get('cpu')
  916         if cpu is not None:
  917             args['cpu_shares'] = int(1024 * cpu)
  918 
  919         with docker_utils.docker_client() as docker:
  920             return docker.update_container(container.container_id, **args)
  921 
  922     @check_container_id
  923     def get_websocket_url(self, context, container):
  924         version = CONF.docker.docker_remote_api_version
  925         remote_api_host = CONF.docker.docker_remote_api_host
  926         remote_api_port = CONF.docker.docker_remote_api_port
  927         url = "ws://" + remote_api_host + ":" + remote_api_port + \
  928               "/v" + version + "/containers/" + container.container_id \
  929               + ATTACH_FLAG
  930         return url
  931 
  932     @check_container_id
  933     @wrap_docker_error
  934     def resize(self, context, container, height, width):
  935         with docker_utils.docker_client() as docker:
  936             height = int(height)
  937             width = int(width)
  938             docker.resize(container.container_id, height, width)
  939             return container
  940 
  941     @check_container_id
  942     @wrap_docker_error
  943     def top(self, context, container, ps_args=None):
  944         with docker_utils.docker_client() as docker:
  945             if ps_args is None or ps_args == 'None':
  946                 return docker.top(container.container_id)
  947             else:
  948                 return docker.top(container.container_id, ps_args)
  949 
  950     @check_container_id
  951     @wrap_docker_error
  952     def get_archive(self, context, container, path):
  953         with docker_utils.docker_client() as docker:
  954             try:
  955                 stream, stat = docker.get_archive(
  956                     container.container_id, path)
  957                 if isinstance(stream, types.GeneratorType):
  958                     filedata = six.b("").join(stream)
  959                 else:
  960                     filedata = stream.read()
  961                 return filedata, stat
  962             except errors.APIError as api_error:
  963                 if is_not_found(api_error):
  964                     raise exception.Invalid(_("%s") % str(api_error))
  965                 raise
  966 
  967     @check_container_id
  968     @wrap_docker_error
  969     def put_archive(self, context, container, path, data):
  970         with docker_utils.docker_client() as docker:
  971             try:
  972                 docker.put_archive(container.container_id, path, data)
  973             except errors.APIError as api_error:
  974                 if is_not_found(api_error):
  975                     raise exception.Invalid(_("%s") % str(api_error))
  976                 raise
  977 
  978     @check_container_id
  979     @wrap_docker_error
  980     def stats(self, context, container):
  981         with docker_utils.docker_client() as docker:
  982             res = docker.stats(container.container_id, decode=False,
  983                                stream=False)
  984 
  985             cpu_usage = res['cpu_stats']['cpu_usage']['total_usage']
  986             system_cpu_usage = res['cpu_stats']['system_cpu_usage']
  987             cpu_percent = float(cpu_usage) / float(system_cpu_usage) * 100
  988             mem_usage = res['memory_stats']['usage'] / 1024 / 1024
  989             mem_limit = res['memory_stats']['limit'] / 1024 / 1024
  990             mem_percent = float(mem_usage) / float(mem_limit) * 100
  991 
  992             blk_stats = res['blkio_stats']['io_service_bytes_recursive']
  993             io_read = 0
  994             io_write = 0
  995             for item in blk_stats:
  996                 if 'Read' == item['op']:
  997                     io_read = io_read + item['value']
  998                 if 'Write' == item['op']:
  999                     io_write = io_write + item['value']
 1000 
 1001             net_stats = res['networks']
 1002             net_rxb = 0
 1003             net_txb = 0
 1004             for k, v in net_stats.items():
 1005                 net_rxb = net_rxb + v['rx_bytes']
 1006                 net_txb = net_txb + v['tx_bytes']
 1007 
 1008             stats = {"CONTAINER": container.name,
 1009                      "CPU %": cpu_percent,
 1010                      "MEM USAGE(MiB)": mem_usage,
 1011                      "MEM LIMIT(MiB)": mem_limit,
 1012                      "MEM %": mem_percent,
 1013                      "BLOCK I/O(B)": str(io_read) + "/" + str(io_write),
 1014                      "NET I/O(B)": str(net_rxb) + "/" + str(net_txb)}
 1015             return stats
 1016 
 1017     @check_container_id
 1018     @wrap_docker_error
 1019     def commit(self, context, container, repository=None, tag=None):
 1020         with docker_utils.docker_client() as docker:
 1021             repository = str(repository)
 1022             if tag is None or tag == "None":
 1023                 return docker.commit(container.container_id, repository)
 1024             else:
 1025                 tag = str(tag)
 1026                 return docker.commit(container.container_id, repository, tag)
 1027 
 1028     def _encode_utf8(self, value):
 1029         if six.PY2 and not isinstance(value, six.text_type):
 1030             value = six.text_type(value)
 1031         return value.encode('utf-8')
 1032 
 1033     def _get_volume_driver(self, volume_mapping):
 1034         driver_name = volume_mapping.volume_provider
 1035         driver = self.volume_drivers.get(driver_name)
 1036         if not driver:
 1037             msg = _("The volume provider '%s' is not supported") % driver_name
 1038             raise exception.ZunException(msg)
 1039 
 1040         return driver
 1041 
 1042     def attach_volume(self, context, volume_mapping):
 1043         volume_driver = self._get_volume_driver(volume_mapping)
 1044         volume_driver.attach(context, volume_mapping)
 1045 
 1046     def detach_volume(self, context, volume_mapping):
 1047         volume_driver = self._get_volume_driver(volume_mapping)
 1048         volume_driver.detach(context, volume_mapping)
 1049 
 1050     def delete_volume(self, context, volume_mapping):
 1051         volume_driver = self._get_volume_driver(volume_mapping)
 1052         volume_driver.delete(context, volume_mapping)
 1053 
 1054     def is_volume_available(self, context, volume_mapping):
 1055         volume_driver = self._get_volume_driver(volume_mapping)
 1056         return volume_driver.is_volume_available(context, volume_mapping)
 1057 
 1058     def is_volume_deleted(self, context, volume_mapping):
 1059         volume_driver = self._get_volume_driver(volume_mapping)
 1060         return volume_driver.is_volume_deleted(context, volume_mapping)
 1061 
 1062     def _get_or_create_docker_network(self, context, network_api,
 1063                                       neutron_net_id):
 1064         docker_net_name = self._get_docker_network_name(context,
 1065                                                         neutron_net_id)
 1066         docker_networks = network_api.list_networks(names=[docker_net_name])
 1067         if not docker_networks:
 1068             network_api.create_network(neutron_net_id=neutron_net_id,
 1069                                        name=docker_net_name)
 1070 
 1071     def _get_docker_network_name(self, context, neutron_net_id):
 1072         # Note(kiseok7): neutron_net_id is a unique ID in neutron networks and
 1073         # docker networks.
 1074         # so it will not be duplicated across projects.
 1075         return neutron_net_id
 1076 
 1077     def get_container_name(self, container):
 1078         return consts.NAME_PREFIX + container.uuid
 1079 
 1080     def get_host_info(self):
 1081         with docker_utils.docker_client() as docker:
 1082             info = docker.info()
 1083             total = info['Containers']
 1084             paused = info['ContainersPaused']
 1085             running = info['ContainersRunning']
 1086             stopped = info['ContainersStopped']
 1087             cpus = info['NCPU']
 1088             architecture = info['Architecture']
 1089             os_type = info['OSType']
 1090             os = info['OperatingSystem']
 1091             kernel_version = info['KernelVersion']
 1092             labels = {}
 1093             slabels = info['Labels']
 1094             if slabels:
 1095                 for l in slabels:
 1096                     kv = l.split("=")
 1097                     label = {kv[0]: kv[1]}
 1098                     labels.update(label)
 1099             runtimes = []
 1100             if 'Runtimes' in info:
 1101                 for key in info['Runtimes']:
 1102                     runtimes.append(key)
 1103             else:
 1104                 runtimes = ['runc']
 1105             docker_root_dir = info['DockerRootDir']
 1106             enable_cpu_pinning = CONF.compute.enable_cpu_pinning
 1107 
 1108             return {'total_containers': total,
 1109                     'running_containers': running,
 1110                     'paused_containers': paused,
 1111                     'stopped_containers': stopped,
 1112                     'cpus': cpus,
 1113                     'architecture': architecture,
 1114                     'os_type': os_type,
 1115                     'os': os,
 1116                     'kernel_version': kernel_version,
 1117                     'labels': labels,
 1118                     'runtimes': runtimes,
 1119                     'docker_root_dir': docker_root_dir,
 1120                     'enable_cpu_pinning': enable_cpu_pinning}
 1121 
 1122     def get_total_disk_for_container(self):
 1123         try:
 1124             disk_usage = psutil.disk_usage(self.docker_root_dir)
 1125         except OSError as e:
 1126             if e.errno != errno.ENOENT:
 1127                 raise
 1128             LOG.warning('Docker data root doesnot exist.')
 1129             # give another try with system root
 1130             disk_usage = psutil.disk_usage('/')
 1131         total_disk = disk_usage.total / 1024 ** 3
 1132         # TODO(hongbin): deprecate reserve_disk_for_image in flavor of
 1133         # reserved_host_disk_mb
 1134         return (int(total_disk),
 1135                 int(total_disk * CONF.compute.reserve_disk_for_image))
 1136 
 1137     def add_security_group(self, context, container, security_group):
 1138 
 1139         with docker_utils.docker_client() as docker:
 1140             network_api = zun_network.api(context=context,
 1141                                           docker_api=docker)
 1142             network_api.add_security_groups_to_ports(container,
 1143                                                      [security_group])
 1144 
 1145     def remove_security_group(self, context, container, security_group):
 1146 
 1147         with docker_utils.docker_client() as docker:
 1148             network_api = zun_network.api(context=context,
 1149                                           docker_api=docker)
 1150             network_api.remove_security_groups_from_ports(container,
 1151                                                           [security_group])
 1152 
 1153     def get_available_nodes(self):
 1154         return [self._host.get_hostname()]
 1155 
 1156     @wrap_docker_error
 1157     def network_detach(self, context, container, network):
 1158         with docker_utils.docker_client() as docker:
 1159             network_api = zun_network.api(context,
 1160                                           docker_api=docker)
 1161             docker_net = self._get_docker_network_name(context, network)
 1162             network_api.disconnect_container_from_network(container,
 1163                                                           docker_net, network)
 1164 
 1165             # Only clear network info related to this network
 1166             # Cannot del container.address directly which will not update
 1167             # changed fields of the container objects as the del operate on
 1168             # the addresses object, only base.getter will called.
 1169             update = container.addresses
 1170             del update[network]
 1171             container.addresses = update
 1172             container.save(context)
 1173 
 1174     def network_attach(self, context, container, requested_network):
 1175         with docker_utils.docker_client() as docker:
 1176             security_group_ids = None
 1177             if container.security_groups:
 1178                 security_group_ids = utils.get_security_group_ids(
 1179                     context, container.security_groups)
 1180             network_api = zun_network.api(context,
 1181                                           docker_api=docker)
 1182             network = requested_network['network']
 1183             if network in container.addresses:
 1184                 raise exception.ZunException('Container %(container)s has '
 1185                                              'already connected to the '
 1186                                              'network %(network)s.'
 1187                                              % {'container': container.uuid,
 1188                                                 'network': network})
 1189             self._get_or_create_docker_network(context, network_api, network)
 1190             docker_net_name = self._get_docker_network_name(context, network)
 1191             addrs = network_api.connect_container_to_network(
 1192                 container, docker_net_name, requested_network,
 1193                 security_groups=security_group_ids)
 1194             if addrs is None:
 1195                 raise exception.ZunException(_(
 1196                     'Unexpected missing of addresses'))
 1197             update = {}
 1198             update[network] = addrs
 1199             addresses = container.addresses
 1200             addresses.update(update)
 1201             container.addresses = addresses
 1202             container.save(context)
 1203 
 1204     def create_network(self, context, neutron_net_id):
 1205         with docker_utils.docker_client() as docker:
 1206             network_api = zun_network.api(context,
 1207                                           docker_api=docker)
 1208             docker_net_name = self._get_docker_network_name(
 1209                 context, neutron_net_id)
 1210             return network_api.create_network(
 1211                 neutron_net_id=neutron_net_id,
 1212                 name=docker_net_name)
 1213 
 1214     def delete_network(self, context, network):
 1215         with docker_utils.docker_client() as docker:
 1216             network_api = zun_network.api(context,
 1217                                           docker_api=docker)
 1218             network_api.remove_network(network)
 1219 
 1220     def create_capsule(self, context, capsule, image, requested_networks,
 1221                        requested_volumes):
 1222         capsule = self.create(context, capsule, image, requested_networks,
 1223                               requested_volumes)
 1224         self.start(context, capsule)
 1225         for container in capsule.init_containers:
 1226             self._create_container_in_capsule(context, capsule, container,
 1227                                               requested_networks,
 1228                                               requested_volumes)
 1229             self._wait_for_init_container(context, container)
 1230             container.save(context)
 1231         for container in capsule.containers:
 1232             self._create_container_in_capsule(context, capsule, container,
 1233                                               requested_networks,
 1234                                               requested_volumes)
 1235         return capsule
 1236 
 1237     def _create_container_in_capsule(self, context, capsule, container,
 1238                                      requested_networks, requested_volumes):
 1239         # pull image
 1240         image_driver_name = container.image_driver
 1241         repo, tag = utils.parse_image_name(container.image, image_driver_name)
 1242         image_pull_policy = utils.get_image_pull_policy(
 1243             container.image_pull_policy, tag)
 1244         image, image_loaded = self.pull_image(
 1245             context, repo, tag, image_pull_policy, image_driver_name)
 1246         image['repo'], image['tag'] = repo, tag
 1247         if not image_loaded:
 1248             self.load_image(image['path'])
 1249         if image_driver_name == 'glance':
 1250             self.read_tar_image(image)
 1251         if image['tag'] != tag:
 1252             LOG.warning("The input tag is different from the tag in tar")
 1253 
 1254         # create container
 1255         with docker_utils.docker_client() as docker:
 1256             name = container.name
 1257             LOG.debug('Creating container with image %(image)s name %(name)s',
 1258                       {'image': image['image'], 'name': name})
 1259             volmaps = requested_volumes.get(container.uuid, [])
 1260             binds = self._get_binds(context, volmaps)
 1261             kwargs = {
 1262                 'name': self.get_container_name(container),
 1263                 'command': container.command,
 1264                 'environment': container.environment,
 1265                 'working_dir': container.workdir,
 1266                 'labels': container.labels,
 1267                 'tty': container.tty,
 1268                 'stdin_open': container.interactive,
 1269             }
 1270 
 1271             host_config = {}
 1272             host_config['privileged'] = container.privileged
 1273             host_config['binds'] = binds
 1274             kwargs['volumes'] = [b['bind'] for b in binds.values()]
 1275             host_config['network_mode'] = 'container:%s' % capsule.container_id
 1276             # TODO(hongbin): Uncomment this after docker-py add support for
 1277             # container mode for pid namespace.
 1278             # host_config['pid_mode'] = 'container:%s' % capsule.container_id
 1279             host_config['ipc_mode'] = 'container:%s' % capsule.container_id
 1280             if container.auto_remove:
 1281                 host_config['auto_remove'] = container.auto_remove
 1282             if container.memory is not None:
 1283                 host_config['mem_limit'] = str(container.memory) + 'M'
 1284             if container.cpu is not None:
 1285                 host_config['cpu_shares'] = int(1024 * container.cpu)
 1286             if container.restart_policy:
 1287                 count = int(container.restart_policy['MaximumRetryCount'])
 1288                 name = container.restart_policy['Name']
 1289                 host_config['restart_policy'] = {'Name': name,
 1290                                                  'MaximumRetryCount': count}
 1291 
 1292             if container.disk:
 1293                 disk_size = str(container.disk) + 'G'
 1294                 host_config['storage_opt'] = {'size': disk_size}
 1295             # The time unit in docker of heath checking is us, and the unit
 1296             # of interval and timeout is seconds.
 1297             if container.healthcheck:
 1298                 healthcheck = {}
 1299                 healthcheck['test'] = container.healthcheck.get('test', '')
 1300                 interval = container.healthcheck.get('interval', 0)
 1301                 healthcheck['interval'] = interval * 10 ** 9
 1302                 healthcheck['retries'] = int(container.healthcheck.
 1303                                              get('retries', 0))
 1304                 timeout = container.healthcheck.get('timeout', 0)
 1305                 healthcheck['timeout'] = timeout * 10 ** 9
 1306                 kwargs['healthcheck'] = healthcheck
 1307 
 1308             kwargs['host_config'] = docker.create_host_config(**host_config)
 1309             if image['tag']:
 1310                 image_repo = image['repo'] + ":" + image['tag']
 1311             else:
 1312                 image_repo = image['repo']
 1313             response = docker.create_container(image_repo, **kwargs)
 1314             container.container_id = response['Id']
 1315             docker.start(container.container_id)
 1316 
 1317             response = docker.inspect_container(container.container_id)
 1318             self._populate_container(container, response, force=True)
 1319             container.save(context)
 1320 
 1321     def _wait_for_init_container(self, context, container, timeout=3600):
 1322         def retry_if_result_is_false(result):
 1323             return result is False
 1324 
 1325         def check_init_container_stopped():
 1326             status = self.show(context, container).status
 1327             if status == consts.STOPPED:
 1328                 return True
 1329             elif status == consts.RUNNING:
 1330                 return False
 1331             else:
 1332                 raise exception.ZunException(
 1333                     _("Container has unexpected status: %s") % status)
 1334 
 1335         r = tenacity.Retrying(
 1336             stop=tenacity.stop_after_delay(timeout),
 1337             wait=tenacity.wait_exponential(),
 1338             retry=tenacity.retry_if_result(retry_if_result_is_false))
 1339         r.call(check_init_container_stopped)
 1340 
 1341     def delete_capsule(self, context, capsule, force):
 1342         for container in capsule.containers:
 1343             self._delete_container_in_capsule(context, capsule, container,
 1344                                               force)
 1345         self.delete(context, capsule, force)
 1346 
 1347     def _delete_container_in_capsule(self, context, capsule, container, force):
 1348         if not container.container_id:
 1349             return
 1350 
 1351         with docker_utils.docker_client() as docker:
 1352             try:
 1353                 docker.stop(container.container_id)
 1354                 docker.remove_container(container.container_id,
 1355                                         force=force)
 1356             except errors.APIError as api_error:
 1357                 if is_not_found(api_error):
 1358                     return
 1359                 if is_not_connected(api_error):
 1360                     return
 1361                 raise