Enable etcd DB backend testing pipeline

As disussed in project plan, we decided to adopt etcd to store
container data which is faster and more flexible than mysql.

This commit enable etcd DB backend testing pipeline in Zun
and adjust the code correspondingly:

1. Set the value of 'db_type' to 'etcd' when runing etcd
   pipeline: 'gate-zun-devstack-dsvm-docker-etcd-nv'.
2. Drop the 'get_XXX_by_id' methods from etcd API since 'id'
   attribute makes no sense in etcd data model.
3. Adjust DB related test cases.
4. Make etcd write request thead-safe.

After the etcd related code been well tested, we'll change the
default DB type from 'mysql' to 'etcd'.

Part of blueprint etcd-db-driver

Change-Id: I06ac9b719ce93898cd4f67f1c7bdc2e0803e086e
This commit is contained in:
Wenzhi Yu 2016-12-20 13:44:22 +08:00
parent 62482a6acc
commit 96bd39afad
18 changed files with 198 additions and 168 deletions

View File

@ -19,6 +19,7 @@
# maintain if we want to change devstack config settings in future.
driver=$1
db=$2
if [ "$driver" = "docker" ]; then
export DEVSTACK_LOCAL_CONFIG+=$'\n'"ZUN_DRIVER=docker"
@ -27,4 +28,10 @@ elif [ "$driver" = "nova-docker" ]; then
export DEVSTACK_LOCAL_CONFIG+=$'\n'"IP_VERSION=4"
fi
if [ "$db" = "etcd" ]; then
export DEVSTACK_LOCAL_CONFIG+=$'\n'"ZUN_DB_TYPE=etcd"
elif [ "$db" = "sql" ]; then
export DEVSTACK_LOCAL_CONFIG+=$'\n'"ZUN_DB_TYPE=sql"
fi
$BASE/new/devstack-gate/devstack-vm-gate.sh

View File

