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:
Hongbin Lu 2016-07-31 21:12:35 -05:00
parent 70984e38d4
commit d740149945
20 changed files with 601 additions and 189 deletions

View File

@ -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()

View File

@ -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.")

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View 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')

View 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
View 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()

View File

@ -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))

View File

@ -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()

View File

@ -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))

View File

@ -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),

View File

@ -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),
]

View File

@ -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})

View File

@ -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'),

View File

@ -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)