Optionally use oslo.log for deprecated opt logging

While we can't add a hard dependency on oslo.log because it uses
oslo.config, in most cases oslo.log will be installed anyway.  In
the interest of being able to make use of features like
fatal_deprecations in oslo.log, let's use it if it's available.

Change-Id: If9499aa6fc28a6b92447b3825d3ca1957cb2255a
This commit is contained in:
Ben Nemec 2018-04-10 17:24:22 +00:00
parent d85735d1b2
commit 5f8b0e0185
5 changed files with 91 additions and 17 deletions

View File

@ -26,6 +26,7 @@ netaddr==0.7.18
openstackdocstheme==1.18.1
os-client-config==1.28.0
oslo.i18n==3.15.3
oslo.log==3.36.0
oslotest==3.2.0
pbr==2.0.0
pep8==1.5.7

View File

@ -498,6 +498,13 @@ import sys
import enum
import six
# NOTE(bnemec): oslo.log depends on oslo.config, so we can't
# have a hard dependency on oslo.log. However, in most cases
# oslo.log will be installed so we can use it.
try:
import oslo_log
except ImportError:
oslo_log = None
from oslo_config import iniparser
from oslo_config import types
@ -847,6 +854,27 @@ def _normalize_group_name(group_name):
return group_name.lower()
def _report_deprecation(format_str, format_dict):
"""Report use of a deprecated option
Uses versionutils from oslo.log if it is available. If not, logs
a simple warning message.
:param format_str: The message to use for the report
:param format_dict: A dict containing keys for any parameters in format_str
"""
if oslo_log:
# We can't import versionutils at the module level because of circular
# imports. Importing just oslo_log at the module level and
# versionutils locally allows us to unit test this and still avoid the
# circular problem.
from oslo_log import versionutils
versionutils.report_deprecated_feature(LOG, format_str,
format_dict)
else:
LOG.warning(format_str, format_dict)
@functools.total_ordering
class Opt(object):
@ -1085,12 +1113,13 @@ class Opt(object):
pretty_reason = ' ({})'.format(self.deprecated_reason)
else:
pretty_reason = ''
LOG.warning('Option "%(option)s" from group "%(group)s" is '
'deprecated for removal%(reason)s. Its value may be '
'silently ignored in the future.',
{'option': self.dest,
'group': pretty_group,
'reason': pretty_reason})
format_str = ('Option "%(option)s" from group "%(group)s" is '
'deprecated for removal%(reason)s. Its value may '
'be silently ignored in the future.')
format_dict = {'option': self.dest,
'group': pretty_group,
'reason': pretty_reason}
_report_deprecation(format_str, format_dict)
return (value, loc)
def _add_to_cli(self, parser, group=None):
@ -2213,12 +2242,9 @@ class _Namespace(argparse.Namespace):
if name in deprecated and name not in self._emitted_deprecations:
self._emitted_deprecations.add(name)
current = (current[0] or 'DEFAULT', current[1])
# NOTE(bnemec): Not using versionutils for this to avoid a
# circular dependency between oslo.config and whatever library
# versionutils ends up in.
LOG.warning(self._deprecated_opt_message,
{'dep_option': name[1], 'dep_group': name[0],
'option': current[1], 'group': current[0]})
format_dict = {'dep_option': name[1], 'dep_group': name[0],
'option': current[1], 'group': current[0]}
_report_deprecation(self._deprecated_opt_message, format_dict)
def _get_value(self, names, multi=False, positional=False,
current_name=None, normalized=True):

View File

