diff --git a/zun/common/utils.py b/zun/common/utils.py index db7daeb96..36bda3d43 100644 --- a/zun/common/utils.py +++ b/zun/common/utils.py @@ -18,6 +18,7 @@ """Utilities and helper functions.""" import eventlet import functools +import inspect import mimetypes from oslo_concurrency import lockutils @@ -36,6 +37,7 @@ from zun.common.i18n import _ from zun.common import privileged import zun.conf from zun.network import neutron +from zun import objects CONF = zun.conf.CONF LOG = logging.getLogger(__name__) @@ -506,3 +508,71 @@ def check_external_network_attach(context, nets): if net.get('router:external') and not net.get('shared'): raise exception.ExternalNetworkAttachForbidden( network_uuid=net['network']) + + +class EventReporter(object): + """Context manager to report container action events.""" + + def __init__(self, context, event_name, *container_uuids): + self.context = context + self.event_name = event_name + self.container_uuids = container_uuids + + def __enter__(self): + for uuid in self.container_uuids: + objects.ContainerActionEvent.event_start( + self.context, uuid, self.event_name, want_result=False) + + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + for uuid in self.container_uuids: + objects.ContainerActionEvent.event_finish( + self.context, uuid, self.event_name, exc_val=exc_val, + exc_tb=exc_tb, want_result=False) + return False + + +def get_wrapped_function(function): + """Get the method at the bottom of a stack of decorators.""" + if not hasattr(function, '__closure__') or not function.__closure__: + return function + + def _get_wrapped_function(function): + if not hasattr(function, '__closure__') or not function.__closure__: + return None + + for closure in function.__closure__: + func = closure.cell_contents + + deeper_func = _get_wrapped_function(func) + if deeper_func: + return deeper_func + elif hasattr(closure.cell_contents, '__call__'): + return closure.cell_contents + + return function + + return _get_wrapped_function(function) + + +def wrap_container_event(prefix): + """Warps a method to log the event taken on the container, and result. + + This decorator wraps a method to log the start and result of an event, as + part of an action taken on a container. + """ + def helper(function): + + @functools.wraps(function) + def decorated_function(self, context, *args, **kwargs): + wrapped_func = get_wrapped_function(function) + keyed_args = inspect.getcallargs(wrapped_func, self, context, + *args, **kwargs) + container_uuid = keyed_args['container'].uuid + + event_name = '{0}_{1}'.format(prefix, function.__name__) + with EventReporter(context, event_name, container_uuid): + return function(self, context, *args, **kwargs) + return decorated_function + return helper diff --git a/zun/compute/api.py b/zun/compute/api.py index d57581b81..ea1f42ec1 100644 --- a/zun/compute/api.py +++ b/zun/compute/api.py @@ -16,8 +16,10 @@ networking and storage of containers, and compute hosts on which they run).""" from zun.common import consts from zun.common import exception from zun.common import profiler +from zun.compute import container_actions from zun.compute import rpcapi import zun.conf +from zun import objects from zun.scheduler import client as scheduler_client CONF = zun.conf.CONF @@ -32,6 +34,10 @@ class API(object): self.scheduler_client = scheduler_client.SchedulerClient() super(API, self).__init__() + def _record_action_start(self, context, container, action): + objects.ContainerAction.action_start(context, container.uuid, + action, want_result=False) + def container_create(self, context, new_container, extra_spec, requested_networks, requested_volumes, run, pci_requests=None): @@ -55,6 +61,8 @@ class API(object): if not images: raise exception.ImageNotFound(image=new_container.image) + self._record_action_start(context, new_container, + container_actions.CREATE) self.rpcapi.container_create(context, host_state['host'], new_container, host_state['limits'], requested_networks, requested_volumes, @@ -67,24 +75,31 @@ class API(object): return dests[0] def container_delete(self, context, container, *args): + self._record_action_start(context, container, container_actions.DELETE) return self.rpcapi.container_delete(context, container, *args) def container_show(self, context, container, *args): return self.rpcapi.container_show(context, container, *args) def container_reboot(self, context, container, *args): + self._record_action_start(context, container, container_actions.REBOOT) return self.rpcapi.container_reboot(context, container, *args) def container_stop(self, context, container, *args): + self._record_action_start(context, container, container_actions.STOP) return self.rpcapi.container_stop(context, container, *args) def container_start(self, context, container): + self._record_action_start(context, container, container_actions.START) return self.rpcapi.container_start(context, container) def container_pause(self, context, container): + self._record_action_start(context, container, container_actions.PAUSE) return self.rpcapi.container_pause(context, container) def container_unpause(self, context, container): + self._record_action_start(context, container, + container_actions.UNPAUSE) return self.rpcapi.container_unpause(context, container) def container_logs(self, context, container, stdout, stderr, @@ -99,6 +114,7 @@ class API(object): return self.rpcapi.container_exec_resize(context, container, *args) def container_kill(self, context, container, *args): + self._record_action_start(context, container, container_actions.KILL) return self.rpcapi.container_kill(context, container, *args) def container_update(self, context, container, *args): @@ -117,9 +133,13 @@ class API(object): return self.rpcapi.container_get_archive(context, container, *args) def add_security_group(self, context, container, *args): + self._record_action_start(context, container, + container_actions.ADD_SECURITY_GROUP) return self.rpcapi.add_security_group(context, container, *args) def remove_security_group(self, context, container, *args): + self._record_action_start(context, container, + container_actions.REMOVE_SECURITY_GROUP) return self.rpcapi.remove_security_group(context, container, *args) def container_put_archive(self, context, container, *args): @@ -129,6 +149,7 @@ class API(object): return self.rpcapi.container_stats(context, container) def container_commit(self, context, container, *args): + self._record_action_start(context, container, container_actions.COMMIT) return self.rpcapi.container_commit(context, container, *args) def image_pull(self, context, image): @@ -157,7 +178,11 @@ class API(object): return self.rpcapi.capsule_delete(context, capsule, *args) def network_detach(self, context, container, *args): + self._record_action_start(context, container, + container_actions.NETWORK_DETACH) return self.rpcapi.network_detach(context, container, *args) def network_attach(self, context, container, *args): + self._record_action_start(context, container, + container_actions.NETWORK_ATTACH) return self.rpcapi.network_attach(context, container, *args) diff --git a/zun/compute/container_actions.py b/zun/compute/container_actions.py new file mode 100644 index 000000000..37580f9f1 --- /dev/null +++ b/zun/compute/container_actions.py @@ -0,0 +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. + +"""Possible actions on an container. + +Action should probably match a user intention at the API level. Because they +can be user visible that should help to avoid confusion. For that reason they +tent to maintain the casing sent to the API. + +Maintaining a list of actions here should protect against inconsistencies when +they are used. +""" + +CREATE = 'create' +DELETE = 'delete' +REBOOT = 'reboot' +STOP = 'stop' +START = 'start' +PAUSE = 'pause' +UNPAUSE = 'unpause' +EXEC_CMD = 'exec_cmd' +KILL = 'kill' +UPDATE = 'update' +CONTAINER_ATTACH = 'container_attach' +RESIZE = 'resize' +ADD_SECURITY_GROUP = 'add_security_group' +REMOVE_SECURITY_GROUP = 'remove_security_group' +PUT_ARCHIVE = 'put_archive' +COMMIT = 'commit' +NETWORK_DETACH = 'network_detach' +NETWORK_ATTACH = 'network_attach' diff --git a/zun/compute/manager.py b/zun/compute/manager.py index 9f4ca3247..d685789f1 100644 --- a/zun/compute/manager.py +++ b/zun/compute/manager.py @@ -25,6 +25,7 @@ from zun.common import exception from zun.common.i18n import _ from zun.common import utils from zun.common.utils import translate_exception +from zun.common.utils import wrap_container_event from zun.compute import compute_node_tracker import zun.conf from zun.container import driver @@ -262,6 +263,7 @@ class Manager(periodic_task.PeriodicTasks): unset_host=True) return + @wrap_container_event(prefix='compute') def _do_container_create(self, context, container, requested_networks, requested_volumes, pci_requests=None, limits=None, reraise=False): @@ -378,6 +380,7 @@ class Manager(periodic_task.PeriodicTasks): six.text_type(e)) self._fail_container(context, container, six.text_type(e)) + @wrap_container_event(prefix='compute') def _do_container_start(self, context, container, reraise=False): LOG.debug('Starting container: %s', container.uuid) self._update_task_state(context, container, consts.CONTAINER_STARTING) @@ -451,6 +454,7 @@ class Manager(periodic_task.PeriodicTasks): utils.spawn_n(do_add_security_group) + @wrap_container_event(prefix='compute') def _add_security_group(self, context, container, security_group): LOG.debug('Adding security_group to container: %s', container.uuid) try: @@ -468,6 +472,7 @@ class Manager(periodic_task.PeriodicTasks): utils.spawn_n(do_remove_security_group) + @wrap_container_event(prefix='compute') def _remove_security_group(self, context, container, security_group): LOG.debug('Removing security_group from container: %s', container.uuid) try: @@ -510,6 +515,7 @@ class Manager(periodic_task.PeriodicTasks): LOG.exception("Unexpected exception: %s", six.text_type(e)) raise + @wrap_container_event(prefix='compute') def _do_container_reboot(self, context, container, timeout, reraise=False): LOG.debug('Rebooting container: %s', container.uuid) self._update_task_state(context, container, consts.CONTAINER_REBOOTING) @@ -535,6 +541,7 @@ class Manager(periodic_task.PeriodicTasks): utils.spawn_n(do_container_reboot) + @wrap_container_event(prefix='compute') def _do_container_stop(self, context, container, timeout, reraise=False): LOG.debug('Stopping container: %s', container.uuid) self._update_task_state(context, container, consts.CONTAINER_STOPPING) @@ -567,6 +574,7 @@ class Manager(periodic_task.PeriodicTasks): utils.spawn_n(do_container_start) + @wrap_container_event(prefix='compute') def _do_container_pause(self, context, container, reraise=False): LOG.debug('Pausing container: %s', container.uuid) try: @@ -591,6 +599,7 @@ class Manager(periodic_task.PeriodicTasks): utils.spawn_n(do_container_pause) + @wrap_container_event(prefix='compute') def _do_container_unpause(self, context, container, reraise=False): LOG.debug('Unpausing container: %s', container.uuid) try: @@ -667,6 +676,7 @@ class Manager(periodic_task.PeriodicTasks): LOG.exception("Unexpected exception: %s", six.text_type(e)) raise + @wrap_container_event(prefix='compute') def _do_container_kill(self, context, container, signal, reraise=False): LOG.debug('Killing a container: %s', container.uuid) try: @@ -824,6 +834,7 @@ class Manager(periodic_task.PeriodicTasks): self.driver.delete_image(container_image_id) raise + @wrap_container_event(prefix='compute') def _do_container_commit(self, context, snapshot_image, container, repository, tag=None): container_image_id = None @@ -1034,6 +1045,7 @@ class Manager(periodic_task.PeriodicTasks): capsule.save(context) capsule.destroy(context) + @wrap_container_event(prefix='compute') def network_detach(self, context, container, network): LOG.debug('Detach network: %(network)s from container: %(container)s.', {'container': container, 'network': network}) @@ -1043,6 +1055,7 @@ class Manager(periodic_task.PeriodicTasks): with excutils.save_and_reraise_exception(reraise=False): LOG.exception("Unexpected exception: %s", six.text_type(e)) + @wrap_container_event(prefix='compute') def network_attach(self, context, container, network): LOG.debug('Attach network: %(network)s to container: %(container)s.', {'container': container, 'network': network}) diff --git a/zun/objects/container_action.py b/zun/objects/container_action.py index 5931bb9a8..54abaa653 100644 --- a/zun/objects/container_action.py +++ b/zun/objects/container_action.py @@ -40,6 +40,8 @@ class ContainerAction(base.ZunPersistentObject, base.ZunObject): 'start_time': fields.DateTimeField(nullable=True), 'finish_time': fields.DateTimeField(nullable=True), 'message': fields.StringField(nullable=True), + # NOTE: By now, this field is only used for etcd. If using sql, + # this field will be None. 'uuid': fields.StringField(nullable=True), } diff --git a/zun/tests/unit/common/test_utils.py b/zun/tests/unit/common/test_utils.py index e40bc259f..9599678e5 100644 --- a/zun/tests/unit/common/test_utils.py +++ b/zun/tests/unit/common/test_utils.py @@ -215,3 +215,49 @@ class TestUtils(base.TestCase): mock.ANY, test_image['uuid']) self.assertEqual(test_image['uuid'], image.uuid) + + @mock.patch.object(objects.ContainerActionEvent, 'event_start') + @mock.patch.object(objects.ContainerActionEvent, 'event_finish') + def test_wart_container_event(self, mock_finish, mock_start): + container = Container(self.context, **db_utils.get_test_container()) + + @utils.wrap_container_event(prefix='compute') + def fake_event(self, context, container): + pass + + fake_event(self, self.context, container=container) + + self.assertTrue(mock_start.called) + self.assertTrue(mock_finish.called) + + @mock.patch.object(objects.ContainerActionEvent, 'event_start') + @mock.patch.object(objects.ContainerActionEvent, 'event_finish') + def test_wrap_container_event_return(self, mock_finish, mock_start): + container = Container(self.context, **db_utils.get_test_container()) + + @utils.wrap_container_event(prefix='compute') + def fake_event(self, context, container): + return True + + retval = fake_event(self, self.context, container=container) + + self.assertTrue(retval) + self.assertTrue(mock_start.called) + self.assertTrue(mock_finish.called) + + @mock.patch.object(objects.ContainerActionEvent, 'event_start') + @mock.patch.object(objects.ContainerActionEvent, 'event_finish') + def test_wrap_conatiner_event_log_exception(self, mock_finish, mock_start): + container = Container(self.context, **db_utils.get_test_container()) + + @utils.wrap_container_event(prefix='compute') + def fake_event(self, context, container): + raise exception.ZunException() + + self.assertRaises(exception.ZunException, fake_event, + self, self.context, container=container) + + self.assertTrue(mock_start.called) + self.assertTrue(mock_finish.called) + args, kwargs = mock_finish.call_args + self.assertIsInstance(kwargs['exc_val'], exception.ZunException) diff --git a/zun/tests/unit/compute/test_compute_manager.py b/zun/tests/unit/compute/test_compute_manager.py index 8084984e3..289d33dc8 100644 --- a/zun/tests/unit/compute/test_compute_manager.py +++ b/zun/tests/unit/compute/test_compute_manager.py @@ -22,6 +22,7 @@ from zun.compute import claims from zun.compute import manager import zun.conf from zun.objects.container import Container +from zun.objects.container_action import ContainerActionEvent from zun.objects.image import Image from zun.objects.volume_mapping import VolumeMapping from zun.tests import base @@ -183,10 +184,13 @@ class TestManager(base.TestCase): self.assertEqual("Creation Failed", container.status_reason) self.assertIsNone(container.task_state) + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(Container, 'save') @mock.patch('zun.image.driver.pull_image') @mock.patch.object(fake_driver, 'create') - def test_container_create(self, mock_create, mock_pull, mock_save): + def test_container_create(self, mock_create, mock_pull, mock_save, + mock_event_finish, mock_event_start): container = Container(self.context, **utils.get_test_container()) image = {'image': 'repo', 'path': 'out_path', 'driver': 'glance'} mock_pull.return_value = image, False @@ -201,11 +205,14 @@ class TestManager(base.TestCase): mock_create.assert_called_once_with(self.context, container, image, networks, volumes) + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(Container, 'save') @mock.patch('zun.image.driver.pull_image') @mock.patch.object(manager.Manager, '_fail_container') def test_container_create_pull_image_failed_docker_error( - self, mock_fail, mock_pull, mock_save): + self, mock_fail, mock_pull, mock_save, mock_event_finish, + mock_event_start): container = Container(self.context, **utils.get_test_container()) mock_pull.side_effect = exception.DockerError("Pull Failed") networks = [] @@ -215,11 +222,14 @@ class TestManager(base.TestCase): mock_fail.assert_called_once_with(self.context, container, "Pull Failed") + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(Container, 'save') @mock.patch('zun.image.driver.pull_image') @mock.patch.object(manager.Manager, '_fail_container') def test_container_create_pull_image_failed_image_not_found( - self, mock_fail, mock_pull, mock_save): + self, mock_fail, mock_pull, mock_save, mock_event_finish, + mock_event_start): container = Container(self.context, **utils.get_test_container()) mock_pull.side_effect = exception.ImageNotFound("Image Not Found") networks = [] @@ -229,11 +239,14 @@ class TestManager(base.TestCase): mock_fail.assert_called_once_with(self.context, container, "Image Not Found") + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(Container, 'save') @mock.patch('zun.image.driver.pull_image') @mock.patch.object(manager.Manager, '_fail_container') def test_container_create_pull_image_failed_zun_exception( - self, mock_fail, mock_pull, mock_save): + self, mock_fail, mock_pull, mock_save, mock_event_finish, + mock_event_start): container = Container(self.context, **utils.get_test_container()) mock_pull.side_effect = exception.ZunException( message="Image Not Found") @@ -244,13 +257,15 @@ class TestManager(base.TestCase): mock_fail.assert_called_once_with(self.context, container, "Image Not Found") + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(Container, 'save') @mock.patch('zun.image.driver.pull_image') @mock.patch.object(fake_driver, 'create') @mock.patch.object(manager.Manager, '_fail_container') - def test_container_create_docker_create_failed(self, mock_fail, - mock_create, mock_pull, - mock_save): + def test_container_create_docker_create_failed( + self, mock_fail, mock_create, mock_pull, mock_save, + mock_event_finish, mock_event_start): container = Container(self.context, **utils.get_test_container()) image = {'image': 'repo', 'path': 'out_path', 'driver': 'glance', 'repo': 'test', 'tag': 'testtag'} @@ -264,6 +279,8 @@ class TestManager(base.TestCase): mock_fail.assert_called_once_with( self.context, container, "Creation Failed", unset_host=True) + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch('zun.common.utils.spawn_n') @mock.patch.object(Container, 'save') @mock.patch.object(VolumeMapping, 'list_by_container', @@ -278,7 +295,7 @@ class TestManager(base.TestCase): self, mock_start, mock_create, mock_is_volume_available, mock_attach_volume, mock_detach_volume, mock_pull, mock_list_by_container, mock_save, - mock_spawn_n): + mock_spawn_n, mock_event_finish, mock_event_start): container = Container(self.context, **utils.get_test_container()) image = {'image': 'repo', 'path': 'out_path', 'driver': 'glance'} mock_create.return_value = container @@ -305,6 +322,8 @@ class TestManager(base.TestCase): mock_is_volume_available.assert_called_once() self.assertEqual(1, len(FakeVolumeMapping.volumes)) + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch('zun.common.utils.spawn_n') @mock.patch.object(Container, 'save') @mock.patch.object(VolumeMapping, 'list_by_container', @@ -319,7 +338,7 @@ class TestManager(base.TestCase): self, mock_start, mock_create, mock_is_volume_available, mock_attach_volume, mock_detach_volume, mock_pull, mock_list_by_container, mock_save, - mock_spawn_n): + mock_spawn_n, mock_event_finish, mock_event_start): mock_is_volume_available.return_value = True mock_attach_volume.side_effect = [None, base.TestingException("fake")] container = Container(self.context, **utils.get_test_container()) @@ -349,6 +368,8 @@ class TestManager(base.TestCase): mock.call(mock.ANY, vol)]) self.assertEqual(0, len(FakeVolumeMapping.volumes)) + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch('zun.common.utils.spawn_n') @mock.patch.object(Container, 'save') @mock.patch.object(VolumeMapping, 'list_by_container', @@ -360,7 +381,8 @@ class TestManager(base.TestCase): def test_container_run_image_not_found( self, mock_pull, mock_is_volume_available, mock_attach_volume, mock_detach_volume, - mock_list_by_container, mock_save, mock_spawn_n): + mock_list_by_container, mock_save, mock_spawn_n, mock_event_finish, + mock_event_start): container_dict = utils.get_test_container( image='test:latest', image_driver='docker', image_pull_policy='ifnotpresent') @@ -386,6 +408,8 @@ class TestManager(base.TestCase): mock_is_volume_available.assert_called_once() self.assertEqual(0, len(FakeVolumeMapping.volumes)) + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch('zun.common.utils.spawn_n') @mock.patch.object(Container, 'save') @mock.patch.object(VolumeMapping, 'list_by_container', @@ -397,7 +421,8 @@ class TestManager(base.TestCase): def test_container_run_image_pull_exception_raised( self, mock_pull, mock_is_volume_available, mock_attach_volume, mock_detach_volume, - mock_list_by_container, mock_save, mock_spawn_n): + mock_list_by_container, mock_save, mock_spawn_n, mock_event_finish, + mock_event_start): container_dict = utils.get_test_container( image='test:latest', image_driver='docker', image_pull_policy='ifnotpresent') @@ -423,6 +448,8 @@ class TestManager(base.TestCase): mock_is_volume_available.assert_called_once() self.assertEqual(0, len(FakeVolumeMapping.volumes)) + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch('zun.common.utils.spawn_n') @mock.patch.object(Container, 'save') @mock.patch.object(VolumeMapping, 'list_by_container', @@ -434,7 +461,8 @@ class TestManager(base.TestCase): def test_container_run_image_pull_docker_error( self, mock_pull, mock_is_volume_available, mock_attach_volume, mock_detach_volume, - mock_list_by_container, mock_save, mock_spawn_n): + mock_list_by_container, mock_save, mock_spawn_n, mock_event_finish, + mock_event_start): container_dict = utils.get_test_container( image='test:latest', image_driver='docker', image_pull_policy='ifnotpresent') @@ -460,6 +488,8 @@ class TestManager(base.TestCase): mock_is_volume_available.assert_called_once() self.assertEqual(0, len(FakeVolumeMapping.volumes)) + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch('zun.common.utils.spawn_n') @mock.patch.object(Container, 'save') @mock.patch.object(VolumeMapping, 'list_by_container', @@ -472,7 +502,8 @@ class TestManager(base.TestCase): def test_container_run_create_raises_docker_error( self, mock_create, mock_pull, mock_is_volume_available, mock_attach_volume, mock_detach_volume, - mock_list_by_container, mock_save, mock_spawn_n): + mock_list_by_container, mock_save, mock_spawn_n, + mock_event_finish, mock_event_start): container = Container(self.context, **utils.get_test_container()) image = {'image': 'repo', 'path': 'out_path', 'driver': 'glance', 'repo': 'test', 'tag': 'testtag'} @@ -501,6 +532,8 @@ class TestManager(base.TestCase): mock_is_volume_available.assert_called_once() self.assertEqual(0, len(FakeVolumeMapping.volumes)) + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(FakeResourceTracker, 'remove_usage_from_container') @mock.patch.object(Container, 'destroy') @@ -509,7 +542,8 @@ class TestManager(base.TestCase): @mock.patch.object(fake_driver, 'delete') def test_container_delete( self, mock_delete, mock_list_by_container, mock_save, - mock_cnt_destroy, mock_remove_usage): + mock_cnt_destroy, mock_remove_usage, mock_event_finish, + mock_event_start): mock_list_by_container.return_value = [] container = Container(self.context, **utils.get_test_container()) self.compute_manager._do_container_delete(self. context, container, @@ -520,6 +554,8 @@ class TestManager(base.TestCase): mock_remove_usage.assert_called_once_with(self.context, container, True) + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(FakeResourceTracker, 'remove_usage_from_container') @mock.patch.object(Container, 'destroy') @@ -528,7 +564,8 @@ class TestManager(base.TestCase): @mock.patch.object(fake_driver, 'delete') def test_container_delete_failed(self, mock_delete, mock_save, mock_fail, mock_destroy, - mock_remove_usage): + mock_remove_usage, mock_event_finish, + mock_event_start): container = Container(self.context, **utils.get_test_container()) mock_delete.side_effect = exception.DockerError( message="Docker Error occurred") @@ -541,6 +578,8 @@ class TestManager(base.TestCase): mock_destroy.assert_not_called() mock_remove_usage.assert_not_called() + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(FakeResourceTracker, 'remove_usage_from_container') @mock.patch.object(Container, 'destroy') @@ -552,7 +591,9 @@ class TestManager(base.TestCase): mock_list_by_container, mock_save, mock_fail, mock_destroy, - mock_remove_usage): + mock_remove_usage, + mock_event_finish, + mock_event_start): mock_list_by_container.return_value = [] container = Container(self.context, **utils.get_test_container()) mock_delete.side_effect = exception.DockerError( @@ -566,6 +607,8 @@ class TestManager(base.TestCase): mock_remove_usage.assert_called_once_with(self.context, container, True) + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(FakeResourceTracker, 'remove_usage_from_container') @mock.patch.object(Container, 'destroy') @@ -576,7 +619,9 @@ class TestManager(base.TestCase): def test_container_delete_sandbox_failed(self, mock_delete, mock_save, mock_delete_sandbox, mock_fail, mock_destroy, - mock_remove_usage): + mock_remove_usage, + mock_event_finish, + mock_event_start): self.compute_manager.use_sandbox = True container = Container(self.context, **utils.get_test_container()) container.set_sandbox_id("sandbox_id") @@ -591,6 +636,8 @@ class TestManager(base.TestCase): mock_destroy.assert_not_called() mock_remove_usage.assert_not_called() + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(FakeResourceTracker, 'remove_usage_from_container') @mock.patch.object(Container, 'destroy') @@ -604,7 +651,9 @@ class TestManager(base.TestCase): mock_save, mock_delete_sandbox, mock_fail, mock_destroy, - mock_remove_usage): + mock_remove_usage, + mock_event_finish, + mock_event_start): mock_list_by_container.return_value = [] self.compute_manager.use_sandbox = True container = Container(self.context, **utils.get_test_container()) @@ -643,19 +692,25 @@ class TestManager(base.TestCase): self.compute_manager.container_show, self.context, container) + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(Container, 'save') @mock.patch.object(fake_driver, 'reboot') - def test_container_reboot(self, mock_reboot, mock_save): + def test_container_reboot(self, mock_reboot, mock_save, mock_event_finish, + mock_event_start): container = Container(self.context, **utils.get_test_container()) self.compute_manager._do_container_reboot(self.context, container, 10) mock_save.assert_called_with(self.context) mock_reboot.assert_called_once_with(self.context, container, 10) + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(manager.Manager, '_fail_container') @mock.patch.object(Container, 'save') @mock.patch.object(fake_driver, 'reboot') def test_container_reboot_failed(self, mock_reboot, mock_save, - mock_fail): + mock_fail, mock_event_finish, + mock_event_start): container = Container(self.context, **utils.get_test_container()) mock_reboot.side_effect = exception.DockerError( message="Docker Error occurred") @@ -666,18 +721,24 @@ class TestManager(base.TestCase): mock_fail.assert_called_with(self.context, container, 'Docker Error occurred') + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(Container, 'save') @mock.patch.object(fake_driver, 'stop') - def test_container_stop(self, mock_stop, mock_save): + def test_container_stop(self, mock_stop, mock_save, mock_event_finish, + mock_event_start): container = Container(self.context, **utils.get_test_container()) self.compute_manager._do_container_stop(self.context, container, 10) mock_save.assert_called_with(self.context) mock_stop.assert_called_once_with(self.context, container, 10) + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(manager.Manager, '_fail_container') @mock.patch.object(Container, 'save') @mock.patch.object(fake_driver, 'stop') - def test_container_stop_failed(self, mock_stop, mock_save, mock_fail): + def test_container_stop_failed(self, mock_stop, mock_save, mock_fail, + mock_event_finish, mock_event_start): container = Container(self.context, **utils.get_test_container()) mock_stop.side_effect = exception.DockerError( message="Docker Error occurred") @@ -688,19 +749,25 @@ class TestManager(base.TestCase): mock_fail.assert_called_with(self.context, container, 'Docker Error occurred') + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(Container, 'save') @mock.patch.object(fake_driver, 'start') - def test_container_start(self, mock_start, mock_save): + def test_container_start(self, mock_start, mock_save, mock_event_finish, + mock_event_start): container = Container(self.context, **utils.get_test_container()) self.compute_manager._do_container_start(self.context, container) mock_save.assert_called_with(self.context) mock_start.assert_called_once_with(self.context, container) + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(Container, 'save') @mock.patch.object(manager.Manager, '_fail_container') @mock.patch.object(fake_driver, 'start') def test_container_start_failed(self, mock_start, - mock_fail, mock_save): + mock_fail, mock_save, mock_event_finish, + mock_event_start): container = Container(self.context, **utils.get_test_container()) mock_start.side_effect = exception.DockerError( message="Docker Error occurred") @@ -711,15 +778,21 @@ class TestManager(base.TestCase): mock_fail.assert_called_with(self.context, container, 'Docker Error occurred') + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(fake_driver, 'pause') - def test_container_pause(self, mock_pause): + def test_container_pause(self, mock_pause, mock_event_finish, + mock_event_start): container = Container(self.context, **utils.get_test_container()) self.compute_manager._do_container_pause(self.context, container) mock_pause.assert_called_once_with(self.context, container) + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(manager.Manager, '_fail_container') @mock.patch.object(fake_driver, 'pause') - def test_container_pause_failed(self, mock_pause, mock_fail): + def test_container_pause_failed(self, mock_pause, mock_fail, + mock_event_finish, mock_event_start): container = Container(self.context, **utils.get_test_container()) mock_pause.side_effect = exception.DockerError( message="Docker Error occurred") @@ -729,15 +802,21 @@ class TestManager(base.TestCase): mock_fail.assert_called_with(self.context, container, 'Docker Error occurred') + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(fake_driver, 'unpause') - def test_container_unpause(self, mock_unpause): + def test_container_unpause(self, mock_unpause, mock_event_finish, + mock_event_start): container = Container(self.context, **utils.get_test_container()) self.compute_manager._do_container_unpause(self.context, container) mock_unpause.assert_called_once_with(self.context, container) + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(manager.Manager, '_fail_container') @mock.patch.object(fake_driver, 'unpause') - def test_container_unpause_failed(self, mock_unpause, mock_fail): + def test_container_unpause_failed(self, mock_unpause, mock_fail, + mock_event_finish, mock_event_start): container = Container(self.context, **utils.get_test_container()) mock_unpause.side_effect = exception.DockerError( message="Docker Error occurred") @@ -785,15 +864,21 @@ class TestManager(base.TestCase): self.compute_manager.container_exec, self.context, container, 'fake_cmd', True, False) + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(fake_driver, 'kill') - def test_container_kill(self, mock_kill): + def test_container_kill(self, mock_kill, mock_event_finish, + mock_event_start): container = Container(self.context, **utils.get_test_container()) self.compute_manager._do_container_kill(self.context, container, None) mock_kill.assert_called_once_with(self.context, container, None) + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(manager.Manager, '_fail_container') @mock.patch.object(fake_driver, 'kill') - def test_container_kill_failed(self, mock_kill, mock_fail): + def test_container_kill_failed(self, mock_kill, mock_fail, + mock_event_finish, mock_event_start): container = Container(self.context, **utils.get_test_container()) mock_kill.side_effect = exception.DockerError( message="Docker Error occurred") @@ -897,11 +982,14 @@ class TestManager(base.TestCase): self.compute_manager.container_exec_resize, self.context, 'fake_exec_id', "100", "100") + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch('zun.image.driver.upload_image_data') @mock.patch.object(fake_driver, 'get_image') @mock.patch.object(fake_driver, 'commit') def test_container_commit(self, mock_commit, - mock_get_image, mock_upload_image_data): + mock_get_image, mock_upload_image_data, + mock_event_finish, mock_event_start): container = Container(self.context, **utils.get_test_container()) mock_get_image_response = mock.MagicMock() mock_get_image_response.data = StringIO().read() @@ -914,24 +1002,36 @@ class TestManager(base.TestCase): mock_commit.assert_called_once_with( self.context, container, 'repo', 'tag') + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch('zun.image.driver.delete_image') @mock.patch.object(fake_driver, 'commit') - def test_container_commit_failed(self, mock_commit, mock_delete): + def test_container_commit_failed(self, mock_commit, mock_delete, + mock_event_finish, mock_event_start): container = Container(self.context, **utils.get_test_container()) + mock_get_image_response = mock.MagicMock() + mock_get_image_response.data = StringIO().read() mock_commit.side_effect = exception.DockerError self.assertRaises(exception.DockerError, self.compute_manager._do_container_commit, - self.context, container, 'repo', 'tag') + self.context, mock_get_image_response, container, + 'repo', 'tag') self.assertTrue(mock_delete.called) + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(fake_driver, 'network_detach') - def test_container_network_detach(self, mock_detach): + def test_container_network_detach(self, mock_detach, mock_event_finish, + mock_event_start): container = Container(self.context, **utils.get_test_container()) self.compute_manager.network_detach(self.context, container, 'network') mock_detach.assert_called_once_with(self.context, container, mock.ANY) + @mock.patch.object(ContainerActionEvent, 'event_start') + @mock.patch.object(ContainerActionEvent, 'event_finish') @mock.patch.object(fake_driver, 'network_attach') - def test_container_network_attach(self, mock_attach): + def test_container_network_attach(self, mock_attach, mock_event_finish, + mock_event_start): container = Container(self.context, **utils.get_test_container()) self.compute_manager.network_attach(self.context, container, 'network')