@ -66,6 +66,7 @@ fi
DOCKER_GROUP=${DOCKER_GROUP:-docker}
ZUN_DRIVER=${ZUN_DRIVER:-docker}
ZUN_DB_TYPE=${ZUN_DB_TYPE:-sql}
ETCD_VERSION=v3.0.13
if is_ubuntu; then
@ -190,6 +191,11 @@ function create_zun_conf {
elif [[ ${ZUN_DRIVER} == "nova-docker" ]]; then
iniset $ZUN_CONF DEFAULT container_driver docker.driver.NovaDockerDriver
fi
if [[ ${ZUN_DB_TYPE} == "etcd" ]]; then
iniset $ZUN_CONF DEFAULT db_type etcd
elif [[ ${ZUN_DB_TYPE} == "sql" ]]; then
iniset $ZUN_CONF DEFAULT db_type sql
fi
iniset $ZUN_CONF DEFAULT debug "$ENABLE_DEBUG_LOG_LEVEL"
iniset $ZUN_CONF oslo_messaging_rabbit rabbit_userid $RABBIT_USERID
iniset $ZUN_CONF oslo_messaging_rabbit rabbit_password $RABBIT_PASSWORD

View File

@ -337,7 +337,7 @@ class ContainersController(rest.RestController):
utils.validate_container_state(container, 'delete')
context = pecan.request.context
pecan.request.rpcapi.container_delete(context, container, force)
container.destroy()
container.destroy(context)
pecan.response.status = 204
@pecan.expose('json')

View File

@ -12,12 +12,16 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_concurrency import lockutils
class Singleton(type):
_instances = {}
_semaphores = lockutils.Semaphores()
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(
Singleton, cls).__call__(*args, **kwargs)
with lockutils.lock('singleton_lock', semaphores=cls._semaphores):
if cls not in cls._instances:
cls._instances[cls] = super(
Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]

View File

@ -36,11 +36,11 @@ class Manager(object):
super(Manager, self).__init__()
self.driver = driver.load_container_driver(container_driver)
def _fail_container(self, container, error):
def _fail_container(self, context, container, error):
container.status = fields.ContainerStatus.ERROR
container.status_reason = error
container.task_state = None
container.save()
container.save(context)
def container_create(self, context, container):
utils.spawn_n(self._do_container_create, context, container)
@ -65,7 +65,7 @@ class Manager(object):
LOG.debug('Creating container: %s', container.uuid)
container.task_state = fields.TaskState.SANDBOX_CREATING
container.save()
container.save(context)
sandbox_id = None
sandbox_image = 'kubernetes/pause'
repo, tag = utils.parse_image_name(sandbox_image)
@ -77,12 +77,12 @@ class Manager(object):
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.exception(_LE("Unexpected exception: %s"),
six.text_type(e))
self._fail_container(container, six.text_type(e))
self._fail_container(context, container, six.text_type(e))
return
self.driver.set_sandbox_id(container, sandbox_id)
container.task_state = fields.TaskState.IMAGE_PULLING
container.save()
container.save(context)
repo, tag = utils.parse_image_name(container.image)
image_pull_policy = utils.get_image_pull_policy(
container.image_pull_policy, tag)
@ -93,7 +93,7 @@ class Manager(object):
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.error(six.text_type(e))
self._do_sandbox_cleanup(context, sandbox_id)
self._fail_container(container, six.text_type(e))
self._fail_container(context, container, six.text_type(e))
return
except exception.DockerError as e:
with excutils.save_and_reraise_exception(reraise=reraise):
@ -101,24 +101,25 @@ class Manager(object):
"Error occurred while calling Docker image API: %s"),
six.text_type(e))
self._do_sandbox_cleanup(context, sandbox_id)
self._fail_container(container, six.text_type(e))
self._fail_container(context, container, six.text_type(e))
return
except Exception as e:
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.exception(_LE("Unexpected exception: %s"),
six.text_type(e))
self._do_sandbox_cleanup(context, sandbox_id)
self._fail_container(container, six.text_type(e))
self._fail_container(context, container, six.text_type(e))
return
container.task_state = fields.TaskState.CONTAINER_CREATING
container.save()
container.save(context)
try:
container = self.driver.create(container, sandbox_id, image)
container = self.driver.create(context, container,
sandbox_id, image)
container.addresses = self._get_container_addresses(context,
container)
container.task_state = None
container.save()
container.save(context)
return container
except exception.DockerError as e:
with excutils.save_and_reraise_exception(reraise=reraise):
@ -126,33 +127,33 @@ class Manager(object):
"Error occurred while calling Docker create API: %s"),
six.text_type(e))
self._do_sandbox_cleanup(context, sandbox_id)
self._fail_container(container, six.text_type(e))
self._fail_container(context, container, six.text_type(e))
return
except Exception as e:
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.exception(_LE("Unexpected exception: %s"),
six.text_type(e))
self._do_sandbox_cleanup(context, sandbox_id)
self._fail_container(container, six.text_type(e))
self._fail_container(context, container, six.text_type(e))
return
def _do_container_start(self, context, container, reraise=False):
LOG.debug('Starting container: %s', container.uuid)
try:
container = self.driver.start(container)
container.save()
container.save(context)
return container
except exception.DockerError as e:
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.error(_LE(
"Error occurred while calling Docker start API: %s"),
six.text_type(e))
self._fail_container(container, six.text_type(e))
self._fail_container(context, container, six.text_type(e))
except Exception as e:
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.exception(_LE("Unexpected exception: %s"),
six.text_type(e))
self._fail_container(container, six.text_type(e))
self._fail_container(context, container, six.text_type(e))
@translate_exception
def container_delete(self, context, container, force):
@ -196,7 +197,7 @@ class Manager(object):
LOG.debug('Showing container: %s', container.uuid)
try:
container = self.driver.show(container)
container.save()
container.save(context)
return container
except exception.DockerError as e:
LOG.error(_LE("Error occurred while calling Docker show API: %s"),
@ -210,7 +211,7 @@ class Manager(object):
LOG.debug('Rebooting container: %s', container.uuid)
try:
container = self.driver.reboot(container, timeout)
container.save()
container.save(context)
return container
except exception.DockerError as e:
with excutils.save_and_reraise_exception(reraise=reraise):
@ -228,7 +229,7 @@ class Manager(object):
LOG.debug('Stopping container: %s', container.uuid)
try:
container = self.driver.stop(container, timeout)
container.save()
container.save(context)
return container
except exception.DockerError as e:
with excutils.save_and_reraise_exception(reraise=reraise):
@ -250,7 +251,7 @@ class Manager(object):
LOG.debug('Pausing container: %s', container.uuid)
try:
container = self.driver.pause(container)
container.save()
container.save(context)
return container
except exception.DockerError as e:
with excutils.save_and_reraise_exception(reraise=reraise):
@ -269,7 +270,7 @@ class Manager(object):
LOG.debug('Unpausing container: %s', container.uuid)
try:
container = self.driver.unpause(container)
container.save()
container.save(context)
return container
except exception.DockerError as e:
with excutils.save_and_reraise_exception(reraise=reraise):
@ -315,7 +316,7 @@ class Manager(object):
LOG.debug('kill signal to container: %s', container.uuid)
try:
container = self.driver.kill(container, signal)
container.save()
container.save(context)
return container
except exception.DockerError as e:
with excutils.save_and_reraise_exception(reraise=reraise):

View File

@ -55,7 +55,7 @@ class DockerDriver(driver.ContainerDriver):
response = docker.images(repo, quiet)
return response
def create(self, container, sandbox_id, image):
def create(self, context, container, sandbox_id, image):
with docker_utils.docker_client() as docker:
name = container.name
if image['path']:
@ -91,7 +91,7 @@ class DockerDriver(driver.ContainerDriver):
response = docker.create_container(image, **kwargs)
container.container_id = response['Id']
container.status = fields.ContainerStatus.STOPPED
container.save()
container.save(context)
return container
def delete(self, container, force):

View File

@ -59,7 +59,7 @@ def load_container_driver(container_driver=None):
class ContainerDriver(object):
'''Base class for container drivers.'''
def create(self, container, sandbox_name=None):
def create(self, context, container, sandbox_name=None):
"""Create a container."""
raise NotImplementedError()

View File

@ -17,8 +17,10 @@
import json
import etcd
from oslo_concurrency import lockutils
from oslo_log import log
from oslo_utils import strutils
from oslo_utils import timeutils
from oslo_utils import uuidutils
import six
@ -91,6 +93,7 @@ class EtcdAPI(object):
def __init__(self, host, port):
self.client = etcd.Client(host=host, port=port)
@lockutils.synchronized('etcd-client')
def clean_all_zun_data(self):
try:
for d in self.client.read('/').children:
@ -123,6 +126,8 @@ class EtcdAPI(object):
return resources
def _process_list_result(self, res_list, limit=None, sort_key=None):
if len(res_list) == 0:
return []
sorted_res_list = res_list
if sort_key:
if not hasattr(res_list[0], sort_key):
@ -140,10 +145,9 @@ class EtcdAPI(object):
try:
res = getattr(self.client.read('/containers'), 'children', None)
except etcd.EtcdKeyNotFound:
LOG.error(
_LE("Path '/containers' does not exist, seems etcd server "
"was not been initialized appropriately for Zun."))
raise
# Before the first container been created, path '/containers'
# does not exist.
return []
except Exception as e:
LOG.error(
_LE("Error occurred while reading from etcd server: %s"),
@ -184,6 +188,7 @@ class EtcdAPI(object):
raise exception.ContainerAlreadyExists(field='name',
value=lowername)
@lockutils.synchronized('etcd_container')
def create_container(self, context, container_data):
# ensure defaults are present for new containers
if not container_data.get('uuid'):
@ -201,23 +206,6 @@ class EtcdAPI(object):
return container
def get_container_by_id(self, context, container_id):
try:
filters = self._add_tenant_filters(
context, {'id': container_id})
containers = self.list_container(context, filters=filters)
except etcd.EtcdKeyNotFound:
raise exception.ContainerNotFound(container=container_id)
except Exception as e:
LOG.error(_LE('Error occurred while retrieving container: %s'),
six.text_type(e))
raise
if len(containers) == 0:
raise exception.ContainerNotFound(container=container_id)
return containers[0]
def get_container_by_uuid(self, context, container_uuid):
try:
res = self.client.read('/containers/' + container_uuid)
@ -256,21 +244,13 @@ class EtcdAPI(object):
return containers[0]
def _get_container_by_ident(self, context, container_ident):
if strutils.is_int_like(container_ident):
container = self.get_container_by_id(context, container_ident)
elif uuidutils.is_uuid_like(container_ident):
container = self.get_container_by_uuid(context, container_ident)
else:
raise exception.InvalidIdentity(identity=container_ident)
return container
def destroy_container(self, context, container_ident):
container = self._get_container_by_ident(context, container_ident)
@lockutils.synchronized('etcd_container')
def destroy_container(self, context, container_uuid):
container = self.get_container_by_uuid(context, container_uuid)
self.client.delete('/containers/' + container.uuid)
def update_container(self, context, container_ident, values):
@lockutils.synchronized('etcd_container')
def update_container(self, context, container_uuid, values):
# NOTE(yuywz): Update would fail if any other client
# write '/containers/$CONTAINER_UUID' in the meanwhile
if 'uuid' in values:
@ -281,15 +261,15 @@ class EtcdAPI(object):
self._validate_unique_container_name(context, values['name'])
try:
target_uuid = self._get_container_by_ident(
context, container_ident).uuid
target_uuid = self.get_container_by_uuid(
context, container_uuid).uuid
target = self.client.read('/containers/' + target_uuid)
target_value = json.loads(target.value)
target_value.update(values)
target.value = json.dumps(target_value)
self.client.update(target)
except etcd.EtcdKeyNotFound:
raise exception.ContainerNotFound(container=container_ident)
raise exception.ContainerNotFound(container=container_uuid)
except Exception as e:
LOG.error(_LE('Error occurred while updating container: %s'),
six.text_type(e))
@ -297,7 +277,9 @@ class EtcdAPI(object):
return translate_etcd_result(target, 'container')
@lockutils.synchronized('etcd_zunservice')
def create_zun_service(self, values):
values['created_at'] = timeutils.isotime()
zun_service = models.ZunService(values)
zun_service.save()
return zun_service
@ -329,6 +311,7 @@ class EtcdAPI(object):
def get_zun_service(self, host, binary):
try:
service = None
res = self.client.read('/zun_services/' + host + '_' + binary)
service = translate_etcd_result(res, 'zun_service')
except etcd.EtcdKeyNotFound:
@ -337,9 +320,10 @@ class EtcdAPI(object):
LOG.error(_LE('Error occurred while retrieving zun service: %s'),
six.text_type(e))
raise
finally:
return service
return service
@lockutils.synchronized('etcd_zunservice')
def destroy_zun_service(self, host, binary):
try:
self.client.delete('/zun_services/' + host + '_' + binary)
@ -350,10 +334,12 @@ class EtcdAPI(object):
six.text_type(e))
raise
@lockutils.synchronized('etcd_zunservice')
def update_zun_service(self, host, binary, values):
try:
target = self.client.read('/zun_services/' + host + '_' + binary)
target_value = json.loads(target.value)
values['updated_at'] = timeutils.isotime()
target_value.update(values)
target.value = json.dumps(target_value)
self.client.update(target)
@ -364,6 +350,7 @@ class EtcdAPI(object):
six.text_type(e))
raise
@lockutils.synchronized('etcd_image')
def pull_image(self, context, values):
if not values.get('uuid'):
values['uuid'] = uuidutils.generate_uuid()
@ -378,6 +365,7 @@ class EtcdAPI(object):
image.save()
return image
@lockutils.synchronized('etcd_image')
def update_image(self, image_uuid, values):
if 'uuid' in values:
msg = _('Cannot overwrite UUID for an existing image.')
@ -403,10 +391,9 @@ class EtcdAPI(object):
try:
res = getattr(self.client.read('/images'), 'children', None)
except etcd.EtcdKeyNotFound:
LOG.error(
_LE("Path '/images' does not exist, seems etcd server "
"was not been initialized appropriately for Zun."))
raise
# Before the first image been pulled, path '/image' does
# not exist.
return []
except Exception as e:
LOG.error(
_LE("Error occurred while reading from etcd server: %s"),

View File

@ -17,6 +17,7 @@ etcd models
"""
import etcd
import json
from zun.common import exception
import zun.db.etcd as db
@ -66,14 +67,14 @@ class Base(object):
if self.path_already_exist(client, path):
raise exception.ResourceExists(name=getattr(self, '__class__'))
client.write(path, self.as_dict())
client.write(path, json.dumps(self.as_dict()))
return
class ZunService(Base):
"""Represents health status of various zun services"""
_path = '/zun_service'
_path = '/zun_services'
_fields = objects.ZunService.fields.keys()
@ -81,6 +82,10 @@ class ZunService(Base):
self.path = ZunService.path()
for f in ZunService.fields():
setattr(self, f, None)
self.id = 1
self.disabled = False
self.forced_down = False
self.report_count = 0
self.update(service_data)
@classmethod
@ -101,7 +106,7 @@ class ZunService(Base):
raise exception.ZunServiceAlreadyExists(host=self.host,
binary=self.binary)
client.write(path, self.as_dict())
client.write(path, json.dumps(self.as_dict()))
return
@ -116,6 +121,7 @@ class Container(Base):
self.path = Container.path()
for f in Container.fields():
setattr(self, f, None)
self.id = 1
self.update(container_data)
@classmethod
@ -138,6 +144,7 @@ class Image(Base):
self.path = Image.path()
for f in Image.fields():
setattr(self, f, None)
self.id = 1
self.update(image_data)
@classmethod

View File

@ -16,6 +16,7 @@
# NOTE(deva): import auth_token so we can override a config option
from keystonemiddleware import auth_token # noqa
from oslo_config import cfg
import pecan
import pecan.testing
from six.moves.urllib import parse as urlparse
@ -37,6 +38,10 @@ class FunctionalTest(base.DbTestCase):
def setUp(self):
super(FunctionalTest, self).setUp()
# NOTE(yuywz): In API test cases, we use sqllite as the DB
# backend, so we should set 'db_type' to 'sql' to access
# sqllite DB with sqlalchemy api.
cfg.CONF.set_override('db_type', 'sql')
zun.conf.CONF.set_override("auth_version", "v2.0",
group='keystone_authtoken',
enforce_type=True)

View File

@ -693,7 +693,7 @@ class TestContainerController(api_base.FunctionalTest):
self.assertEqual(204, response.status_int)
mock_container_delete.assert_called_once_with(
mock.ANY, test_container_obj, False)
mock_destroy.assert_called_once_with()
mock_destroy.assert_called_once_with(mock.ANY)
def test_delete_by_uuid_invalid_state(self):
uuid = uuidutils.generate_uuid()
@ -729,7 +729,7 @@ class TestContainerController(api_base.FunctionalTest):
self.assertEqual(204, response.status_int)
mock_container_delete.assert_called_once_with(
mock.ANY, test_container_obj, False)
mock_destroy.assert_called_once_with()
mock_destroy.assert_called_once_with(mock.ANY)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.rpcapi.API.container_kill')

View File

@ -37,7 +37,8 @@ class TestManager(base.TestCase):
@mock.patch.object(Container, 'save')
def test_fail_container(self, mock_save):
container = Container(self.context, **utils.get_test_container())
self.compute_manager._fail_container(container, "Creation Failed")
self.compute_manager._fail_container(self.context, container,
"Creation Failed")
self.assertEqual(fields.ContainerStatus.ERROR, container.status)
self.assertEqual("Creation Failed", container.status_reason)
self.assertIsNone(container.task_state)
@ -52,10 +53,11 @@ class TestManager(base.TestCase):
mock_pull.return_value = 'fake_path'
mock_create_sandbox.return_value = 'fake_id'
self.compute_manager._do_container_create(self.context, container)
mock_save.assert_called_with()
mock_save.assert_called_with(self.context)
mock_pull.assert_any_call(self.context, container.image, 'latest',
'always')
mock_create.assert_called_once_with(container, 'fake_id', 'fake_path')
mock_create.assert_called_once_with(self.context, container,
'fake_id', 'fake_path')
@mock.patch.object(Container, 'save')
@mock.patch.object(fake_driver, 'create_sandbox')
@ -67,7 +69,8 @@ class TestManager(base.TestCase):
mock_pull.side_effect = exception.DockerError("Pull Failed")
mock_create_sandbox.return_value = mock.MagicMock()
self.compute_manager._do_container_create(self.context, container)
mock_fail.assert_called_once_with(container, "Pull Failed")
mock_fail.assert_called_once_with(self.context,
container, "Pull Failed")
@mock.patch.object(Container, 'save')
@mock.patch.object(fake_driver, 'create_sandbox')
@ -79,7 +82,8 @@ class TestManager(base.TestCase):
mock_pull.side_effect = exception.ImageNotFound("Image Not Found")
mock_create_sandbox.return_value = mock.MagicMock()
self.compute_manager._do_container_create(self.context, container)
mock_fail.assert_called_once_with(container, "Image Not Found")
mock_fail.assert_called_once_with(self.context,
container, "Image Not Found")
@mock.patch.object(Container, 'save')
@mock.patch.object(fake_driver, 'create_sandbox')
@ -92,7 +96,8 @@ class TestManager(base.TestCase):
message="Image Not Found")
mock_create_sandbox.return_value = mock.MagicMock()
self.compute_manager._do_container_create(self.context, container)
mock_fail.assert_called_once_with(container, "Image Not Found")
mock_fail.assert_called_once_with(self.context,
container, "Image Not Found")
@mock.patch.object(Container, 'save')
@mock.patch('zun.image.driver.pull_image')
@ -107,7 +112,8 @@ class TestManager(base.TestCase):
mock_create.side_effect = exception.DockerError("Creation Failed")
mock_create_sandbox.return_value = mock.MagicMock()
self.compute_manager._do_container_create(self.context, container)
mock_fail.assert_called_once_with(container, "Creation Failed")
mock_fail.assert_called_once_with(self.context,
container, "Creation Failed")
@mock.patch.object(Container, 'save')
@mock.patch('zun.image.driver.pull_image')
@ -120,10 +126,11 @@ class TestManager(base.TestCase):
mock_create.return_value = container
container.status = 'Stopped'
self.compute_manager._do_container_run(self.context, container)
mock_save.assert_called_with()
mock_save.assert_called_with(self.context)
mock_pull.assert_any_call(self.context, container.image, 'latest',
'always')
mock_create.assert_called_once_with(container, None, 'fake_path')
mock_create.assert_called_once_with(self.context, container,
None, 'fake_path')
mock_start.assert_called_once_with(container)
@mock.patch.object(Container, 'save')
@ -136,8 +143,9 @@ class TestManager(base.TestCase):
message="Image Not Found")
self.compute_manager._do_container_run(self.context,
container)
mock_save.assert_called_with()
mock_fail.assert_called_with(container, 'Image Not Found')
mock_save.assert_called_with(self.context)
mock_fail.assert_called_with(self.context,
container, 'Image Not Found')
mock_pull.assert_called_once_with(self.context, 'kubernetes/pause',
'latest', 'ifnotpresent')
@ -151,8 +159,9 @@ class TestManager(base.TestCase):
message="Image Not Found")
self.compute_manager._do_container_run(self.context,
container)
mock_save.assert_called_with()
mock_fail.assert_called_with(container, 'Image Not Found')
mock_save.assert_called_with(self.context)
mock_fail.assert_called_with(self.context,
container, 'Image Not Found')
mock_pull.assert_called_once_with(self.context, 'kubernetes/pause',
'latest', 'ifnotpresent')
@ -166,8 +175,9 @@ class TestManager(base.TestCase):
message="Docker Error occurred")
self.compute_manager._do_container_run(self.context,
container)
mock_save.assert_called_with()
mock_fail.assert_called_with(container, 'Docker Error occurred')
mock_save.assert_called_with(self.context)
mock_fail.assert_called_with(self.context,
container, 'Docker Error occurred')
mock_pull.assert_called_once_with(self.context, 'kubernetes/pause',
'latest', 'ifnotpresent')
@ -184,11 +194,12 @@ class TestManager(base.TestCase):
message="Docker Error occurred")
self.compute_manager._do_container_run(self.context,
container)
mock_save.assert_called_with()
mock_fail.assert_called_with(container, 'Docker Error occurred')
mock_save.assert_called_with(self.context)
mock_fail.assert_called_with(self.context,
container, 'Docker Error occurred')
mock_pull.assert_any_call(self.context, container.image, 'latest',
'always')
mock_create.assert_called_once_with(container, None,
mock_create.assert_called_once_with(self.context, container, None,
{'name': 'nginx', 'path': None})
@mock.patch.object(fake_driver, 'delete')

View File

@ -1,36 +1,40 @@
# 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 zun.conf
from zun.db import api as db_api
from zun.db.sqlalchemy import api as sqla_api
from zun.db.sqlalchemy import migration
from zun.tests import base
from zun.tests.unit.db.base import Database
CONF = zun.conf.CONF
_DB_CACHE = None
class DriverTestCase(base.TestCase):
def setUp(self):
super(DriverTestCase, self).setUp()
self.dbapi = db_api.get_instance()
global _DB_CACHE
if not _DB_CACHE:
_DB_CACHE = Database(sqla_api, migration,
sql_connection=CONF.database.connection)
self.useFixture(_DB_CACHE)
# 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 oslo_config import cfg
import zun.conf
from zun.db import api as db_api
from zun.db.sqlalchemy import api as sqla_api
from zun.db.sqlalchemy import migration
from zun.tests import base
from zun.tests.unit.db.base import Database
CONF = zun.conf.CONF
_DB_CACHE = None
class DriverTestCase(base.TestCase):
def setUp(self):
super(DriverTestCase, self).setUp()
# NOTE(yuywz): In driver test cases, we use sqllite as
# the DB backend, so we should set 'db_type' to 'sql'
# to access sqllite DB with sqlalchemy api.
cfg.CONF.set_override('db_type', 'sql')
self.dbapi = db_api.get_instance()
global _DB_CACHE
if not _DB_CACHE:
_DB_CACHE = Database(sqla_api, migration,
sql_connection=CONF.database.connection)
self.useFixture(_DB_CACHE)

View File

@ -19,6 +19,7 @@ from zun.container.docker import utils as docker_utils
from zun.objects import fields
from zun.tests.unit.container import base
from zun.tests.unit.db import utils as db_utils
import zun.tests.unit.objects.utils as obj_utils
class TestDockerDriver(base.DriverTestCase):
@ -53,15 +54,16 @@ class TestDockerDriver(base.DriverTestCase):
self.driver.images(repo='test')
self.mock_docker.images.assert_called_once_with('test', False)
def test_create_image_path_is_none(self):
@mock.patch('zun.objects.container.Container.save')
def test_create_image_path_is_none(self, mock_save):
self.mock_docker.create_host_config = mock.Mock(
return_value={'Id1': 'val1', 'key2': 'val2'})
self.mock_docker.create_container = mock.Mock(
return_value={'Id': 'val1', 'key1': 'val2'})
db_image = {'path': ''}
db_container = db_utils.create_test_container(context=self.context)
result_container = self.driver.create(db_container, 'test_sandbox',
db_image)
container = obj_utils.get_test_container(context=self.context)
result_container = self.driver.create(self.context, container,
'test_sandbox', db_image)
host_config = {}
host_config['network_mode'] = 'container:test_sandbox'
@ -82,12 +84,13 @@ class TestDockerDriver(base.DriverTestCase):
'host_config': {'Id1': 'val1', 'key2': 'val2'},
}
self.mock_docker.create_container.assert_called_once_with(
db_container.image, **kwargs)
container.image, **kwargs)
self.assertEqual(result_container.container_id, 'val1')
self.assertEqual(result_container.status,
fields.ContainerStatus.STOPPED)
def test_create_image_path_is_not_none(self):
@mock.patch('zun.objects.container.Container.save')
def test_create_image_path_is_not_none(self, mock_save):
self.mock_docker.load_image = mock.Mock(return_value='load_test')
self.mock_docker.create_host_config = mock.Mock(
return_value={'Id1': 'val1', 'key2': 'val2'})
@ -95,9 +98,10 @@ class TestDockerDriver(base.DriverTestCase):
return_value={'Id': 'val1', 'key1': 'val2'})
mock_open_file = mock.mock_open(read_data='test_data')
with mock.patch('zun.container.docker.driver.open', mock_open_file):
db_container = db_utils.create_test_container(context=self.context)
result_container = self.driver.create(db_container, 'test_sandbox',
{'path': 'test_path'})
container = obj_utils.get_test_container(context=self.context)
result_container = self.driver.create(
self.context, container,
'test_sandbox', {'path': 'test_path'})
self.mock_docker.load_image.assert_called_once_with('test_data')
host_config = {}
@ -119,7 +123,7 @@ class TestDockerDriver(base.DriverTestCase):
'name': 'zun-ea8e2a25-2901-438d-8157-de7ffd68d051',
}
self.mock_docker.create_container.assert_called_once_with(
db_container.image, **kwargs)
container.image, **kwargs)
self.assertEqual(result_container.container_id, 'val1')
self.assertEqual(result_container.status,
fields.ContainerStatus.STOPPED)

View File

@ -281,17 +281,6 @@ class EtcdDbContainerTestCase(base.DbTestCase):
utils.create_test_container,
context=self.context)
@mock.patch.object(etcd_client, 'read')
@mock.patch.object(etcd_client, 'write')
def test_get_container_by_id(self, mock_write, mock_read):
mock_read.side_effect = etcd.EtcdKeyNotFound
container = utils.create_test_container(context=self.context)
mock_read.side_effect = lambda *args: FakeEtcdMutlipleResult(
[container.as_dict()])
res = dbapi.Connection.get_container_by_id(self.context, container.id)
self.assertEqual(container.id, res.id)
self.assertEqual(container.uuid, res.uuid)
@mock.patch.object(etcd_client, 'read')
@mock.patch.object(etcd_client, 'write')
def test_get_container_by_uuid(self, mock_write, mock_read):
@ -319,9 +308,6 @@ class EtcdDbContainerTestCase(base.DbTestCase):
@mock.patch.object(etcd_client, 'read')
def test_get_container_that_does_not_exist(self, mock_read):
mock_read.side_effect = etcd.EtcdKeyNotFound
self.assertRaises(exception.ContainerNotFound,
dbapi.Connection.get_container_by_id,
self.context, 99)
self.assertRaises(exception.ContainerNotFound,
dbapi.Connection.get_container_by_uuid,
self.context,
@ -410,9 +396,9 @@ class EtcdDbContainerTestCase(base.DbTestCase):
def test_destroy_container(self, mock_delete, mock_write, mock_read):
mock_read.side_effect = etcd.EtcdKeyNotFound
container = utils.create_test_container(context=self.context)
mock_read.side_effect = lambda *args: FakeEtcdMutlipleResult(
[container.as_dict()])
dbapi.Connection.destroy_container(self.context, container.id)
mock_read.side_effect = lambda *args: FakeEtcdResult(
container.as_dict())
dbapi.Connection.destroy_container(self.context, container.uuid)
mock_delete.assert_called_once_with('/containers/%s' % container.uuid)
@mock.patch.object(etcd_client, 'read')

View File

@ -29,6 +29,10 @@ from zun.tests.unit.db.utils import FakeEtcdResult
class DbImageTestCase(base.DbTestCase):
def setUp(self):
cfg.CONF.set_override('db_type', 'sql')
super(DbImageTestCase, self).setUp()
def test_pull_image(self):
utils.create_test_image(context=self.context,
repo="ubuntu:latest")

View File

@ -65,9 +65,9 @@ class EtcdDbZunServiceTestCase(base.DbTestCase):
def test_get_zun_service_not_found(self, mock_write, mock_read):
mock_read.side_effect = etcd.EtcdKeyNotFound
zun_service = utils.create_test_zun_service()
self.assertRaises(exception.ZunServiceNotFound,
dbapi.Connection.get_zun_service, self.context,
'wrong_host_name', zun_service.binary)
res = dbapi.Connection.get_zun_service(
self.context, 'wrong_host_name', zun_service.binary)
self.assertIsNone(res)
@mock.patch.object(etcd_client, 'read')
@mock.patch.object(etcd_client, 'write')

View File

@ -14,6 +14,7 @@
# under the License.
import mock
from oslo_utils import uuidutils
from testtools.matchers import HasLength
@ -83,13 +84,16 @@ class TestContainerObject(base.DbTestCase):
self.assertEqual(self.context, container._context)
def test_status_reason_in_fields(self):
container = objects.Container(self.context, **self.fake_container)
self.assertTrue(hasattr(container, 'status_reason'))
container.status_reason = "Docker Error happened"
container.create(self.context)
containers = objects.Container.list(self.context)
self.assertTrue(hasattr(containers[0], 'status_reason'))
self.assertEqual("Docker Error happened", containers[0].status_reason)
with mock.patch.object(self.dbapi, 'create_container',
autospec=True) as mock_create_container:
mock_create_container.return_value = self.fake_container
container = objects.Container(self.context, **self.fake_container)
self.assertTrue(hasattr(container, 'status_reason'))
container.status_reason = "Docker Error happened"
container.create(self.context)
self.assertEqual(
"Docker Error happened",
mock_create_container.call_args_list[0][0][1]['status_reason'])
def test_destroy(self):
uuid = self.fake_container['uuid']