Implement Zun compute
* Implement compute manager * Introduce zun/container folder for containers (equivalent to nova/virt in Nova) * Have a docker driver in zun/container/docker * Remove conductor since it is not needed anymore Change-Id: I627f7ba1c40584178e1526947088c847554a82af
This commit is contained in:
		@@ -1,48 +0,0 @@
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
#    you may not use this file except in compliance with the License.
 | 
			
		||||
#    You may obtain a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#        http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
#    See the License for the specific language governing permissions and
 | 
			
		||||
#    limitations under the License.
 | 
			
		||||
 | 
			
		||||
"""Starter script for the Zun conductor service."""
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
from oslo_config import cfg
 | 
			
		||||
from oslo_log import log as logging
 | 
			
		||||
from oslo_service import service
 | 
			
		||||
 | 
			
		||||
from zun.common.i18n import _LI
 | 
			
		||||
from zun.common import rpc_service
 | 
			
		||||
from zun.common import service as zun_service
 | 
			
		||||
from zun.common import short_id
 | 
			
		||||
from zun.conductor.handlers import default as default_handler
 | 
			
		||||
 | 
			
		||||
LOG = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    zun_service.prepare_service(sys.argv)
 | 
			
		||||
 | 
			
		||||
    LOG.info(_LI('Starting server in PID %s'), os.getpid())
 | 
			
		||||
    cfg.CONF.log_opt_values(LOG, logging.DEBUG)
 | 
			
		||||
 | 
			
		||||
    cfg.CONF.import_opt('topic', 'zun.conductor.config', group='conductor')
 | 
			
		||||
 | 
			
		||||
    conductor_id = short_id.generate_id()
 | 
			
		||||
    endpoints = [
 | 
			
		||||
        default_handler.Handler(),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    server = rpc_service.Service.create(cfg.CONF.conductor.topic,
 | 
			
		||||
                                        conductor_id, endpoints,
 | 
			
		||||
                                        binary='zun-conductor')
 | 
			
		||||
    launcher = service.launch(cfg.CONF, server)
 | 
			
		||||
    launcher.wait()
 | 
			
		||||
@@ -337,5 +337,9 @@ class ConfigInvalid(ZunException):
 | 
			
		||||
    message = _("Invalid configuration file. %(error_msg)s")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ContainerNotFound(HTTPNotFound):
 | 
			
		||||
    message = _("Container %(container)s could not be found.")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ContainerAlreadyExists(ResourceExists):
 | 
			
		||||
    message = _("A container with UUID %(uuid)s already exists.")
 | 
			
		||||
 
 | 
			
		||||
@@ -36,29 +36,29 @@ class API(rpc_service.API):
 | 
			
		||||
    def container_create(self, context, container):
 | 
			
		||||
        return self._call('container_create', container=container)
 | 
			
		||||
 | 
			
		||||
    def container_delete(self, context, container_uuid):
 | 
			
		||||
        return self._call('container_delete', container_uuid=container_uuid)
 | 
			
		||||
    def container_delete(self, context, container):
 | 
			
		||||
        return self._call('container_delete', container=container)
 | 
			
		||||
 | 
			
		||||
    def container_show(self, context, container_uuid):
 | 
			
		||||
        return self._call('container_show', container_uuid=container_uuid)
 | 
			
		||||
    def container_show(self, context, container):
 | 
			
		||||
        return self._call('container_show', container=container)
 | 
			
		||||
 | 
			
		||||
    def container_reboot(self, context, container_uuid):
 | 
			
		||||
        return self._call('container_reboot', container_uuid=container_uuid)
 | 
			
		||||
    def container_reboot(self, context, container):
 | 
			
		||||
        return self._call('container_reboot', container=container)
 | 
			
		||||
 | 
			
		||||
    def container_stop(self, context, container_uuid):
 | 
			
		||||
        return self._call('container_stop', container_uuid=container_uuid)
 | 
			
		||||
    def container_stop(self, context, container):
 | 
			
		||||
        return self._call('container_stop', container=container)
 | 
			
		||||
 | 
			
		||||
    def container_start(self, context, container_uuid):
 | 
			
		||||
        return self._call('container_start', container_uuid=container_uuid)
 | 
			
		||||
    def container_start(self, context, container):
 | 
			
		||||
        return self._call('container_start', container=container)
 | 
			
		||||
 | 
			
		||||
    def container_pause(self, context, container_uuid):
 | 
			
		||||
        return self._call('container_pause', container_uuid=container_uuid)
 | 
			
		||||
    def container_pause(self, context, container):
 | 
			
		||||
        return self._call('container_pause', container=container)
 | 
			
		||||
 | 
			
		||||
    def container_unpause(self, context, container_uuid):
 | 
			
		||||
        return self._call('container_unpause', container_uuid=container_uuid)
 | 
			
		||||
    def container_unpause(self, context, container):
 | 
			
		||||
        return self._call('container_unpause', container=container)
 | 
			
		||||
 | 
			
		||||
    def container_logs(self, context, container_uuid):
 | 
			
		||||
        return self._call('container_logs', container_uuid=container_uuid)
 | 
			
		||||
    def container_logs(self, context, container):
 | 
			
		||||
        return self._call('container_logs', container=container)
 | 
			
		||||
 | 
			
		||||
    def container_exec(self, context, container_uuid, command):
 | 
			
		||||
        return self._call('container_exec', container_uuid=container_uuid)
 | 
			
		||||
    def container_exec(self, context, container, command):
 | 
			
		||||
        return self._call('container_exec', container=container)
 | 
			
		||||
 
 | 
			
		||||
@@ -12,39 +12,132 @@
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
from oslo_log import log as logging
 | 
			
		||||
 | 
			
		||||
from zun.common.i18n import _LE
 | 
			
		||||
from zun.container import driver
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
LOG = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Manager(object):
 | 
			
		||||
    '''Manages the running containers.'''
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
    def __init__(self, container_driver=None):
 | 
			
		||||
        super(Manager, self).__init__()
 | 
			
		||||
        self.driver = driver.load_container_driver(container_driver)
 | 
			
		||||
 | 
			
		||||
    def container_create(self, context, container):
 | 
			
		||||
        pass
 | 
			
		||||
        LOG.debug('Creating container...', context=context,
 | 
			
		||||
                  container=container)
 | 
			
		||||
        try:
 | 
			
		||||
            container = self.driver.create(container)
 | 
			
		||||
            return container
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            LOG.exception(_LE("Unexpected exception: %s,"), str(e))
 | 
			
		||||
            raise
 | 
			
		||||
 | 
			
		||||
    def container_delete(self, context, container_uuid):
 | 
			
		||||
        pass
 | 
			
		||||
    def container_delete(self, context, container):
 | 
			
		||||
        LOG.debug('Deleting container...', context=context,
 | 
			
		||||
                  container=container.uuid)
 | 
			
		||||
        try:
 | 
			
		||||
            self.driver.delete(container)
 | 
			
		||||
            container.destroy()
 | 
			
		||||
            return container
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            LOG.exception(_LE("Unexpected exception: %s,"), str(e))
 | 
			
		||||
            raise
 | 
			
		||||
 | 
			
		||||
    def container_show(self, context, container_uuid):
 | 
			
		||||
        pass
 | 
			
		||||
    def container_list(self, context):
 | 
			
		||||
        LOG.debug('Showing container...', context=context)
 | 
			
		||||
        try:
 | 
			
		||||
            return self.driver.list()
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            LOG.exception(_LE("Unexpected exception: %s,"), str(e))
 | 
			
		||||
            raise
 | 
			
		||||
 | 
			
		||||
    def container_reboot(self, context, container_uuid):
 | 
			
		||||
        pass
 | 
			
		||||
    def container_show(self, context, container):
 | 
			
		||||
        LOG.debug('Showing container...', context=context,
 | 
			
		||||
                  container=container.uuid)
 | 
			
		||||
        try:
 | 
			
		||||
            container = self.driver.show(container)
 | 
			
		||||
            container.save()
 | 
			
		||||
            return container
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            LOG.exception(_LE("Unexpected exception: %s,"), str(e))
 | 
			
		||||
            raise
 | 
			
		||||
 | 
			
		||||
    def container_stop(self, context, container_uuid):
 | 
			
		||||
        pass
 | 
			
		||||
    def container_reboot(self, context, container):
 | 
			
		||||
        LOG.debug('Rebooting container...', context=context,
 | 
			
		||||
                  container=container)
 | 
			
		||||
        try:
 | 
			
		||||
            container = self.driver.reboot(container)
 | 
			
		||||
            container.save()
 | 
			
		||||
            return container
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            LOG.exception(_LE("Unexpected exception: %s,"), str(e))
 | 
			
		||||
            raise
 | 
			
		||||
 | 
			
		||||
    def container_start(self, context, container_uuid):
 | 
			
		||||
        pass
 | 
			
		||||
    def container_stop(self, context, container):
 | 
			
		||||
        LOG.debug('Stopping container...', context=context,
 | 
			
		||||
                  container=container)
 | 
			
		||||
        try:
 | 
			
		||||
            container = self.driver.stop(container)
 | 
			
		||||
            container.save()
 | 
			
		||||
            return container
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            LOG.exception(_LE("Unexpected exception: %s,"), str(e))
 | 
			
		||||
            raise
 | 
			
		||||
 | 
			
		||||
    def container_pause(self, context, container_uuid):
 | 
			
		||||
        pass
 | 
			
		||||
    def container_start(self, context, container):
 | 
			
		||||
        LOG.debug('Starting container...', context=context,
 | 
			
		||||
                  container=container.uuid)
 | 
			
		||||
        try:
 | 
			
		||||
            container = self.driver.start(container)
 | 
			
		||||
            container.save()
 | 
			
		||||
            return container
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            LOG.exception(_LE("Unexpected exception: %s,"), str(e))
 | 
			
		||||
            raise
 | 
			
		||||
 | 
			
		||||
    def container_unpause(self, context, container_uuid):
 | 
			
		||||
        pass
 | 
			
		||||
    def container_pause(self, context, container):
 | 
			
		||||
        LOG.debug('Pausing container...', context=context,
 | 
			
		||||
                  container=container)
 | 
			
		||||
        try:
 | 
			
		||||
            container = self.driver.pause(container)
 | 
			
		||||
            container.save()
 | 
			
		||||
            return container
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            LOG.exception(_LE("Unexpected exception: %s,"), str(e))
 | 
			
		||||
            raise
 | 
			
		||||
 | 
			
		||||
    def container_logs(self, context, container_uuid):
 | 
			
		||||
        pass
 | 
			
		||||
    def container_unpause(self, context, container):
 | 
			
		||||
        LOG.debug('Unpausing container...', context=context,
 | 
			
		||||
                  container=container)
 | 
			
		||||
        try:
 | 
			
		||||
            container = self.driver.unpause(container)
 | 
			
		||||
            container.save()
 | 
			
		||||
            return container
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            LOG.exception(_LE("Unexpected exception: %s,"), str(e))
 | 
			
		||||
            raise
 | 
			
		||||
 | 
			
		||||
    def container_exec(self, context, container_uuid, command):
 | 
			
		||||
        pass
 | 
			
		||||
    def container_logs(self, context, container):
 | 
			
		||||
        LOG.debug('Showing container logs...', context=context,
 | 
			
		||||
                  container=container)
 | 
			
		||||
        try:
 | 
			
		||||
            return self.driver.show_logs(container)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            LOG.exception(_LE("Unexpected exception: %s,"), str(e))
 | 
			
		||||
            raise
 | 
			
		||||
 | 
			
		||||
    def container_exec(self, context, container, command):
 | 
			
		||||
        # TODO(hongbin): support exec command interactively
 | 
			
		||||
        LOG.debug('Executing command in container...', context=context,
 | 
			
		||||
                  container=container)
 | 
			
		||||
        try:
 | 
			
		||||
            return self.driver.execute(container)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            LOG.exception(_LE("Unexpected exception: %s,"), str(e))
 | 
			
		||||
            raise
 | 
			
		||||
 
 | 
			
		||||
@@ -1,33 +0,0 @@
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
#    you may not use this file except in compliance with the License.
 | 
			
		||||
#    You may obtain a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#        http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
#    See the License for the specific language governing permissions and
 | 
			
		||||
#    limitations under the License.
 | 
			
		||||
 | 
			
		||||
"""API for interfacing with Zun Backend."""
 | 
			
		||||
from oslo_config import cfg
 | 
			
		||||
 | 
			
		||||
from zun.common import rpc_service
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# The Backend API class serves as a AMQP client for communicating
 | 
			
		||||
# on a topic exchange specific to the conductors.  This allows the ReST
 | 
			
		||||
# API to trigger operations on the conductors
 | 
			
		||||
 | 
			
		||||
class API(rpc_service.API):
 | 
			
		||||
    def __init__(self, transport=None, context=None, topic=None):
 | 
			
		||||
        if topic is None:
 | 
			
		||||
            cfg.CONF.import_opt('topic', 'zun.conductor.config',
 | 
			
		||||
                                group='conductor')
 | 
			
		||||
        super(API, self).__init__(transport, context,
 | 
			
		||||
                                  topic=cfg.CONF.conductor.topic)
 | 
			
		||||
 | 
			
		||||
    # NOTE(vivek): Add all APIs here
 | 
			
		||||
    def container_get(self, uuid):
 | 
			
		||||
        return self._call('container_get', uuid=uuid)
 | 
			
		||||
@@ -1,28 +0,0 @@
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
#    you may not use this file except in compliance with the License.
 | 
			
		||||
#    You may obtain a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#        http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
#    See the License for the specific language governing permissions and
 | 
			
		||||
#    limitations under the License.
 | 
			
		||||
 | 
			
		||||
"""Config options for Zun Backend service."""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from oslo_config import cfg
 | 
			
		||||
 | 
			
		||||
SERVICE_OPTS = [
 | 
			
		||||
    cfg.StrOpt('topic',
 | 
			
		||||
               default='zun-conductor',
 | 
			
		||||
               help='The queue to add conductor tasks to.'),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
opt_group = cfg.OptGroup(
 | 
			
		||||
    name='conductor',
 | 
			
		||||
    title='Options for the zun-conductor service')
 | 
			
		||||
cfg.CONF.register_group(opt_group)
 | 
			
		||||
cfg.CONF.register_opts(SERVICE_OPTS, opt_group)
 | 
			
		||||
@@ -1,24 +0,0 @@
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
#    you may not use this file except in compliance with the License.
 | 
			
		||||
#    You may obtain a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#        http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
#    See the License for the specific language governing permissions and
 | 
			
		||||
#    limitations under the License.
 | 
			
		||||
 | 
			
		||||
"""Zun Conductor default handler."""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# These are the database operations - They are executed by the conductor
 | 
			
		||||
# service.  API calls via AMQP trigger the handlers to be called.
 | 
			
		||||
 | 
			
		||||
class Handler(object):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super(Handler, self).__init__()
 | 
			
		||||
 | 
			
		||||
    def container_get(uuid):
 | 
			
		||||
        pass
 | 
			
		||||
							
								
								
									
										142
									
								
								zun/container/docker/driver.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								zun/container/docker/driver.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
			
		||||
# Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
# you may not use this file except in compliance with the License.
 | 
			
		||||
# You may obtain a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
# Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
# distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 | 
			
		||||
# implied.
 | 
			
		||||
# See the License for the specific language governing permissions and
 | 
			
		||||
# limitations under the License.
 | 
			
		||||
 | 
			
		||||
from docker import errors
 | 
			
		||||
import six
 | 
			
		||||
 | 
			
		||||
from oslo_config import cfg
 | 
			
		||||
from oslo_log import log as logging
 | 
			
		||||
 | 
			
		||||
from zun.container.docker import utils as docker_utils
 | 
			
		||||
from zun.container import driver
 | 
			
		||||
from zun.objects import fields
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
LOG = logging.getLogger(__name__)
 | 
			
		||||
CONF = cfg.CONF
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DockerDriver(driver.ContainerDriver):
 | 
			
		||||
    '''Implementation of container drivers for Docker.'''
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super(DockerDriver, self).__init__()
 | 
			
		||||
 | 
			
		||||
    def create(self, container):
 | 
			
		||||
        with docker_utils.docker_client() as docker:
 | 
			
		||||
            name = container.name
 | 
			
		||||
            image = container.image
 | 
			
		||||
            LOG.debug('Creating container with image %s name %s'
 | 
			
		||||
                      % (image, name))
 | 
			
		||||
            try:
 | 
			
		||||
                image_repo, image_tag = docker_utils.parse_docker_image(image)
 | 
			
		||||
                docker.pull(image_repo, tag=image_tag)
 | 
			
		||||
                kwargs = {'name': name,
 | 
			
		||||
                          'hostname': container.uuid,
 | 
			
		||||
                          'command': container.command,
 | 
			
		||||
                          'environment': container.environment}
 | 
			
		||||
                if docker_utils.is_docker_api_version_atleast(docker, '1.19'):
 | 
			
		||||
                    if container.memory is not None:
 | 
			
		||||
                        kwargs['host_config'] = {'mem_limit':
 | 
			
		||||
                                                 container.memory}
 | 
			
		||||
                else:
 | 
			
		||||
                    kwargs['mem_limit'] = container.memory
 | 
			
		||||
 | 
			
		||||
                response = docker.create_container(image, **kwargs)
 | 
			
		||||
                container.container_id = response['Id']
 | 
			
		||||
                container.status = fields.ContainerStatus.STOPPED
 | 
			
		||||
            except errors.APIError as e:
 | 
			
		||||
                container.status = fields.ContainerStatus.ERROR
 | 
			
		||||
                container.status_reason = six.text_type(e)
 | 
			
		||||
                raise
 | 
			
		||||
            container.save()
 | 
			
		||||
            return container
 | 
			
		||||
 | 
			
		||||
    def delete(self, container):
 | 
			
		||||
        with docker_utils.docker_client() as docker:
 | 
			
		||||
            return docker.remove_container(container.container_id)
 | 
			
		||||
 | 
			
		||||
    def list(self):
 | 
			
		||||
        with docker_utils.docker_client() as docker:
 | 
			
		||||
            return docker.list_instances()
 | 
			
		||||
 | 
			
		||||
    def show(self, container):
 | 
			
		||||
        with docker_utils.docker_client() as docker:
 | 
			
		||||
            try:
 | 
			
		||||
                result = docker.inspect_container(container.uuid)
 | 
			
		||||
                status = result.get('State')
 | 
			
		||||
                if status:
 | 
			
		||||
                    if status.get('Error') is True:
 | 
			
		||||
                        container.status = fields.ContainerStatus.ERROR
 | 
			
		||||
                    elif status.get('Paused'):
 | 
			
		||||
                        container.status = fields.ContainerStatus.PAUSED
 | 
			
		||||
                    elif status.get('Running'):
 | 
			
		||||
                        container.status = fields.ContainerStatus.RUNNING
 | 
			
		||||
                    else:
 | 
			
		||||
                        container.status = fields.ContainerStatus.STOPPED
 | 
			
		||||
                return container
 | 
			
		||||
            except errors.APIError as api_error:
 | 
			
		||||
                if '404' in str(api_error):
 | 
			
		||||
                    container.status = fields.ContainerStatus.ERROR
 | 
			
		||||
                    return container
 | 
			
		||||
                raise
 | 
			
		||||
 | 
			
		||||
    def reboot(self, container):
 | 
			
		||||
        with docker_utils.docker_client() as docker:
 | 
			
		||||
            docker.restart(container.container_id)
 | 
			
		||||
            container.status = fields.ContainerStatus.RUNNING
 | 
			
		||||
            return container
 | 
			
		||||
 | 
			
		||||
    def stop(self, container):
 | 
			
		||||
        with docker_utils.docker_client() as docker:
 | 
			
		||||
            docker.stop(container.container_id)
 | 
			
		||||
            container.status = fields.ContainerStatus.STOPPED
 | 
			
		||||
            return container
 | 
			
		||||
 | 
			
		||||
    def start(self, container):
 | 
			
		||||
        with docker_utils.docker_client() as docker:
 | 
			
		||||
            docker.start(container.container_id)
 | 
			
		||||
            container.status = fields.ContainerStatus.RUNNING
 | 
			
		||||
            return container
 | 
			
		||||
 | 
			
		||||
    def pause(self, container):
 | 
			
		||||
        with docker_utils.docker_client() as docker:
 | 
			
		||||
            docker.pause(container.container_id)
 | 
			
		||||
            container.status = fields.ContainerStatus.PAUSED
 | 
			
		||||
            return container
 | 
			
		||||
 | 
			
		||||
    def unpause(self, container):
 | 
			
		||||
        with docker_utils.docker_client() as docker:
 | 
			
		||||
            docker.unpause(container.container_id)
 | 
			
		||||
            container.status = fields.ContainerStatus.RUNNING
 | 
			
		||||
            return container
 | 
			
		||||
 | 
			
		||||
    def show_logs(self, container):
 | 
			
		||||
        with docker_utils.docker_client() as docker:
 | 
			
		||||
            return docker.get_container_logs(container.container_id)
 | 
			
		||||
 | 
			
		||||
    def execute(self, container, command):
 | 
			
		||||
        with docker_utils.docker_client() as docker:
 | 
			
		||||
            if docker_utils.is_docker_library_version_atleast('1.2.0'):
 | 
			
		||||
                create_res = docker.exec_create(
 | 
			
		||||
                    container.container_id, command, True, True, False)
 | 
			
		||||
                exec_output = docker.exec_start(create_res, False, False,
 | 
			
		||||
                                                False)
 | 
			
		||||
            else:
 | 
			
		||||
                exec_output = docker.execute(container.container_id, command)
 | 
			
		||||
            return exec_output
 | 
			
		||||
 | 
			
		||||
    def _encode_utf8(self, value):
 | 
			
		||||
        if six.PY2 and not isinstance(value, unicode):
 | 
			
		||||
            value = unicode(value)
 | 
			
		||||
        return value.encode('utf-8')
 | 
			
		||||
							
								
								
									
										146
									
								
								zun/container/docker/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								zun/container/docker/utils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,146 @@
 | 
			
		||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
# not use this file except in compliance with the License. You may obtain
 | 
			
		||||
# a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#      http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
# Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
# License for the specific language governing permissions and limitations
 | 
			
		||||
# under the License.
 | 
			
		||||
import contextlib
 | 
			
		||||
 | 
			
		||||
import docker
 | 
			
		||||
from docker import client
 | 
			
		||||
from docker import tls
 | 
			
		||||
from docker.utils import utils
 | 
			
		||||
from oslo_config import cfg
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
docker_opts = [
 | 
			
		||||
    cfg.StrOpt('docker_remote_api_version',
 | 
			
		||||
               default='1.20',
 | 
			
		||||
               help='Docker remote api version. Override it according to '
 | 
			
		||||
                    'specific docker api version in your environment.'),
 | 
			
		||||
    cfg.IntOpt('default_timeout',
 | 
			
		||||
               default=60,
 | 
			
		||||
               help='Default timeout in seconds for docker client '
 | 
			
		||||
                    'operations.'),
 | 
			
		||||
    cfg.StrOpt('api_url',
 | 
			
		||||
               default='unix:///var/run/docker.sock',
 | 
			
		||||
               help='API endpoint of docker daemon'),
 | 
			
		||||
    cfg.BoolOpt('api_insecure',
 | 
			
		||||
                default=False,
 | 
			
		||||
                help='If set, ignore any SSL validation issues'),
 | 
			
		||||
    cfg.StrOpt('ca_file',
 | 
			
		||||
               help='Location of CA certificates file for '
 | 
			
		||||
                    'securing docker api requests (tlscacert).'),
 | 
			
		||||
    cfg.StrOpt('cert_file',
 | 
			
		||||
               help='Location of TLS certificate file for '
 | 
			
		||||
                    'securing docker api requests (tlscert).'),
 | 
			
		||||
    cfg.StrOpt('key_file',
 | 
			
		||||
               help='Location of TLS private key file for '
 | 
			
		||||
                    'securing docker api requests (tlskey).'),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
CONF = cfg.CONF
 | 
			
		||||
CONF.register_opts(docker_opts, 'docker')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def parse_docker_image(image):
 | 
			
		||||
    image_parts = image.split(':', 1)
 | 
			
		||||
 | 
			
		||||
    image_repo = image_parts[0]
 | 
			
		||||
    image_tag = None
 | 
			
		||||
 | 
			
		||||
    if len(image_parts) > 1:
 | 
			
		||||
        image_tag = image_parts[1]
 | 
			
		||||
 | 
			
		||||
    return image_repo, image_tag
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def is_docker_library_version_atleast(version):
 | 
			
		||||
    if utils.compare_version(docker.version, version) <= 0:
 | 
			
		||||
        return True
 | 
			
		||||
    return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def is_docker_api_version_atleast(docker, version):
 | 
			
		||||
    if utils.compare_version(docker.version()['ApiVersion'], version) <= 0:
 | 
			
		||||
        return True
 | 
			
		||||
    return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@contextlib.contextmanager
 | 
			
		||||
def docker_client():
 | 
			
		||||
    client_kwargs = dict()
 | 
			
		||||
    if not CONF.docker.api_insecure:
 | 
			
		||||
        client_kwargs['ca_cert'] = CONF.docker.ca_file
 | 
			
		||||
        client_kwargs['client_key'] = CONF.docker.key_file
 | 
			
		||||
        client_kwargs['client_cert'] = CONF.docker.key_file
 | 
			
		||||
 | 
			
		||||
    yield DockerHTTPClient(
 | 
			
		||||
        CONF.docker.api_url,
 | 
			
		||||
        CONF.docker.docker_remote_api_version,
 | 
			
		||||
        CONF.docker.default_timeout,
 | 
			
		||||
        **client_kwargs
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DockerHTTPClient(client.Client):
 | 
			
		||||
    def __init__(self, url=CONF.docker.api_url,
 | 
			
		||||
                 ver=CONF.docker.docker_remote_api_version,
 | 
			
		||||
                 timeout=CONF.docker.default_timeout,
 | 
			
		||||
                 ca_cert=None,
 | 
			
		||||
                 client_key=None,
 | 
			
		||||
                 client_cert=None):
 | 
			
		||||
 | 
			
		||||
        if ca_cert and client_key and client_cert:
 | 
			
		||||
            ssl_config = tls.TLSConfig(
 | 
			
		||||
                client_cert=(client_cert, client_key),
 | 
			
		||||
                verify=ca_cert,
 | 
			
		||||
                assert_hostname=False,
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            ssl_config = False
 | 
			
		||||
 | 
			
		||||
        super(DockerHTTPClient, self).__init__(
 | 
			
		||||
            base_url=url,
 | 
			
		||||
            version=ver,
 | 
			
		||||
            timeout=timeout,
 | 
			
		||||
            tls=ssl_config
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def list_instances(self, inspect=False):
 | 
			
		||||
        """List all containers."""
 | 
			
		||||
        res = []
 | 
			
		||||
        for container in self.containers(all=True):
 | 
			
		||||
            info = self.inspect_container(container['Id'])
 | 
			
		||||
            if not info:
 | 
			
		||||
                continue
 | 
			
		||||
            if inspect:
 | 
			
		||||
                res.append(info)
 | 
			
		||||
            else:
 | 
			
		||||
                res.append(info['Config'].get('Hostname'))
 | 
			
		||||
        return res
 | 
			
		||||
 | 
			
		||||
    def pause(self, container):
 | 
			
		||||
        """Pause a running container."""
 | 
			
		||||
        if isinstance(container, dict):
 | 
			
		||||
            container = container.get('Id')
 | 
			
		||||
        url = self._url('/containers/{0}/pause'.format(container))
 | 
			
		||||
        res = self._post(url)
 | 
			
		||||
        self._raise_for_status(res)
 | 
			
		||||
 | 
			
		||||
    def unpause(self, container):
 | 
			
		||||
        """Unpause a paused container."""
 | 
			
		||||
        if isinstance(container, dict):
 | 
			
		||||
            container = container.get('Id')
 | 
			
		||||
        url = self._url('/containers/{0}/unpause'.format(container))
 | 
			
		||||
        res = self._post(url)
 | 
			
		||||
        self._raise_for_status(res)
 | 
			
		||||
 | 
			
		||||
    def get_container_logs(self, docker_id):
 | 
			
		||||
        """Fetch the logs of a container."""
 | 
			
		||||
        return self.logs(docker_id)
 | 
			
		||||
							
								
								
									
										123
									
								
								zun/container/driver.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								zun/container/driver.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
			
		||||
# Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
# you may not use this file except in compliance with the License.
 | 
			
		||||
# You may obtain a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
# Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
# distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 | 
			
		||||
# implied.
 | 
			
		||||
# See the License for the specific language governing permissions and
 | 
			
		||||
# limitations under the License.
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
from oslo_config import cfg
 | 
			
		||||
from oslo_log import log as logging
 | 
			
		||||
from oslo_utils import importutils
 | 
			
		||||
 | 
			
		||||
from zun.common.i18n import _LE
 | 
			
		||||
from zun.common.i18n import _LI
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
LOG = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
driver_opts = [
 | 
			
		||||
    cfg.StrOpt('container_driver',
 | 
			
		||||
               default='docker.driver.DockerDriver',
 | 
			
		||||
               help="""Defines which driver to use for controlling container.
 | 
			
		||||
 | 
			
		||||
Possible values:
 | 
			
		||||
 | 
			
		||||
* ``docker.driver.DockerDriver``
 | 
			
		||||
 | 
			
		||||
Services which consume this:
 | 
			
		||||
 | 
			
		||||
* ``zun-compute``
 | 
			
		||||
 | 
			
		||||
Interdependencies to other options:
 | 
			
		||||
 | 
			
		||||
* None
 | 
			
		||||
""")
 | 
			
		||||
]
 | 
			
		||||
CONF = cfg.CONF
 | 
			
		||||
CONF.register_opts(driver_opts)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def load_container_driver(container_driver=None):
 | 
			
		||||
    """Load a container driver module.
 | 
			
		||||
 | 
			
		||||
    Load the container driver module specified by the container_driver
 | 
			
		||||
    configuration option or, if supplied, the driver name supplied as an
 | 
			
		||||
    argument.
 | 
			
		||||
    :param container_driver: a container driver name to override the config opt
 | 
			
		||||
    :returns: a ContainerDriver instance
 | 
			
		||||
    """
 | 
			
		||||
    if not container_driver:
 | 
			
		||||
        container_driver = CONF.container_driver
 | 
			
		||||
 | 
			
		||||
    if not container_driver:
 | 
			
		||||
        LOG.error(_LE("Container driver option required, but not specified"))
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 | 
			
		||||
    LOG.info(_LI("Loading container driver '%s'"), container_driver)
 | 
			
		||||
    try:
 | 
			
		||||
        driver = importutils.import_object(
 | 
			
		||||
            'zun.container.%s' % container_driver)
 | 
			
		||||
        if not isinstance(driver, ContainerDriver):
 | 
			
		||||
            raise Exception(_('Expected driver of type: %s') %
 | 
			
		||||
                            str(ContainerDriver))
 | 
			
		||||
 | 
			
		||||
        return driver
 | 
			
		||||
    except ImportError:
 | 
			
		||||
        LOG.exception(_LE("Unable to load the container driver"))
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ContainerDriver(object):
 | 
			
		||||
    '''Base class for container drivers.'''
 | 
			
		||||
 | 
			
		||||
    def create(self, container):
 | 
			
		||||
        """Create a container."""
 | 
			
		||||
        raise NotImplementedError()
 | 
			
		||||
 | 
			
		||||
    def delete(self, container):
 | 
			
		||||
        """Delete a container."""
 | 
			
		||||
        raise NotImplementedError()
 | 
			
		||||
 | 
			
		||||
    def list(self):
 | 
			
		||||
        """List all containers."""
 | 
			
		||||
        raise NotImplementedError()
 | 
			
		||||
 | 
			
		||||
    def show(self, container):
 | 
			
		||||
        """Show the details of a container."""
 | 
			
		||||
        raise NotImplementedError()
 | 
			
		||||
 | 
			
		||||
    def reboot(self, container):
 | 
			
		||||
        """Reboot a container."""
 | 
			
		||||
        raise NotImplementedError()
 | 
			
		||||
 | 
			
		||||
    def stop(self, container):
 | 
			
		||||
        """Stop a container."""
 | 
			
		||||
        raise NotImplementedError()
 | 
			
		||||
 | 
			
		||||
    def start(self, container):
 | 
			
		||||
        """Start a container."""
 | 
			
		||||
        raise NotImplementedError()
 | 
			
		||||
 | 
			
		||||
    def pause(self, container):
 | 
			
		||||
        """Pause a container."""
 | 
			
		||||
        raise NotImplementedError()
 | 
			
		||||
 | 
			
		||||
    def unpause(self, container):
 | 
			
		||||
        """Pause a container."""
 | 
			
		||||
        raise NotImplementedError()
 | 
			
		||||
 | 
			
		||||
    def show_logs(self, container):
 | 
			
		||||
        """Show logs of a container."""
 | 
			
		||||
        raise NotImplementedError()
 | 
			
		||||
 | 
			
		||||
    def execute(self, container, command):
 | 
			
		||||
        """Execute a command in a running container."""
 | 
			
		||||
        raise NotImplementedError()
 | 
			
		||||
@@ -0,0 +1,34 @@
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
"""add container_id column to container
 | 
			
		||||
 | 
			
		||||
Revision ID: 5971a6844738
 | 
			
		||||
Revises: 9fe371393a24
 | 
			
		||||
Create Date: 2016-08-05 17:38:05.231740
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
# revision identifiers, used by Alembic.
 | 
			
		||||
revision = '5971a6844738'
 | 
			
		||||
down_revision = '9fe371393a24'
 | 
			
		||||
branch_labels = None
 | 
			
		||||
depends_on = None
 | 
			
		||||
 | 
			
		||||
from alembic import op
 | 
			
		||||
import sqlalchemy as sa
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def upgrade():
 | 
			
		||||
    op.add_column('container',
 | 
			
		||||
                  sa.Column('container_id', sa.String(length=255),
 | 
			
		||||
                            nullable=True))
 | 
			
		||||
@@ -160,7 +160,7 @@ class Connection(api.Connection):
 | 
			
		||||
        try:
 | 
			
		||||
            return query.one()
 | 
			
		||||
        except NoResultFound:
 | 
			
		||||
            raise exception.InstanceNotFound(container=container_id)
 | 
			
		||||
            raise exception.ContainerNotFound(container=container_id)
 | 
			
		||||
 | 
			
		||||
    def get_container_by_uuid(self, context, container_uuid):
 | 
			
		||||
        query = model_query(models.Container)
 | 
			
		||||
@@ -169,7 +169,7 @@ class Connection(api.Connection):
 | 
			
		||||
        try:
 | 
			
		||||
            return query.one()
 | 
			
		||||
        except NoResultFound:
 | 
			
		||||
            raise exception.InstanceNotFound(container=container_uuid)
 | 
			
		||||
            raise exception.ContainerNotFound(container=container_uuid)
 | 
			
		||||
 | 
			
		||||
    def get_container_by_name(self, context, container_name):
 | 
			
		||||
        query = model_query(models.Container)
 | 
			
		||||
@@ -178,7 +178,7 @@ class Connection(api.Connection):
 | 
			
		||||
        try:
 | 
			
		||||
            return query.one()
 | 
			
		||||
        except NoResultFound:
 | 
			
		||||
            raise exception.InstanceNotFound(container=container_name)
 | 
			
		||||
            raise exception.ContainerNotFound(container=container_name)
 | 
			
		||||
        except MultipleResultsFound:
 | 
			
		||||
            raise exception.Conflict('Multiple containers exist with same '
 | 
			
		||||
                                     'name. Please use the container uuid '
 | 
			
		||||
@@ -191,7 +191,7 @@ class Connection(api.Connection):
 | 
			
		||||
            query = add_identity_filter(query, container_id)
 | 
			
		||||
            count = query.delete()
 | 
			
		||||
            if count != 1:
 | 
			
		||||
                raise exception.InstanceNotFound(container_id)
 | 
			
		||||
                raise exception.ContainerNotFound(container_id)
 | 
			
		||||
 | 
			
		||||
    def update_container(self, container_id, values):
 | 
			
		||||
        # NOTE(dtantsur): this can lead to very strange errors
 | 
			
		||||
@@ -209,7 +209,7 @@ class Connection(api.Connection):
 | 
			
		||||
            try:
 | 
			
		||||
                ref = query.with_lockmode('update').one()
 | 
			
		||||
            except NoResultFound:
 | 
			
		||||
                raise exception.InstanceNotFound(container=container_id)
 | 
			
		||||
                raise exception.ContainerNotFound(container=container_id)
 | 
			
		||||
 | 
			
		||||
            if 'provision_state' in values:
 | 
			
		||||
                values['provision_updated_at'] = timeutils.utcnow()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,3 @@
 | 
			
		||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
@@ -127,6 +125,7 @@ class Container(Base):
 | 
			
		||||
    project_id = Column(String(255))
 | 
			
		||||
    user_id = Column(String(255))
 | 
			
		||||
    uuid = Column(String(36))
 | 
			
		||||
    container_id = Column(String(36))
 | 
			
		||||
    name = Column(String(255))
 | 
			
		||||
    image = Column(String(255))
 | 
			
		||||
    command = Column(String(255))
 | 
			
		||||
 
 | 
			
		||||
@@ -21,12 +21,14 @@ from zun.objects import fields as z_fields
 | 
			
		||||
class Container(base.ZunPersistentObject, base.ZunObject,
 | 
			
		||||
                base.ZunObjectDictCompat):
 | 
			
		||||
    # Version 1.0: Initial version
 | 
			
		||||
    VERSION = '1.0'
 | 
			
		||||
    # Version 1.1: Add container_id column
 | 
			
		||||
    VERSION = '1.1'
 | 
			
		||||
 | 
			
		||||
    dbapi = dbapi.get_instance()
 | 
			
		||||
 | 
			
		||||
    fields = {
 | 
			
		||||
        'id': fields.IntegerField(),
 | 
			
		||||
        'container_id': fields.StringField(nullable=True),
 | 
			
		||||
        'uuid': fields.StringField(nullable=True),
 | 
			
		||||
        'name': fields.StringField(nullable=True),
 | 
			
		||||
        'project_id': fields.StringField(nullable=True),
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ import zun.api.app
 | 
			
		||||
import zun.common.keystone
 | 
			
		||||
import zun.common.rpc_service
 | 
			
		||||
import zun.common.service
 | 
			
		||||
import zun.conductor.config
 | 
			
		||||
import zun.compute.config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def list_opts():
 | 
			
		||||
@@ -27,6 +27,6 @@ def list_opts():
 | 
			
		||||
             zun.common.service.service_opts,
 | 
			
		||||
         )),
 | 
			
		||||
        ('api', zun.api.app.API_SERVICE_OPTS),
 | 
			
		||||
        ('conductor', zun.conductor.config.SERVICE_OPTS),
 | 
			
		||||
        ('compute', zun.compute.config.SERVICE_OPTS),
 | 
			
		||||
        ('keystone_auth', zun.common.keystone.keystone_auth_opts),
 | 
			
		||||
    ]
 | 
			
		||||
 
 | 
			
		||||
@@ -50,9 +50,9 @@ class DbContainerTestCase(base.DbTestCase):
 | 
			
		||||
        self.assertEqual(container.uuid, res.uuid)
 | 
			
		||||
 | 
			
		||||
    def test_get_container_that_does_not_exist(self):
 | 
			
		||||
        self.assertRaises(exception.InstanceNotFound,
 | 
			
		||||
        self.assertRaises(exception.ContainerNotFound,
 | 
			
		||||
                          self.dbapi.get_container_by_id, self.context, 99)
 | 
			
		||||
        self.assertRaises(exception.InstanceNotFound,
 | 
			
		||||
        self.assertRaises(exception.ContainerNotFound,
 | 
			
		||||
                          self.dbapi.get_container_by_uuid,
 | 
			
		||||
                          self.context,
 | 
			
		||||
                          uuidutils.generate_uuid())
 | 
			
		||||
@@ -110,19 +110,19 @@ class DbContainerTestCase(base.DbTestCase):
 | 
			
		||||
    def test_destroy_container(self):
 | 
			
		||||
        container = utils.create_test_container()
 | 
			
		||||
        self.dbapi.destroy_container(container.id)
 | 
			
		||||
        self.assertRaises(exception.InstanceNotFound,
 | 
			
		||||
        self.assertRaises(exception.ContainerNotFound,
 | 
			
		||||
                          self.dbapi.get_container_by_id,
 | 
			
		||||
                          self.context, container.id)
 | 
			
		||||
 | 
			
		||||
    def test_destroy_container_by_uuid(self):
 | 
			
		||||
        container = utils.create_test_container()
 | 
			
		||||
        self.dbapi.destroy_container(container.uuid)
 | 
			
		||||
        self.assertRaises(exception.InstanceNotFound,
 | 
			
		||||
        self.assertRaises(exception.ContainerNotFound,
 | 
			
		||||
                          self.dbapi.get_container_by_uuid,
 | 
			
		||||
                          self.context, container.uuid)
 | 
			
		||||
 | 
			
		||||
    def test_destroy_container_that_does_not_exist(self):
 | 
			
		||||
        self.assertRaises(exception.InstanceNotFound,
 | 
			
		||||
        self.assertRaises(exception.ContainerNotFound,
 | 
			
		||||
                          self.dbapi.destroy_container,
 | 
			
		||||
                          uuidutils.generate_uuid())
 | 
			
		||||
 | 
			
		||||
@@ -139,7 +139,7 @@ class DbContainerTestCase(base.DbTestCase):
 | 
			
		||||
    def test_update_container_not_found(self):
 | 
			
		||||
        container_uuid = uuidutils.generate_uuid()
 | 
			
		||||
        new_image = 'new-image'
 | 
			
		||||
        self.assertRaises(exception.InstanceNotFound,
 | 
			
		||||
        self.assertRaises(exception.ContainerNotFound,
 | 
			
		||||
                          self.dbapi.update_container,
 | 
			
		||||
                          container_uuid, {'image': new_image})
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ def get_test_container(**kw):
 | 
			
		||||
    return {
 | 
			
		||||
        'id': kw.get('id', 42),
 | 
			
		||||
        'uuid': kw.get('uuid', 'ea8e2a25-2901-438d-8157-de7ffd68d051'),
 | 
			
		||||
        'container_id': kw.get('container_id', 'ddcb39a3fcec'),
 | 
			
		||||
        'name': kw.get('name', 'container1'),
 | 
			
		||||
        'project_id': kw.get('project_id', 'fake_project'),
 | 
			
		||||
        'user_id': kw.get('user_id', 'fake_user'),
 | 
			
		||||
 
 | 
			
		||||
@@ -71,14 +71,15 @@ class TestContainerObject(base.DbTestCase):
 | 
			
		||||
        with mock.patch.object(self.dbapi, 'list_container',
 | 
			
		||||
                               autospec=True) as mock_get_list:
 | 
			
		||||
            mock_get_list.return_value = [self.fake_container]
 | 
			
		||||
            filt = {'status': 'Running'}
 | 
			
		||||
            containers = objects.Container.list(self.context,
 | 
			
		||||
                                                filters={'bay_uuid': 'uuid'})
 | 
			
		||||
                                                filters=filt)
 | 
			
		||||
            self.assertEqual(1, mock_get_list.call_count)
 | 
			
		||||
            self.assertThat(containers, HasLength(1))
 | 
			
		||||
            self.assertIsInstance(containers[0], objects.Container)
 | 
			
		||||
            self.assertEqual(self.context, containers[0]._context)
 | 
			
		||||
            mock_get_list.assert_called_once_with(self.context,
 | 
			
		||||
                                                  filters={'bay_uuid': 'uuid'},
 | 
			
		||||
                                                  filters=filt,
 | 
			
		||||
                                                  limit=None, marker=None,
 | 
			
		||||
                                                  sort_key=None, sort_dir=None)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user