diff --git a/openstack/common/versionutils.py b/openstack/common/versionutils.py index 13259a3b..2865b7db 100644 --- a/openstack/common/versionutils.py +++ b/openstack/common/versionutils.py @@ -19,15 +19,24 @@ Helpers for comparing version strings. import functools import inspect +import logging +from oslo.config import cfg import pkg_resources import six from openstack.common._i18n import _ -from openstack.common import log as logging LOG = logging.getLogger(__name__) +CONF = cfg.CONF + + +opts = [ + cfg.BoolOpt('fatal_deprecations', + default=False, + help='Enables or disables fatal status of deprecations.'), +] class deprecated(object): @@ -127,7 +136,7 @@ class deprecated(object): @six.wraps(func_or_cls) def wrapped(*args, **kwargs): - LOG.deprecated(msg, details) + report_deprecated_feature(LOG, msg, details) return func_or_cls(*args, **kwargs) return wrapped elif inspect.isclass(func_or_cls): @@ -139,7 +148,7 @@ class deprecated(object): # and added to the oslo-incubator requrements @functools.wraps(orig_init, assigned=('__name__', '__doc__')) def new_init(self, *args, **kwargs): - LOG.deprecated(msg, details) + report_deprecated_feature(LOG, msg, details) orig_init(self, *args, **kwargs) func_or_cls.__init__ = new_init return func_or_cls @@ -201,3 +210,44 @@ def is_compatible(requested_version, current_version, same_major=True): return False return current_parts >= requested_parts + + +# Track the messages we have sent already. See +# report_deprecated_feature(). +_deprecated_messages_sent = {} + + +def report_deprecated_feature(logger, msg, *args, **kwargs): + """Call this function when a deprecated feature is used. + + If the system is configured for fatal deprecations then the message + is logged at the 'critical' level and :class:`DeprecatedConfig` will + be raised. + + Otherwise, the message will be logged (once) at the 'warn' level. + + :raises: :class:`DeprecatedConfig` if the system is configured for + fatal deprecations. + """ + stdmsg = _("Deprecated: %s") % msg + CONF.register_opts(opts) + if CONF.fatal_deprecations: + logger.critical(stdmsg, *args, **kwargs) + raise DeprecatedConfig(msg=stdmsg) + + # Using a list because a tuple with dict can't be stored in a set. + sent_args = _deprecated_messages_sent.setdefault(msg, list()) + + if args in sent_args: + # Already logged this message, so don't log it again. + return + + sent_args.append(args) + logger.warn(stdmsg, *args, **kwargs) + + +class DeprecatedConfig(Exception): + message = _("Fatal call to deprecated config: %(msg)s") + + def __init__(self, msg): + super(Exception, self).__init__(self.message % dict(msg=msg)) diff --git a/tests/unit/test_versionutils.py b/tests/unit/test_versionutils.py index 00481690..4cb3aabb 100644 --- a/tests/unit/test_versionutils.py +++ b/tests/unit/test_versionutils.py @@ -21,7 +21,7 @@ from openstack.common import versionutils class DeprecatedTestCase(test_base.BaseTestCase): - def assert_deprecated(self, mock_log, no_removal=False, + def assert_deprecated(self, mock_reporter, no_removal=False, **expected_details): decorator = versionutils.deprecated if 'in_favor_of' in expected_details: @@ -38,10 +38,14 @@ class DeprecatedTestCase(test_base.BaseTestCase): expected_msg = getattr( decorator, '_deprecated_msg_with_no_alternative_no_removal') - mock_log.deprecated.assert_called_with(expected_msg, expected_details) + # The first argument is the logger, and we don't care about + # that, so ignore it with ANY. + mock_reporter.assert_called_with(mock.ANY, + expected_msg, + expected_details) - @mock.patch('openstack.common.versionutils.LOG', mock.Mock()) - def test_deprecating_a_function_returns_correct_value(self): + @mock.patch('openstack.common.versionutils.report_deprecated_feature') + def test_deprecating_a_function_returns_correct_value(self, mock_reporter): @versionutils.deprecated(as_of=versionutils.deprecated.ICEHOUSE) def do_outdated_stuff(data): @@ -52,8 +56,8 @@ class DeprecatedTestCase(test_base.BaseTestCase): self.assertThat(retval, matchers.Equals(expected_rv)) - @mock.patch('openstack.common.versionutils.LOG', mock.Mock()) - def test_deprecating_a_method_returns_correct_value(self): + @mock.patch('openstack.common.versionutils.report_deprecated_feature') + def test_deprecating_a_method_returns_correct_value(self, mock_reporter): class C(object): @versionutils.deprecated(as_of=versionutils.deprecated.ICEHOUSE) @@ -64,8 +68,8 @@ class DeprecatedTestCase(test_base.BaseTestCase): self.assertThat(retval, matchers.Equals((1, 'of anything'))) - @mock.patch('openstack.common.versionutils.LOG') - def test_deprecated_with_unknown_future_release(self, mock_log): + @mock.patch('openstack.common.versionutils.report_deprecated_feature') + def test_deprecated_with_unknown_future_release(self, mock_reporter): @versionutils.deprecated(as_of=versionutils.deprecated.BEXAR, in_favor_of='different_stuff()') @@ -74,14 +78,14 @@ class DeprecatedTestCase(test_base.BaseTestCase): do_outdated_stuff() - self.assert_deprecated(mock_log, + self.assert_deprecated(mock_reporter, what='do_outdated_stuff()', in_favor_of='different_stuff()', as_of='Bexar', remove_in='D') - @mock.patch('openstack.common.versionutils.LOG') - def test_deprecated_with_known_future_release(self, mock_log): + @mock.patch('openstack.common.versionutils.report_deprecated_feature') + def test_deprecated_with_known_future_release(self, mock_reporter): @versionutils.deprecated(as_of=versionutils.deprecated.GRIZZLY, in_favor_of='different_stuff()') @@ -90,14 +94,14 @@ class DeprecatedTestCase(test_base.BaseTestCase): do_outdated_stuff() - self.assert_deprecated(mock_log, + self.assert_deprecated(mock_reporter, what='do_outdated_stuff()', in_favor_of='different_stuff()', as_of='Grizzly', remove_in='Icehouse') - @mock.patch('openstack.common.versionutils.LOG') - def test_deprecated_without_replacement(self, mock_log): + @mock.patch('openstack.common.versionutils.report_deprecated_feature') + def test_deprecated_without_replacement(self, mock_reporter): @versionutils.deprecated(as_of=versionutils.deprecated.GRIZZLY) def do_outdated_stuff(): @@ -105,13 +109,13 @@ class DeprecatedTestCase(test_base.BaseTestCase): do_outdated_stuff() - self.assert_deprecated(mock_log, + self.assert_deprecated(mock_reporter, what='do_outdated_stuff()', as_of='Grizzly', remove_in='Icehouse') - @mock.patch('openstack.common.versionutils.LOG') - def test_deprecated_with_custom_what(self, mock_log): + @mock.patch('openstack.common.versionutils.report_deprecated_feature') + def test_deprecated_with_custom_what(self, mock_reporter): @versionutils.deprecated(as_of=versionutils.deprecated.GRIZZLY, what='v2.0 API', @@ -121,14 +125,14 @@ class DeprecatedTestCase(test_base.BaseTestCase): do_outdated_stuff() - self.assert_deprecated(mock_log, + self.assert_deprecated(mock_reporter, what='v2.0 API', in_favor_of='v3 API', as_of='Grizzly', remove_in='Icehouse') - @mock.patch('openstack.common.versionutils.LOG') - def test_deprecated_with_removed_next_release(self, mock_log): + @mock.patch('openstack.common.versionutils.report_deprecated_feature') + def test_deprecated_with_removed_next_release(self, mock_reporter): @versionutils.deprecated(as_of=versionutils.deprecated.GRIZZLY, remove_in=1) @@ -137,13 +141,13 @@ class DeprecatedTestCase(test_base.BaseTestCase): do_outdated_stuff() - self.assert_deprecated(mock_log, + self.assert_deprecated(mock_reporter, what='do_outdated_stuff()', as_of='Grizzly', remove_in='Havana') - @mock.patch('openstack.common.versionutils.LOG') - def test_deprecated_with_removed_plus_3(self, mock_log): + @mock.patch('openstack.common.versionutils.report_deprecated_feature') + def test_deprecated_with_removed_plus_3(self, mock_reporter): @versionutils.deprecated(as_of=versionutils.deprecated.GRIZZLY, remove_in=+3) @@ -152,27 +156,27 @@ class DeprecatedTestCase(test_base.BaseTestCase): do_outdated_stuff() - self.assert_deprecated(mock_log, + self.assert_deprecated(mock_reporter, what='do_outdated_stuff()', as_of='Grizzly', remove_in='Juno') - @mock.patch('openstack.common.versionutils.LOG') - def test_deprecated_with_removed_zero(self, mock_log): + @mock.patch('openstack.common.versionutils.report_deprecated_feature') + def test_deprecated_with_removed_zero(self, mock_reporter): @versionutils.deprecated(as_of=versionutils.deprecated.GRIZZLY, remove_in=0) def do_outdated_stuff(): return do_outdated_stuff() - self.assert_deprecated(mock_log, + self.assert_deprecated(mock_reporter, no_removal=True, what='do_outdated_stuff()', as_of='Grizzly', remove_in='Grizzly') - @mock.patch('openstack.common.versionutils.LOG') - def test_deprecated_with_removed_zero_and_alternative(self, mock_log): + @mock.patch('openstack.common.versionutils.report_deprecated_feature') + def test_deprecated_with_removed_zero_and_alternative(self, mock_reporter): @versionutils.deprecated(as_of=versionutils.deprecated.GRIZZLY, in_favor_of='different_stuff()', remove_in=0) @@ -180,15 +184,15 @@ class DeprecatedTestCase(test_base.BaseTestCase): return do_outdated_stuff() - self.assert_deprecated(mock_log, + self.assert_deprecated(mock_reporter, no_removal=True, what='do_outdated_stuff()', as_of='Grizzly', in_favor_of='different_stuff()', remove_in='Grizzly') - @mock.patch('openstack.common.versionutils.LOG') - def test_deprecated_class_without_init(self, mock_log): + @mock.patch('openstack.common.versionutils.report_deprecated_feature') + def test_deprecated_class_without_init(self, mock_reporter): @versionutils.deprecated(as_of=versionutils.deprecated.JUNO, remove_in=+1) @@ -197,13 +201,13 @@ class DeprecatedTestCase(test_base.BaseTestCase): obj = OutdatedClass() self.assertIsInstance(obj, OutdatedClass) - self.assert_deprecated(mock_log, + self.assert_deprecated(mock_reporter, what='OutdatedClass()', as_of='Juno', remove_in='Kilo') - @mock.patch('openstack.common.versionutils.LOG') - def test_deprecated_class_with_init(self, mock_log): + @mock.patch('openstack.common.versionutils.report_deprecated_feature') + def test_deprecated_class_with_init(self, mock_reporter): mock_arguments = mock.MagicMock() args = (1, 5, 7) kwargs = {'first': 10, 'second': 20} @@ -223,7 +227,7 @@ class DeprecatedTestCase(test_base.BaseTestCase): self.assertEqual('It is __init__ method.', obj.__init__.__doc__) self.assertEqual(args, mock_arguments.args) self.assertEqual(kwargs, mock_arguments.kwargs) - self.assert_deprecated(mock_log, + self.assert_deprecated(mock_reporter, what='OutdatedClass()', as_of='Juno', remove_in='Kilo')