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

@ -19,6 +19,7 @@
# maintain if we want to change devstack config settings in future. # maintain if we want to change devstack config settings in future.
driver=$1 driver=$1
db=$2
if [ "$driver" = "docker" ]; then if [ "$driver" = "docker" ]; then
export DEVSTACK_LOCAL_CONFIG+=$'\n'"ZUN_DRIVER=docker" 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" export DEVSTACK_LOCAL_CONFIG+=$'\n'"IP_VERSION=4"
fi 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 $BASE/new/devstack-gate/devstack-vm-gate.sh

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

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

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

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

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

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

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

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

@ -16,6 +16,7 @@
# NOTE(deva): import auth_token so we can override a config option # NOTE(deva): import auth_token so we can override a config option
from keystonemiddleware import auth_token # noqa from keystonemiddleware import auth_token # noqa
from oslo_config import cfg
import pecan import pecan
import pecan.testing import pecan.testing
from six.moves.urllib import parse as urlparse from six.moves.urllib import parse as urlparse
@ -37,6 +38,10 @@ class FunctionalTest(base.DbTestCase):
def setUp(self): def setUp(self):
super(FunctionalTest, self).setUp() 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", zun.conf.CONF.set_override("auth_version", "v2.0",
group='keystone_authtoken', group='keystone_authtoken',
enforce_type=True) enforce_type=True)

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

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

@ -10,8 +10,9 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import zun.conf from oslo_config import cfg
import zun.conf
from zun.db import api as db_api from zun.db import api as db_api
from zun.db.sqlalchemy import api as sqla_api from zun.db.sqlalchemy import api as sqla_api
from zun.db.sqlalchemy import migration from zun.db.sqlalchemy import migration
@ -26,7 +27,10 @@ _DB_CACHE = None
class DriverTestCase(base.TestCase): class DriverTestCase(base.TestCase):
def setUp(self): def setUp(self):
super(DriverTestCase, self).setUp() 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() self.dbapi = db_api.get_instance()
global _DB_CACHE global _DB_CACHE

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

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

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

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

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