From b72980960e62e05ad4dcd9af196105e3af0ac43f Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 18 May 2020 17:49:24 +0100 Subject: [PATCH] Remove hooks This extension point was deprecated in 13.0.0 (Mitaka) as it was unmaintainable. Now, over four years later, it's finally time to remove them. A combination of notifications, versioned or otherwise, and dynamic vendordata should be used instead. Change-Id: Idb9a0c06d8abdb158bcee5be12c35dcb67257e60 Signed-off-by: Stephen Finucane --- nova/compute/api.py | 2 - nova/compute/manager.py | 3 - nova/hooks.py | 167 --------------- nova/network/neutron.py | 2 - nova/test.py | 6 - nova/tests/unit/compute/test_compute.py | 15 -- nova/tests/unit/compute/test_compute_mgr.py | 145 +++++++++---- nova/tests/unit/test_hooks.py | 199 ------------------ .../notes/remove-hooks-96d08645404d327c.yaml | 13 ++ 9 files changed, 113 insertions(+), 439 deletions(-) delete mode 100644 nova/hooks.py delete mode 100644 nova/tests/unit/test_hooks.py create mode 100644 releasenotes/notes/remove-hooks-96d08645404d327c.yaml diff --git a/nova/compute/api.py b/nova/compute/api.py index 7f68070885c2..752db08855b9 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -58,7 +58,6 @@ from nova.db import base from nova.db.sqlalchemy import api as db_api from nova import exception from nova import exception_wrapper -from nova import hooks from nova.i18n import _ from nova.image import glance from nova.network import constants @@ -1927,7 +1926,6 @@ class API(base.Base): "is specified.") raise exception.InvalidFixedIpAndMaxCountRequest(reason=msg) - @hooks.add_hook("create_instance") def create(self, context, instance_type, image_href, kernel_id=None, ramdisk_id=None, min_count=None, max_count=None, diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 18b730a1bef2..a242ad71d04a 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -73,7 +73,6 @@ import nova.conf import nova.context from nova import exception from nova import exception_wrapper -from nova import hooks from nova.i18n import _ from nova.image import glance from nova import manager @@ -2159,7 +2158,6 @@ class ComputeManager(manager.Manager): 'Trusted image certificates provided on host that does not ' 'support certificate validation.') - @hooks.add_hook('build_instance') @wrap_exception() @reverts_task_state @wrap_instance_event(prefix='compute') @@ -2949,7 +2947,6 @@ class ComputeManager(manager.Manager): if exc_info is not None and raise_exc: six.reraise(exc_info[0], exc_info[1], exc_info[2]) - @hooks.add_hook("delete_instance") def _delete_instance(self, context, instance, bdms): """Delete an instance on this host. diff --git a/nova/hooks.py b/nova/hooks.py deleted file mode 100644 index 37c595920c5f..000000000000 --- a/nova/hooks.py +++ /dev/null @@ -1,167 +0,0 @@ -# Copyright (c) 2012 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. - -"""Decorator and config option definitions for adding custom code (hooks) -around callables. - -NOTE: as of Nova 13.0 hooks are DEPRECATED and will be removed in the -near future. You should not build any new code using this facility. - -Any method may have the 'add_hook' decorator applied, which yields the -ability to invoke Hook objects before or after the method. (i.e. pre and -post) - -Hook objects are loaded by HookLoaders. Each named hook may invoke multiple -Hooks. - -Example Hook object:: - - | class MyHook(object): - | def pre(self, *args, **kwargs): - | # do stuff before wrapped callable runs - | - | def post(self, rv, *args, **kwargs): - | # do stuff after wrapped callable runs - -Example Hook object with function parameters:: - - | class MyHookWithFunction(object): - | def pre(self, f, *args, **kwargs): - | # do stuff with wrapped function info - | def post(self, f, *args, **kwargs): - | # do stuff with wrapped function info - -""" - -import functools - -from oslo_log import log as logging -import stevedore - -from nova.i18n import _ - -LOG = logging.getLogger(__name__) -NS = 'nova.hooks' - -_HOOKS = {} # hook name => hook manager - - -class FatalHookException(Exception): - """Exception which should be raised by hooks to indicate that normal - execution of the hooked function should be terminated. Raised exception - will be logged and reraised. - """ - pass - - -class HookManager(stevedore.hook.HookManager): - def __init__(self, name): - """Invoke_on_load creates an instance of the Hook class - - :param name: The name of the hooks to load. - :type name: str - """ - super(HookManager, self).__init__(NS, name, invoke_on_load=True) - - def _run(self, name, method_type, args, kwargs, func=None): - if method_type not in ('pre', 'post'): - msg = _("Wrong type of hook method. " - "Only 'pre' and 'post' type allowed") - raise ValueError(msg) - - # TODO(stephenfin): Kill this - for e in self.extensions: - obj = e.obj - hook_method = getattr(obj, method_type, None) - if hook_method: - LOG.warning("Hooks are deprecated as of Nova 13.0 and " - "will be removed in a future release") - LOG.debug("Running %(name)s %(type)s-hook: %(obj)s", - {'name': name, 'type': method_type, 'obj': obj}) - try: - if func: - hook_method(func, *args, **kwargs) - else: - hook_method(*args, **kwargs) - except FatalHookException: - msg = ( - "Fatal Exception running %(name)s %(type)s-hook: " - "%(obj)s" - ) - LOG.exception(msg, {'name': name, 'type': method_type, - 'obj': obj}) - raise - except Exception: - msg = "Exception running %(name)s %(type)s-hook: %(obj)s" - LOG.exception(msg, {'name': name, 'type': method_type, - 'obj': obj}) - - def run_pre(self, name, args, kwargs, f=None): - """Execute optional pre methods of loaded hooks. - - :param name: The name of the loaded hooks. - :param args: Positional arguments which would be transmitted into - all pre methods of loaded hooks. - :param kwargs: Keyword args which would be transmitted into all pre - methods of loaded hooks. - :param f: Target function. - """ - self._run(name=name, method_type='pre', args=args, kwargs=kwargs, - func=f) - - def run_post(self, name, rv, args, kwargs, f=None): - """Execute optional post methods of loaded hooks. - - :param name: The name of the loaded hooks. - :param rv: Return values of target method call. - :param args: Positional arguments which would be transmitted into - all post methods of loaded hooks. - :param kwargs: Keyword args which would be transmitted into all post - methods of loaded hooks. - :param f: Target function. - """ - self._run(name=name, method_type='post', args=(rv,) + args, - kwargs=kwargs, func=f) - - -def add_hook(name, pass_function=False): - """Execute optional pre and post methods around the decorated - function. This is useful for customization around callables. - """ - - def outer(f): - f.__hook_name__ = name - - @functools.wraps(f) - def inner(*args, **kwargs): - manager = _HOOKS.setdefault(name, HookManager(name)) - - function = None - if pass_function: - function = f - - manager.run_pre(name, args, kwargs, f=function) - rv = f(*args, **kwargs) - manager.run_post(name, rv, args, kwargs, f=function) - - return rv - - return inner - return outer - - -def reset(): - """Clear loaded hooks.""" - _HOOKS.clear() diff --git a/nova/network/neutron.py b/nova/network/neutron.py index f3ac861be9e2..045a316e98c9 100644 --- a/nova/network/neutron.py +++ b/nova/network/neutron.py @@ -37,7 +37,6 @@ import nova.conf from nova import context as nova_context from nova.db import base from nova import exception -from nova import hooks from nova.i18n import _ from nova.network import constants from nova.network import model as network_model @@ -102,7 +101,6 @@ def get_binding_profile(port): return port.get(constants.BINDING_PROFILE, {}) or {} -@hooks.add_hook('instance_network_info') def update_instance_cache_with_nw_info(impl, context, instance, nw_info=None): if instance.deleted: LOG.debug('Instance is deleted, no further info cache update', diff --git a/nova/test.py b/nova/test.py index 6f5a8eebe3c0..48f1849b0e64 100644 --- a/nova/test.py +++ b/nova/test.py @@ -748,12 +748,6 @@ class NoDBTestCase(TestCase): USES_DB = False -class BaseHookTestCase(NoDBTestCase): - def assert_has_hook(self, expected_name, func): - self.assertTrue(hasattr(func, '__hook_name__')) - self.assertEqual(expected_name, func.__hook_name__) - - class MatchType(object): """Matches any instance of a specified type diff --git a/nova/tests/unit/compute/test_compute.py b/nova/tests/unit/compute/test_compute.py index 1db1bff1054f..4335a6961480 100644 --- a/nova/tests/unit/compute/test_compute.py +++ b/nova/tests/unit/compute/test_compute.py @@ -13425,18 +13425,3 @@ class CheckRequestedImageTestCase(test.TestCase): self.compute_api._validate_flavor_image, self.context, image['id'], image, self.instance_type, None) - - -class ComputeHooksTestCase(test.BaseHookTestCase): - def test_delete_instance_has_hook(self): - delete_func = compute_manager.ComputeManager._delete_instance - self.assert_has_hook('delete_instance', delete_func) - - def test_create_instance_has_hook(self): - create_func = compute.API.create - self.assert_has_hook('create_instance', create_func) - - def test_build_instance_has_hook(self): - build_instance_func = (compute_manager.ComputeManager. - _do_build_and_run_instance) - self.assert_has_hook('build_instance', build_instance_func) diff --git a/nova/tests/unit/compute/test_compute_mgr.py b/nova/tests/unit/compute/test_compute_mgr.py index 0839458cca8f..4eea27734b1a 100644 --- a/nova/tests/unit/compute/test_compute_mgr.py +++ b/nova/tests/unit/compute/test_compute_mgr.py @@ -6025,15 +6025,6 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase): mock_finish.assert_called_once_with(self.context, self.instance.uuid, mock.ANY, exc_val=mock.ANY, exc_tb=mock.ANY, want_result=False) - @staticmethod - def _assert_build_instance_hook_called(mock_hooks, result): - # NOTE(coreywright): we want to test the return value of - # _do_build_and_run_instance, but it doesn't bubble all the way up, so - # mock the hooking, which allows us to test that too, though a little - # too intimately - mock_hooks.setdefault().run_post.assert_called_once_with( - 'build_instance', result, mock.ANY, mock.ANY, f=None) - @mock.patch.object(objects.Instance, 'save') @mock.patch.object(nova.compute.manager.ComputeManager, '_default_block_device_names') @@ -6349,12 +6340,23 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase): @mock.patch.object(objects.InstanceActionEvent, 'event_start') @mock.patch.object(objects.Instance, 'save') @mock.patch.object(manager.ComputeManager, '_build_and_run_instance') - @mock.patch('nova.hooks._HOOKS') - def _test_build_and_run_instance(self, mock_hooks, mock_build, mock_save, + def _test_build_and_run_instance(self, mock_build, mock_save, mock_start, mock_finish): self._do_build_instance_update(mock_save) - self.compute.build_and_run_instance(self.context, self.instance, + orig_do_build_and_run = self.compute._do_build_and_run_instance + + def _wrapped_do_build_and_run_instance(*args, **kwargs): + ret = orig_do_build_and_run(*args, **kwargs) + self.assertEqual(build_results.ACTIVE, ret) + return ret + + with mock.patch.object( + self.compute, '_do_build_and_run_instance', + side_effect=_wrapped_do_build_and_run_instance, + ): + self.compute.build_and_run_instance( + self.context, self.instance, self.image, request_spec={}, filter_properties=self.filter_properties, injected_files=self.injected_files, @@ -6364,8 +6366,6 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase): block_device_mapping=self.block_device_mapping, node=self.node, limits=self.limits, host_list=fake_host_list) - self._assert_build_instance_hook_called(mock_hooks, - build_results.ACTIVE) self._instance_action_events(mock_start, mock_finish) self._assert_build_instance_update(mock_save) mock_build.assert_called_once_with(self.context, self.instance, @@ -6411,8 +6411,7 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase): @mock.patch.object(manager.ComputeManager, '_set_instance_obj_error_state') @mock.patch.object(conductor_api.ComputeTaskAPI, 'build_instances') @mock.patch.object(manager.ComputeManager, '_build_and_run_instance') - @mock.patch('nova.hooks._HOOKS') - def test_build_abort_exception(self, mock_hooks, mock_build_run, + def test_build_abort_exception(self, mock_build_run, mock_build, mock_set, mock_nil, mock_add, mock_clean_vol, mock_clean_net, mock_save, mock_start, mock_finish): @@ -6420,7 +6419,19 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase): mock_build_run.side_effect = exception.BuildAbortException(reason='', instance_uuid=self.instance.uuid) - self.compute.build_and_run_instance(self.context, self.instance, + orig_do_build_and_run = self.compute._do_build_and_run_instance + + def _wrapped_do_build_and_run_instance(*args, **kwargs): + ret = orig_do_build_and_run(*args, **kwargs) + self.assertEqual(build_results.FAILED, ret) + return ret + + with mock.patch.object( + self.compute, '_do_build_and_run_instance', + side_effect=_wrapped_do_build_and_run_instance, + ): + self.compute.build_and_run_instance( + self.context, self.instance, self.image, request_spec={}, filter_properties=self.filter_properties, injected_files=self.injected_files, @@ -6432,8 +6443,6 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase): self._instance_action_events(mock_start, mock_finish) self._assert_build_instance_update(mock_save) - self._assert_build_instance_hook_called(mock_hooks, - build_results.FAILED) mock_build_run.assert_called_once_with(self.context, self.instance, self.image, self.injected_files, self.admin_pass, self.requested_networks, self.security_groups, @@ -6458,16 +6467,28 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase): @mock.patch.object(manager.ComputeManager, '_set_instance_obj_error_state') @mock.patch.object(conductor_api.ComputeTaskAPI, 'build_instances') @mock.patch.object(manager.ComputeManager, '_build_and_run_instance') - @mock.patch('nova.hooks._HOOKS') - def test_rescheduled_exception(self, mock_hooks, mock_build_run, + def test_rescheduled_exception(self, mock_build_run, mock_build, mock_set, mock_nil, mock_save, mock_start, mock_finish): self._do_build_instance_update(mock_save, reschedule_update=True) mock_build_run.side_effect = exception.RescheduledException(reason='', instance_uuid=self.instance.uuid) - with mock.patch.object( - self.compute.network_api, 'get_instance_nw_info', + orig_do_build_and_run = self.compute._do_build_and_run_instance + + def _wrapped_do_build_and_run_instance(*args, **kwargs): + ret = orig_do_build_and_run(*args, **kwargs) + self.assertEqual(build_results.RESCHEDULED, ret) + return ret + + with test.nested( + mock.patch.object( + self.compute, '_do_build_and_run_instance', + side_effect=_wrapped_do_build_and_run_instance, + ), + mock.patch.object( + self.compute.network_api, 'get_instance_nw_info', + ), ): self.compute.build_and_run_instance( self.context, self.instance, @@ -6481,8 +6502,6 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase): node=self.node, limits=self.limits, host_list=fake_host_list) - self._assert_build_instance_hook_called(mock_hooks, - build_results.RESCHEDULED) self._instance_action_events(mock_start, mock_finish) self._assert_build_instance_update(mock_save, reschedule_update=True) mock_build_run.assert_called_once_with(self.context, self.instance, @@ -6692,15 +6711,26 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase): @mock.patch.object(manager.ComputeManager, '_set_instance_obj_error_state') @mock.patch.object(compute_utils, 'add_instance_fault_from_exc') @mock.patch.object(manager.ComputeManager, '_build_and_run_instance') - @mock.patch('nova.hooks._HOOKS') - def test_rescheduled_exception_without_retry(self, mock_hooks, + def test_rescheduled_exception_without_retry(self, mock_build_run, mock_add, mock_set, mock_clean_net, mock_clean_vol, mock_nil, mock_save, mock_start, mock_finish): self._do_build_instance_update(mock_save) mock_build_run.side_effect = exception.RescheduledException(reason='', instance_uuid=self.instance.uuid) - self.compute.build_and_run_instance(self.context, self.instance, + orig_do_build_and_run = self.compute._do_build_and_run_instance + + def _wrapped_do_build_and_run_instance(*args, **kwargs): + ret = orig_do_build_and_run(*args, **kwargs) + self.assertEqual(build_results.FAILED, ret) + return ret + + with mock.patch.object( + self.compute, '_do_build_and_run_instance', + side_effect=_wrapped_do_build_and_run_instance, + ): + self.compute.build_and_run_instance( + self.context, self.instance, self.image, request_spec={}, filter_properties={}, injected_files=self.injected_files, @@ -6710,8 +6740,6 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase): block_device_mapping=self.block_device_mapping, node=self.node, limits=self.limits, host_list=fake_host_list) - self._assert_build_instance_hook_called(mock_hooks, - build_results.FAILED) self._instance_action_events(mock_start, mock_finish) self._assert_build_instance_update(mock_save) mock_build_run.assert_called_once_with(self.context, self.instance, @@ -6739,8 +6767,7 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase): '_nil_out_instance_obj_host_and_node') @mock.patch.object(conductor_api.ComputeTaskAPI, 'build_instances') @mock.patch.object(manager.ComputeManager, '_build_and_run_instance') - @mock.patch('nova.hooks._HOOKS') - def test_rescheduled_exception_do_not_deallocate_network(self, mock_hooks, + def test_rescheduled_exception_do_not_deallocate_network(self, mock_build_run, mock_build, mock_nil, mock_clean_net, mock_save, mock_start, mock_finish): @@ -6748,7 +6775,19 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase): mock_build_run.side_effect = exception.RescheduledException(reason='', instance_uuid=self.instance.uuid) - self.compute.build_and_run_instance(self.context, self.instance, + orig_do_build_and_run = self.compute._do_build_and_run_instance + + def _wrapped_do_build_and_run_instance(*args, **kwargs): + ret = orig_do_build_and_run(*args, **kwargs) + self.assertEqual(build_results.RESCHEDULED, ret) + return ret + + with mock.patch.object( + self.compute, '_do_build_and_run_instance', + side_effect=_wrapped_do_build_and_run_instance, + ): + self.compute.build_and_run_instance( + self.context, self.instance, self.image, request_spec={}, filter_properties=self.filter_properties, injected_files=self.injected_files, @@ -6759,8 +6798,6 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase): node=self.node, limits=self.limits, host_list=fake_host_list) - self._assert_build_instance_hook_called(mock_hooks, - build_results.RESCHEDULED) self._instance_action_events(mock_start, mock_finish) self._assert_build_instance_update(mock_save, reschedule_update=True) mock_build_run.assert_called_once_with(self.context, self.instance, @@ -6784,15 +6821,26 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase): '_nil_out_instance_obj_host_and_node') @mock.patch.object(conductor_api.ComputeTaskAPI, 'build_instances') @mock.patch.object(manager.ComputeManager, '_build_and_run_instance') - @mock.patch('nova.hooks._HOOKS') - def test_rescheduled_exception_deallocate_network(self, mock_hooks, + def test_rescheduled_exception_deallocate_network(self, mock_build_run, mock_build, mock_nil, mock_clean, mock_save, mock_start, mock_finish): self._do_build_instance_update(mock_save, reschedule_update=True) mock_build_run.side_effect = exception.RescheduledException(reason='', instance_uuid=self.instance.uuid) - self.compute.build_and_run_instance(self.context, self.instance, + orig_do_build_and_run = self.compute._do_build_and_run_instance + + def _wrapped_do_build_and_run_instance(*args, **kwargs): + ret = orig_do_build_and_run(*args, **kwargs) + self.assertEqual(build_results.RESCHEDULED, ret) + return ret + + with mock.patch.object( + self.compute, '_do_build_and_run_instance', + side_effect=_wrapped_do_build_and_run_instance, + ): + self.compute.build_and_run_instance( + self.context, self.instance, self.image, request_spec={}, filter_properties=self.filter_properties, injected_files=self.injected_files, @@ -6802,8 +6850,6 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase): block_device_mapping=self.block_device_mapping, node=self.node, limits=self.limits, host_list=fake_host_list) - self._assert_build_instance_hook_called(mock_hooks, - build_results.RESCHEDULED) self._instance_action_events(mock_start, mock_finish) self._assert_build_instance_update(mock_save, reschedule_update=True) mock_build_run.assert_called_once_with(self.context, self.instance, @@ -6832,8 +6878,7 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase): @mock.patch.object(manager.ComputeManager, '_set_instance_obj_error_state') @mock.patch.object(conductor_api.ComputeTaskAPI, 'build_instances') @mock.patch.object(manager.ComputeManager, '_build_and_run_instance') - @mock.patch('nova.hooks._HOOKS') - def _test_build_and_run_exceptions(self, exc, mock_hooks, mock_build_run, + def _test_build_and_run_exceptions(self, exc, mock_build_run, mock_build, mock_set, mock_nil, mock_add, mock_clean_vol, mock_clean_net, mock_save, mock_start, mock_finish, set_error=False, cleanup_volumes=False, @@ -6841,7 +6886,19 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase): self._do_build_instance_update(mock_save) mock_build_run.side_effect = exc - self.compute.build_and_run_instance(self.context, self.instance, + orig_do_build_and_run = self.compute._do_build_and_run_instance + + def _wrapped_do_build_and_run_instance(*args, **kwargs): + ret = orig_do_build_and_run(*args, **kwargs) + self.assertEqual(build_results.FAILED, ret) + return ret + + with mock.patch.object( + self.compute, '_do_build_and_run_instance', + side_effect=_wrapped_do_build_and_run_instance, + ): + self.compute.build_and_run_instance( + self.context, self.instance, self.image, request_spec={}, filter_properties=self.filter_properties, injected_files=self.injected_files, @@ -6851,8 +6908,6 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase): block_device_mapping=self.block_device_mapping, node=self.node, limits=self.limits, host_list=fake_host_list) - self._assert_build_instance_hook_called(mock_hooks, - build_results.FAILED) self._instance_action_events(mock_start, mock_finish) self._assert_build_instance_update(mock_save) if cleanup_volumes: diff --git a/nova/tests/unit/test_hooks.py b/nova/tests/unit/test_hooks.py deleted file mode 100644 index 78d33a101c4e..000000000000 --- a/nova/tests/unit/test_hooks.py +++ /dev/null @@ -1,199 +0,0 @@ -# Copyright (c) 2012 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. - -"""Tests for hook customization.""" - -import stevedore - -from nova import hooks -from nova import test - - -class SampleHookA(object): - name = "a" - - def _add_called(self, op, kwargs): - called = kwargs.get('called', None) - if called is not None: - called.append(op + self.name) - - def pre(self, *args, **kwargs): - self._add_called("pre", kwargs) - - -class SampleHookB(SampleHookA): - name = "b" - - def post(self, rv, *args, **kwargs): - self._add_called("post", kwargs) - - -class SampleHookC(SampleHookA): - name = "c" - - def pre(self, f, *args, **kwargs): - self._add_called("pre" + f.__name__, kwargs) - - def post(self, f, rv, *args, **kwargs): - self._add_called("post" + f.__name__, kwargs) - - -class SampleHookExceptionPre(SampleHookA): - name = "epre" - exception = Exception() - - def pre(self, f, *args, **kwargs): - raise self.exception - - -class SampleHookExceptionPost(SampleHookA): - name = "epost" - exception = Exception() - - def post(self, f, rv, *args, **kwargs): - raise self.exception - - -class MockEntryPoint(object): - - def __init__(self, cls): - self.cls = cls - - def load(self): - return self.cls - - -class MockedHookTestCase(test.BaseHookTestCase): - PLUGINS = [] - - def setUp(self): - super(MockedHookTestCase, self).setUp() - - hooks.reset() - - hook_manager = hooks.HookManager.make_test_instance(self.PLUGINS) - self.stub_out('nova.hooks.HookManager', lambda x: hook_manager) - - -class HookTestCase(MockedHookTestCase): - PLUGINS = [ - stevedore.extension.Extension('test_hook', - MockEntryPoint(SampleHookA), SampleHookA, SampleHookA()), - stevedore.extension.Extension('test_hook', - MockEntryPoint(SampleHookB), SampleHookB, SampleHookB()), - ] - - def setUp(self): - super(HookTestCase, self).setUp() - - hooks.reset() - - @hooks.add_hook('test_hook') - def _hooked(self, a, b=1, c=2, called=None): - return 42 - - def test_basic(self): - self.assertEqual(42, self._hooked(1)) - - mgr = hooks._HOOKS['test_hook'] - self.assert_has_hook('test_hook', self._hooked) - self.assertEqual(2, len(mgr.extensions)) - self.assertEqual(SampleHookA, mgr.extensions[0].plugin) - self.assertEqual(SampleHookB, mgr.extensions[1].plugin) - - def test_order_of_execution(self): - called_order = [] - self._hooked(42, called=called_order) - self.assertEqual(['prea', 'preb', 'postb'], called_order) - - -class HookTestCaseWithFunction(MockedHookTestCase): - PLUGINS = [ - stevedore.extension.Extension('function_hook', - MockEntryPoint(SampleHookC), SampleHookC, SampleHookC()), - ] - - @hooks.add_hook('function_hook', pass_function=True) - def _hooked(self, a, b=1, c=2, called=None): - return 42 - - def test_basic(self): - self.assertEqual(42, self._hooked(1)) - mgr = hooks._HOOKS['function_hook'] - - self.assert_has_hook('function_hook', self._hooked) - self.assertEqual(1, len(mgr.extensions)) - self.assertEqual(SampleHookC, mgr.extensions[0].plugin) - - def test_order_of_execution(self): - called_order = [] - self._hooked(42, called=called_order) - self.assertEqual(['pre_hookedc', 'post_hookedc'], called_order) - - -class HookFailPreTestCase(MockedHookTestCase): - PLUGINS = [ - stevedore.extension.Extension('fail_pre', - MockEntryPoint(SampleHookExceptionPre), - SampleHookExceptionPre, SampleHookExceptionPre()), - ] - - @hooks.add_hook('fail_pre', pass_function=True) - def _hooked(self, a, b=1, c=2, called=None): - return 42 - - def test_hook_fail_should_still_return(self): - self.assertEqual(42, self._hooked(1)) - - mgr = hooks._HOOKS['fail_pre'] - self.assert_has_hook('fail_pre', self._hooked) - self.assertEqual(1, len(mgr.extensions)) - self.assertEqual(SampleHookExceptionPre, mgr.extensions[0].plugin) - - def test_hook_fail_should_raise_fatal(self): - self.stub_out('nova.tests.unit.test_hooks.' - 'SampleHookExceptionPre.exception', - hooks.FatalHookException()) - - self.assertRaises(hooks.FatalHookException, - self._hooked, 1) - - -class HookFailPostTestCase(MockedHookTestCase): - PLUGINS = [ - stevedore.extension.Extension('fail_post', - MockEntryPoint(SampleHookExceptionPost), - SampleHookExceptionPost, SampleHookExceptionPost()), - ] - - @hooks.add_hook('fail_post', pass_function=True) - def _hooked(self, a, b=1, c=2, called=None): - return 42 - - def test_hook_fail_should_still_return(self): - self.assertEqual(42, self._hooked(1)) - - mgr = hooks._HOOKS['fail_post'] - self.assert_has_hook('fail_post', self._hooked) - self.assertEqual(1, len(mgr.extensions)) - self.assertEqual(SampleHookExceptionPost, mgr.extensions[0].plugin) - - def test_hook_fail_should_raise_fatal(self): - self.stub_out('nova.tests.unit.test_hooks.' - 'SampleHookExceptionPost.exception', - hooks.FatalHookException()) - - self.assertRaises(hooks.FatalHookException, - self._hooked, 1) diff --git a/releasenotes/notes/remove-hooks-96d08645404d327c.yaml b/releasenotes/notes/remove-hooks-96d08645404d327c.yaml new file mode 100644 index 000000000000..cd795ba59eab --- /dev/null +++ b/releasenotes/notes/remove-hooks-96d08645404d327c.yaml @@ -0,0 +1,13 @@ +--- +upgrade: + - | + Support for hooks has been removed. In previous versions of nova, these + provided a mechanism to extend nova with custom code through a plugin + mechanism. However, they were deprecated in 13.0.0 (Mitaka) as + unmaintainable long-term. `Versioned notifications`__ and `vendordata`__ + should be used instead. + For more information, refer to `this thread`__. + + __ https://docs.openstack.org/nova/latest/reference/notifications.html + __ https://docs.openstack.org/nova/latest/admin/vendordata.html + __ http://lists.openstack.org/pipermail/openstack-dev/2016-February/087782.html