From a33fbbab05c830eb17ebde1f99e8801ed94c8d2a Mon Sep 17 00:00:00 2001 From: Chaolei Li Date: Mon, 4 Dec 2017 15:29:07 +0800 Subject: [PATCH] Add container action object and sql db api Add container action object and sql db api for container actions. Change-Id: I62cd021d4fc4b69432cc67b3506b9b24a9e7c779 Implements: blueprint container-actions-api --- zun/common/context.py | 15 +- zun/common/exception.py | 9 + zun/db/api.py | 37 +++ zun/db/sqlalchemy/api.py | 107 +++++++++ zun/objects/__init__.py | 6 + zun/objects/container_action.py | 175 ++++++++++++++ zun/tests/unit/db/test_container_action.py | 221 ++++++++++++++++++ zun/tests/unit/db/utils.py | 31 +++ .../unit/objects/test_container_action.py | 128 ++++++++++ zun/tests/unit/objects/test_objects.py | 4 +- 10 files changed, 730 insertions(+), 3 deletions(-) create mode 100644 zun/objects/container_action.py create mode 100644 zun/tests/unit/db/test_container_action.py create mode 100644 zun/tests/unit/objects/test_container_action.py diff --git a/zun/common/context.py b/zun/common/context.py index f1d04a3b4..92eb34461 100644 --- a/zun/common/context.py +++ b/zun/common/context.py @@ -13,6 +13,8 @@ import copy from eventlet.green import threading from oslo_context import context +from oslo_utils import timeutils +import six from zun.common import exception from zun.common import policy @@ -27,7 +29,7 @@ class RequestContext(context.RequestContext): project_name=None, project_id=None, roles=None, is_admin=None, read_only=False, show_deleted=False, request_id=None, trust_id=None, auth_token_info=None, - all_tenants=False, password=None, **kwargs): + all_tenants=False, password=None, timestamp=None, **kwargs): """Stores several additional request parameters: :param domain_id: The ID of the domain. @@ -65,6 +67,12 @@ class RequestContext(context.RequestContext): else: self.is_admin = is_admin + if not timestamp: + timestamp = timeutils.utcnow() + if isinstance(timestamp, six.string_types): + timestamp = timeutils.parse_strtime(timestamp) + self.timestamp = timestamp + def to_dict(self): value = super(RequestContext, self).to_dict() value.update({'auth_token': self.auth_token, @@ -85,7 +93,10 @@ class RequestContext(context.RequestContext): 'trust_id': self.trust_id, 'auth_token_info': self.auth_token_info, 'password': self.password, - 'all_tenants': self.all_tenants}) + 'all_tenants': self.all_tenants, + 'timestamp': timeutils.strtime(self.timestamp) if + hasattr(self, 'timestamp') else None + }) return value def to_policy_values(self): diff --git a/zun/common/exception.py b/zun/common/exception.py index 266eb554f..dd9f8477f 100644 --- a/zun/common/exception.py +++ b/zun/common/exception.py @@ -655,3 +655,12 @@ class VolumeCreateFailed(Invalid): class VolumeDeleteFailed(Invalid): message = _("Volume Deletion failed: %(deletion_failed)s") + + +class ContainerActionNotFound(ZunException): + message = _("Action for request_id %(request_id)s on container" + " %(container_uuid)s not fount") + + +class ContainerActionEventNotFound(ZunException): + message = _("Event %(event)s not found for action id %(action_id)s") diff --git a/zun/db/api.py b/zun/db/api.py index 06b95f356..f81626c2d 100644 --- a/zun/db/api.py +++ b/zun/db/api.py @@ -857,3 +857,40 @@ def destroy_pci_device(node_id, address): def update_pci_device(node_id, address, value): """Update a pci device.""" return _get_dbdriver_instance().update_pci_device(node_id, address, value) + + +@profiler.trace("db") +def action_start(context, values): + """Start an action for an container.""" + return _get_dbdriver_instance().action_start(context, values) + + +@profiler.trace("db") +def actions_get(context, uuid): + """Get all container actions for the provided container.""" + return _get_dbdriver_instance().actions_get(context, uuid) + + +@profiler.trace("db") +def action_get_by_request_id(context, uuid, request_id): + """Get the action by request_id and given container.""" + return _get_dbdriver_instance().action_get_by_request_id(context, uuid, + request_id) + + +@profiler.trace("db") +def action_event_start(context, values): + """Start an event on an container action.""" + return _get_dbdriver_instance().action_event_start(context, values) + + +@profiler.trace("db") +def action_event_finish(context, values): + """Finish an event on an container action.""" + return _get_dbdriver_instance().action_event_finish(context, values) + + +@profiler.trace("db") +def action_events_get(context, action_id): + """Get the events by action id.""" + return _get_dbdriver_instance().action_events_get(context, action_id) diff --git a/zun/db/sqlalchemy/api.py b/zun/db/sqlalchemy/api.py index 10a72b528..9b4573def 100644 --- a/zun/db/sqlalchemy/api.py +++ b/zun/db/sqlalchemy/api.py @@ -25,6 +25,7 @@ import sqlalchemy as sa from sqlalchemy.orm import contains_eager from sqlalchemy.orm.exc import MultipleResultsFound from sqlalchemy.orm.exc import NoResultFound +from sqlalchemy.sql.expression import desc from sqlalchemy.sql import func from zun.common import consts @@ -960,3 +961,109 @@ class Connection(object): device.update(values) device.save() return query.one() + + def action_start(self, context, values): + action = models.ContainerAction() + action.update(values) + action.save() + return action + + def actions_get(self, context, container_uuid): + """Get all container actions for the provided uuid.""" + query = model_query(models.ContainerAction).\ + filter_by(container_uuid=container_uuid) + actions = _paginate_query(models.ContainerAction, sort_dir='desc', + sort_key='created_at', query=query) + + return actions + + def action_get_by_request_id(self, context, container_uuid, request_id): + """Get the action by request_id and given container.""" + action = self._action_get_by_request_id(context, container_uuid, + request_id) + return action + + def _action_get_by_request_id(self, context, container_uuid, request_id): + result = model_query(models.ContainerAction).\ + filter_by(container_uuid=container_uuid).\ + filter_by(request_id=request_id).\ + first() + return result + + def _action_get_last_created_by_container_uuid(self, context, + container_uuid): + result = model_query(models.ContainerAction).\ + filter_by(container_uuid=container_uuid).\ + order_by(desc("created_at"), desc("id")).\ + first() + return result + + def action_event_start(self, context, values): + """Start an event on a container action.""" + action = self._action_get_by_request_id(context, + values['container_uuid'], + values['request_id']) + + # When zun-compute restarts, the request_id was different with + # request_id recorded in ContainerAction, so we can't get the original + # recode according to request_id. Try to get the last created action + # so that init_container can continue to finish the recovery action. + if not action and not context.project_id: + action = self._action_get_last_created_by_container_uuid( + context, values['container_uuid']) + + if not action: + raise exception.ContainerActionNotFound( + request_id=values['request_id'], + container_uuid=values['container_uuid']) + + values['action_id'] = action['id'] + + event = models.ContainerActionEvent() + event.update(values) + event.save() + + return event + + def action_event_finish(self, context, values): + """Finish an event on a container action.""" + action = self._action_get_by_request_id(context, + values['container_uuid'], + values['request_id']) + + # When zun-compute restarts, the request_id was different with + # request_id recorded in ContainerAction, so we can't get the original + # recode according to request_id. Try to get the last created action + # so that init_container can continue to finish the recovery action. + if not action and not context.project_id: + action = self._action_get_last_created_by_container_uuid( + context, values['container_uuid']) + + if not action: + raise exception.ContainerActionNotFound( + request_id=values['request_id'], + container_uuid=values['container_uuid']) + event = model_query(models.ContainerActionEvent).\ + filter_by(action_id=action['id']).\ + filter_by(event=values['event']).\ + first() + + if not event: + raise exception.ContainerActionEventNotFound( + action_id=action['id'], event=values['event']) + + event.update(values) + event.save() + + if values['result'].lower() == 'error': + action.update({'message': 'Error'}) + action.save() + + return event + + def action_events_get(self, context, action_id): + query = model_query(models.ContainerActionEvent).\ + filter_by(action_id=action_id) + events = _paginate_query(models.ContainerActionEvent, sort_dir='desc', + sort_key='created_at', query=query) + return events diff --git a/zun/objects/__init__.py b/zun/objects/__init__.py index 2c5fcea47..242c7cf31 100644 --- a/zun/objects/__init__.py +++ b/zun/objects/__init__.py @@ -13,6 +13,7 @@ from zun.objects import capsule from zun.objects import compute_node from zun.objects import container +from zun.objects import container_action from zun.objects import container_pci_requests from zun.objects import image from zun.objects import numa @@ -23,6 +24,7 @@ from zun.objects import resource_provider from zun.objects import volume_mapping from zun.objects import zun_service + Container = container.Container VolumeMapping = volume_mapping.VolumeMapping ZunService = zun_service.ZunService @@ -37,6 +39,8 @@ PciDevice = pci_device.PciDevice PciDevicePool = pci_device_pool.PciDevicePool ContainerPCIRequest = container_pci_requests.ContainerPCIRequest ContainerPCIRequests = container_pci_requests.ContainerPCIRequests +ContainerAction = container_action.ContainerAction +ContainerActionEvent = container_action.ContainerActionEvent __all__ = ( Container, @@ -53,4 +57,6 @@ __all__ = ( PciDevicePool, ContainerPCIRequest, ContainerPCIRequests, + ContainerAction, + ContainerActionEvent, ) diff --git a/zun/objects/container_action.py b/zun/objects/container_action.py new file mode 100644 index 000000000..dfc69f75f --- /dev/null +++ b/zun/objects/container_action.py @@ -0,0 +1,175 @@ +# 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 traceback + +from oslo_log import log as logging +from oslo_utils import timeutils +from oslo_versionedobjects import fields +import six + +from zun.db import api as dbapi +from zun.objects import base + +LOG = logging.getLogger(__name__) + + +@base.ZunObjectRegistry.register +class ContainerAction(base.ZunPersistentObject, base.ZunObject): + + # Version 1.0: Initial version + VERSION = '1.0' + + fields = { + 'id': fields.IntegerField(), + 'action': fields.StringField(nullable=True), + 'container_uuid': fields.UUIDField(nullable=True), + 'request_id': fields.StringField(nullable=True), + 'user_id': fields.StringField(nullable=True), + 'project_id': fields.StringField(nullable=True), + 'start_time': fields.DateTimeField(nullable=True), + 'finish_time': fields.DateTimeField(nullable=True), + 'message': fields.StringField(nullable=True), + } + + @staticmethod + def _from_db_object(context, action, db_action): + for field in action.fields: + setattr(action, field, db_action[field]) + + action.obj_reset_changes() + return action + + @staticmethod + def _from_db_object_list(context, cls, db_objects): + """Converts a list of database entities to a list of formal objects.""" + return [ContainerAction._from_db_object(context, cls(context), obj) + for obj in db_objects] + + @staticmethod + def pack_action_start(context, container_uuid, action_name): + values = {'request_id': context.request_id, + 'container_uuid': container_uuid, + 'user_id': context.user_id, + 'project_id': context.project_id, + 'action': action_name, + 'start_time': context.timestamp} + return values + + @staticmethod + def pack_action_finish(context, container_uuid): + values = {'request_id': context.request_id, + 'container_uuid': container_uuid, + 'finish_time': timeutils.utcnow()} + return values + + @base.remotable_classmethod + def get_by_request_id(cls, context, container_uuid, request_id): + db_action = dbapi.action_get_by_request_id(context, container_uuid, + request_id) + if db_action: + return cls._from_db_object(context, cls(context), db_action) + + @base.remotable_classmethod + def action_start(cls, context, container_uuid, action_name, + want_result=True): + values = cls.pack_action_start(context, container_uuid, action_name) + db_action = dbapi.action_start(context, values) + if want_result: + return cls._from_db_object(context, cls(context), db_action) + + @base.remotable_classmethod + def get_by_container_uuid(cls, context, instance_uuid): + db_actions = dbapi.actions_get(context, instance_uuid) + return ContainerAction._from_db_object_list(context, cls, db_actions) + + +@base.ZunObjectRegistry.register +class ContainerActionEvent(base.ZunPersistentObject, base.ZunObject): + # Version 1.0: Initial version + VERSION = '1.0' + fields = { + 'id': fields.IntegerField(), + 'event': fields.StringField(nullable=True), + 'action_id': fields.IntegerField(nullable=True), + 'start_time': fields.DateTimeField(nullable=True), + 'finish_time': fields.DateTimeField(nullable=True), + 'result': fields.StringField(nullable=True), + 'traceback': fields.StringField(nullable=True), + } + + @staticmethod + def _from_db_object(context, event, db_event): + for field in event.fields: + setattr(event, field, db_event[field]) + + event.obj_reset_changes() + return event + + @staticmethod + def _from_db_object_list(context, cls, db_objects): + """Converts a list of database entities to a list of formal objects.""" + return [ContainerActionEvent._from_db_object(context, cls(context), + obj) + for obj in db_objects] + + @staticmethod + def pack_action_event_start(context, container_uuid, event_name): + values = {'event': event_name, + 'container_uuid': container_uuid, + 'request_id': context.request_id, + 'start_time': timeutils.utcnow()} + return values + + @staticmethod + def pack_action_event_finish(context, container_uuid, event_name, + exc_val=None, exc_tb=None): + values = {'event': event_name, + 'container_uuid': container_uuid, + 'request_id': context.request_id, + 'finish_time': timeutils.utcnow()} + if exc_tb is None: + values['result'] = 'Success' + else: + values['result'] = 'Error' + values['message'] = exc_val + values['traceback'] = exc_tb + return values + + @base.remotable_classmethod + def event_start(cls, context, container_uuid, event_name, + want_result=True): + values = cls.pack_action_event_start(context, container_uuid, + event_name) + db_event = dbapi.action_event_start(context, values) + if want_result: + return cls._from_db_object(context, cls(context), db_event) + + @base.remotable_classmethod + def event_finish(cls, context, container_uuid, event_name, exc_val=None, + exc_tb=None, want_result=None): + if exc_val: + exc_val = six.text_type(exc_val) + if exc_tb and not isinstance(exc_tb, six.string_types): + exc_tb = ''.join(traceback.format_tb(exc_tb)) + values = cls.pack_action_event_finish(context, container_uuid, + event_name, exc_val=exc_val, + exc_tb=exc_tb) + db_event = dbapi.action_event_finish(context, values) + if want_result: + return cls._from_db_object(context, cls(context), db_event) + + @base.remotable_classmethod + def get_by_action(cls, context, action_id): + db_events = dbapi.action_events_get(context, action_id) + return ContainerActionEvent._from_db_object_list(context, cls, + db_events) diff --git a/zun/tests/unit/db/test_container_action.py b/zun/tests/unit/db/test_container_action.py new file mode 100644 index 000000000..8052ecbde --- /dev/null +++ b/zun/tests/unit/db/test_container_action.py @@ -0,0 +1,221 @@ +# 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. + +"""Tests for manipulating Container Actions via the DB API""" +import datetime + +from oslo_config import cfg +from oslo_utils import timeutils +from oslo_utils import uuidutils + +from zun.common import exception +import zun.conf +from zun.db import api as dbapi +from zun.tests.unit.db import base +from zun.tests.unit.db import utils + +CONF = zun.conf.CONF + + +class DbContainerActionTestCase(base.DbTestCase, + base.ModelsObjectComparatorMixin): + IGNORED_FIELDS = [ + 'id', + 'created_at', + 'updated_at', + ] + + def setUp(self): + cfg.CONF.set_override('db_type', 'sql') + super(DbContainerActionTestCase, self).setUp() + + def _create_action_values(self, uuid, action='create_container'): + + utils.create_test_container(context=self.context, + name='cont1', + uuid=uuid) + + values = { + 'action': action, + 'container_uuid': uuid, + 'request_id': self.context.request_id, + 'user_id': self.context.user_id, + 'project_id': self.context.project_id, + 'start_time': timeutils.utcnow(), + 'message': 'action-message' + } + return values + + def _create_event_values(self, uuid, event='do_create', extra=None): + values = { + 'event': event, + 'container_uuid': uuid, + 'request_id': self.context.request_id, + 'start_time': timeutils.utcnow(), + 'details': 'fake-details', + } + if extra is not None: + values.update(extra) + return values + + def _assertActionSaved(self, action, uuid): + """Retrieve the action to ensure it was successfully added.""" + actions = dbapi.actions_get(self.context, uuid) + self.assertEqual(1, len(actions)) + self._assertEqualObjects(action, actions[0]) + + def _assertActionEventSaved(self, event, action_id): + """Retrieve the event to ensure it was successfully added.""" + events = dbapi.action_events_get(self.context, action_id) + self.assertEqual(1, len(events)) + self._assertEqualObjects(event, events[0], + ['container_uuid', 'request_id']) + + def test_container_action_start(self): + """Create a container action.""" + uuid = uuidutils.generate_uuid() + action_values = self._create_action_values(uuid) + action = dbapi.action_start(self.context, action_values) + + ignored_keys = self.IGNORED_FIELDS + ['finish_time'] + self._assertEqualObjects(action_values, action, ignored_keys) + + self._assertActionSaved(action, uuid) + + def test_container_actions_get_by_container(self): + """Ensure we can get actions by UUID.""" + uuid1 = uuidutils.generate_uuid() + + expected = [] + + action_values = self._create_action_values(uuid1) + action = dbapi.action_start(self.context, action_values) + expected.append(action) + + action_values['action'] = 'test-action' + action = dbapi.action_start(self.context, action_values) + expected.append(action) + + # Create an other container action. + uuid2 = uuidutils.generate_uuid() + action_values = self._create_action_values(uuid2, 'test-action') + dbapi.action_start(self.context, action_values) + + actions = dbapi.actions_get(self.context, uuid1) + self._assertEqualListsOfObjects(expected, actions) + + def test_container_action_get_by_container_and_request(self): + """Ensure we can get an action by container UUID and request_id""" + uuid1 = uuidutils.generate_uuid() + + action_values = self._create_action_values(uuid1) + dbapi.action_start(self.context, action_values) + request_id = action_values['request_id'] + + # An other action using a different req id + action_values['action'] = 'test-action' + action_values['request_id'] = 'req-00000000-7522-4d99-7ff-111111111111' + dbapi.action_start(self.context, action_values) + + action = dbapi.action_get_by_request_id(self.context, uuid1, + request_id) + self.assertEqual('create_container', action['action']) + self.assertEqual(self.context.request_id, action['request_id']) + + def test_container_action_event_start(self): + """Create a container action event.""" + uuid = uuidutils.generate_uuid() + + action_values = self._create_action_values(uuid) + action = dbapi.action_start(self.context, action_values) + + event_values = self._create_event_values(uuid) + event = dbapi.action_event_start(self.context, event_values) + + event_values['action_id'] = action['id'] + ignored_keys = self.IGNORED_FIELDS + ['finish_time', 'traceback', + 'result'] + self._assertEqualObjects(event_values, event, ignored_keys) + + self._assertActionEventSaved(event, action['id']) + + def test_container_action_event_start_without_action(self): + uuid = uuidutils.generate_uuid() + + event_values = self._create_event_values(uuid) + self.assertRaises(exception.ContainerActionNotFound, + dbapi.action_event_start, self.context, event_values) + + def test_container_action_event_finish_success(self): + """Finish a container action event.""" + uuid = uuidutils.generate_uuid() + + action = dbapi.action_start(self.context, + self._create_action_values(uuid)) + + dbapi.action_event_start(self.context, + self._create_event_values(uuid)) + + event_values = { + 'finish_time': timeutils.utcnow() + datetime.timedelta(seconds=5), + 'result': 'Success' + } + + event_values = self._create_event_values(uuid, extra=event_values) + event = dbapi.action_event_finish(self.context, event_values) + + self._assertActionEventSaved(event, action['id']) + action = dbapi.action_get_by_request_id(self.context, uuid, + self.context.request_id) + self.assertNotEqual('Error', action['message']) + + def test_container_action_event_finish_without_action(self): + uuid = uuidutils.generate_uuid() + + event_values = { + 'finish_time': timeutils.utcnow() + datetime.timedelta(seconds=5), + 'result': 'Success' + } + event_values = self._create_event_values(uuid, extra=event_values) + self.assertRaises(exception.ContainerActionNotFound, + dbapi.action_event_finish, + self.context, event_values) + + def test_container_action_events_get_in_order(self): + """Ensure retrived action events are in order.""" + uuid1 = uuidutils.generate_uuid() + + action = dbapi.action_start(self.context, + self._create_action_values(uuid1)) + + extra1 = { + 'created_at': timeutils.utcnow() + } + + extra2 = { + 'created_at': timeutils.utcnow() + datetime.timedelta(seconds=5) + } + + event_val1 = self._create_event_values(uuid1, 'fake1', extra=extra1) + event_val2 = self._create_event_values(uuid1, 'fake2', extra=extra1) + event_val3 = self._create_event_values(uuid1, 'fake3', extra=extra2) + + event1 = dbapi.action_event_start(self.context, event_val1) + event2 = dbapi.action_event_start(self.context, event_val2) + event3 = dbapi.action_event_start(self.context, event_val3) + + events = dbapi.action_events_get(self.context, action['id']) + + self.assertEqual(3, len(events)) + + self._assertEqualOrderedListOfObjects([event3, event2, event1], events, + ['container_uuid', 'request_id']) diff --git a/zun/tests/unit/db/utils.py b/zun/tests/unit/db/utils.py index 71bae5708..53a5f65dc 100644 --- a/zun/tests/unit/db/utils.py +++ b/zun/tests/unit/db/utils.py @@ -395,3 +395,34 @@ def create_test_capsule(**kwargs): del capsule['id'] dbapi = db_api._get_dbdriver_instance() return dbapi.create_capsule(kwargs['context'], capsule) + + +def get_test_action(**kwargs): + return { + 'created_at': kwargs.get('created_at'), + 'updated_at': kwargs.get('updated_at'), + 'id': kwargs.get('id', 123), + 'action': kwargs.get('action', 'fake-action'), + 'container_uuid': kwargs.get('container_uuid', + 'ea8e2a25-2901-438d-8157-de7ffd68d051'), + 'request_id': kwargs.get('request_id', 'fake-request'), + 'user_id': kwargs.get('user_id', 'fake-user'), + 'project_id': kwargs.get('project_id', 'fake-project'), + 'start_time': kwargs.get('start_time'), + 'finish_time': kwargs.get('finish_time'), + 'message': kwargs.get('message', 'fake-message'), + } + + +def get_test_action_event(**kwargs): + return { + 'created_at': kwargs.get('created_at'), + 'updated_at': kwargs.get('updated_at'), + 'id': kwargs.get('id', 123), + 'event': kwargs.get('event', 'fake-event'), + 'action_id': kwargs.get('action_id', 123), + 'start_time': kwargs.get('start_time'), + 'finish_time': kwargs.get('finish_time'), + 'result': kwargs.get('result', 'Error'), + 'traceback': kwargs.get('traceback', 'fake-tb'), + } diff --git a/zun/tests/unit/objects/test_container_action.py b/zun/tests/unit/objects/test_container_action.py new file mode 100644 index 000000000..a7a966978 --- /dev/null +++ b/zun/tests/unit/objects/test_container_action.py @@ -0,0 +1,128 @@ +# Copyright 2015 OpenStack Foundation +# All Rights Reserved. +# +# 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 mock + +from oslo_utils import fixture as utils_fixture +from oslo_utils import timeutils + +from testtools.matchers import HasLength + +from zun import objects +from zun.tests.unit.db import base +from zun.tests.unit.db import utils + +NOW = timeutils.utcnow().replace(microsecond=0) + + +class TestContainerActionObject(base.DbTestCase): + + def setUp(self): + super(TestContainerActionObject, self).setUp() + self.fake_action = utils.get_test_action() + + def test_get_by_request_id(self): + container_ident = self.fake_action['container_uuid'] + request_id = self.fake_action['request_id'] + with mock.patch.object(self.dbapi, 'action_get_by_request_id', + autospec=True) as mock_get_action: + mock_get_action.return_value = self.fake_action + action = objects.ContainerAction.get_by_request_id( + self.context, container_ident, request_id) + mock_get_action.assert_called_once_with( + self.context, container_ident, request_id) + self.assertEqual(self.context, action._context) + + def test_get_by_container_uuid(self): + container_ident = self.fake_action['container_uuid'] + with mock.patch.object(self.dbapi, 'actions_get', autospec=True) \ + as mock_get_actions: + mock_get_actions.return_value = [self.fake_action] + actions = objects.ContainerAction.get_by_container_uuid( + self.context, container_ident) + mock_get_actions.assert_called_once_with(self.context, + container_ident) + + self.assertThat(actions, HasLength(1)) + self.assertIsInstance(actions[0], objects.ContainerAction) + self.assertEqual(self.context, actions[0]._context) + + def test_action_start(self): + self.useFixture(utils_fixture.TimeFixture(NOW)) + container_ident = self.fake_action['container_uuid'] + action_name = self.fake_action['action'] + test_class = objects.ContainerAction + expected_packed_values = test_class.pack_action_start( + self.context, container_ident, action_name) + with mock.patch.object(self.dbapi, 'action_start', autospec=True) \ + as mock_action_start: + mock_action_start.return_value = self.fake_action + action = objects.ContainerAction.action_start( + self.context, container_ident, action_name, want_result=True) + mock_action_start.assert_called_once_with( + self.context, expected_packed_values) + self.assertEqual(self.context, action._context) + + +class TestContainerActionEventObject(base.DbTestCase): + + def setUp(self): + super(TestContainerActionEventObject, self).setUp() + self.fake_action = utils.get_test_action() + self.fake_event = utils.get_test_action_event() + + def test_get_by_action(self): + action_id = self.fake_event['action_id'] + with mock.patch.object(self.dbapi, 'action_events_get', + autospec=True) as mock_get_event: + mock_get_event.return_value = [self.fake_event] + events = objects.ContainerActionEvent.get_by_action(self.context, + action_id) + mock_get_event.assert_called_once_with(self.context, action_id) + self.assertThat(events, HasLength(1)) + self.assertIsInstance(events[0], objects.ContainerActionEvent) + self.assertEqual(self.context, events[0]._context) + + def test_event_start(self): + self.useFixture(utils_fixture.TimeFixture(NOW)) + container_uuid = self.fake_action['container_uuid'] + event_name = self.fake_event['event'] + test_class = objects.ContainerActionEvent + expected_packed_values = test_class.pack_action_event_start( + self.context, container_uuid, event_name) + with mock.patch.object(self.dbapi, 'action_event_start', + autospec=True) as mock_event_start: + mock_event_start.return_value = self.fake_event + event = objects.ContainerActionEvent.event_start( + self.context, container_uuid, event_name, want_result=True) + mock_event_start.assert_called_once_with(self.context, + expected_packed_values) + self.assertEqual(self.context, event._context) + + def test_event_finish(self): + self.useFixture(utils_fixture.TimeFixture(NOW)) + container_uuid = self.fake_action['container_uuid'] + event_name = self.fake_event['event'] + test_class = objects.ContainerActionEvent + expected_packed_values = test_class.pack_action_event_finish( + self.context, container_uuid, event_name) + with mock.patch.object(self.dbapi, 'action_event_finish', + autospec=True) as mock_event_finish: + mock_event_finish.return_value = self.fake_event + event = objects.ContainerActionEvent.event_finish( + self.context, container_uuid, event_name, want_result=True) + mock_event_finish.assert_called_once_with(self.context, + expected_packed_values) + self.assertEqual(self.context, event._context) diff --git a/zun/tests/unit/objects/test_objects.py b/zun/tests/unit/objects/test_objects.py index 9c106eef3..8d4fcc96b 100644 --- a/zun/tests/unit/objects/test_objects.py +++ b/zun/tests/unit/objects/test_objects.py @@ -359,7 +359,9 @@ object_data = { 'PciDevicePool': '1.0-3f5ddc3ff7bfa14da7f6c7e9904cc000', 'PciDevicePoolList': '1.0-15ecf022a68ddbb8c2a6739cfc9f8f5e', 'ContainerPCIRequest': '1.0-b060f9f9f734bedde79a71a4d3112ee0', - 'ContainerPCIRequests': '1.0-7b8f7f044661fe4e24e6949c035af2c4' + 'ContainerPCIRequests': '1.0-7b8f7f044661fe4e24e6949c035af2c4', + 'ContainerAction': '1.0-8d6facdc65855c6c6afbed8531209279', + 'ContainerActionEvent': '1.0-2974d0a6f5d4821fd4e223a88c10181a' }