@ -964,6 +964,15 @@ class PositionalTestCase(BaseTestCase):
self.assertEqual('arg1', self.conf.arg1)
# The real report_deprecated_feature has caching that causes races in our
# unit tests. This replicates the relevant functionality.
def _fake_deprecated_feature(logger, msg, *args, **kwargs):
stdmsg = 'Deprecated: %s' % msg
logger.warning(stdmsg, *args, **kwargs)
@mock.patch('oslo_log.versionutils.report_deprecated_feature',
_fake_deprecated_feature)
class ConfigFileOptsTestCase(BaseTestCase):
def setUp(self):
@ -4834,6 +4843,8 @@ class DeprecationWarningTestBase(BaseTestCase):
self._parser_class = cfg.ConfigParser
@mock.patch('oslo_log.versionutils.report_deprecated_feature',
_fake_deprecated_feature)
class DeprecationWarningTestScenarios(DeprecationWarningTestBase):
scenarios = [('default-deprecated', dict(deprecated=True,
group='DEFAULT')),
@ -4867,7 +4878,8 @@ class DeprecationWarningTestScenarios(DeprecationWarningTestBase):
self.assertEqual('baz', self.conf.other.foo)
self.assertEqual('baz', self.conf.other.foo)
if self.deprecated:
expected = (cfg._Namespace._deprecated_opt_message %
expected = ('Deprecated: ' +
cfg._Namespace._deprecated_opt_message %
{'dep_option': 'bar',
'dep_group': self.group,
'option': 'foo',
@ -4877,7 +4889,11 @@ class DeprecationWarningTestScenarios(DeprecationWarningTestBase):
self.assertEqual(expected, self.log_fixture.output)
@mock.patch('oslo_log.versionutils.report_deprecated_feature',
_fake_deprecated_feature)
class DeprecationWarningTests(DeprecationWarningTestBase):
log_prefix = 'Deprecated: '
def test_DeprecatedOpt(self):
default_deprecated = [cfg.DeprecatedOpt('bar')]
other_deprecated = [cfg.DeprecatedOpt('baz', group='other')]
@ -4915,7 +4931,8 @@ class DeprecationWarningTests(DeprecationWarningTestBase):
'option': current_name,
'group': current_group}
)
self.assertEqual(expected + '\n', self.log_fixture.output)
self.assertEqual(self.log_prefix + expected + '\n',
self.log_fixture.output)
def test_deprecated_for_removal(self):
self.conf.register_opt(cfg.StrOpt('foo',
@ -4934,7 +4951,7 @@ class DeprecationWarningTests(DeprecationWarningTestBase):
expected = ('Option "foo" from group "DEFAULT" is deprecated for '
'removal. Its value may be silently ignored in the '
'future.\n')
self.assertEqual(expected, self.log_fixture.output)
self.assertEqual(self.log_prefix + expected, self.log_fixture.output)
def test_deprecated_for_removal_with_group(self):
self.conf.register_group(cfg.OptGroup('other'))
@ -4956,7 +4973,7 @@ class DeprecationWarningTests(DeprecationWarningTestBase):
expected = ('Option "foo" from group "other" is deprecated for '
'removal. Its value may be silently ignored in the '
'future.\n')
self.assertEqual(expected, self.log_fixture.output)
self.assertEqual(self.log_prefix + expected, self.log_fixture.output)
def test_deprecated_with_dest(self):
self.conf.register_group(cfg.OptGroup('other'))
@ -4975,4 +4992,14 @@ class DeprecationWarningTests(DeprecationWarningTestBase):
'dep_group': 'other',
'option': 'foo-bar',
'group': 'other'} + '\n')
self.assertEqual(expected, self.log_fixture.output)
self.assertEqual(self.log_prefix + expected, self.log_fixture.output)
class DeprecationWarningTestsNoOsloLog(DeprecationWarningTests):
log_prefix = ''
def setUp(self):
super(DeprecationWarningTestsNoOsloLog, self).setUp()
# NOTE(bnemec): For some reason if I apply this as a class decorator
# it ends up applying to the parent class too and breaks those tests.
self.useFixture(fixtures.MockPatchObject(cfg, 'oslo_log', None))

View File

@ -0,0 +1,13 @@
---
features:
- |
oslo.config now supports the fatal-deprecations option from oslo.log. This
behavior is only enabled if oslo.log is installed, but oslo.log is still
not a hard requirement to avoid a circular dependency.
upgrade:
- |
Because support for fatal-deprecations was added in this release, users who
have fatal-deprecations enabled and have deprecated config opts in use
(which previously was not a problem because oslo.config didn't respect the
fatal-deprecations option) will need to resolve that before upgrading or
services may fail to start.

View File

@ -9,6 +9,11 @@ testscenarios>=0.4 # Apache-2.0/BSD
testtools>=2.2.0 # MIT
oslotest>=3.2.0 # Apache-2.0
# oslo.log can't be a runtime dep because it would cause a circular dependency,
# but we can optionally make use of it so we want to have it installed in our
# test environment.
oslo.log>=3.36.0 # Apache-2.0
# when we can require tox>= 1.4, this can go into tox.ini:
# [testenv:cover]
# deps = {[testenv]deps} coverage
@ -22,3 +27,5 @@ mock>=2.0.0 # BSD
# Bandit security code scanner
bandit>=1.1.0 # Apache-2.0
reno>=2.5.0 # Apache-2.0