track the location where configuration options are set
Add a get_location() method to ConfigOpts to ask where the option value was set. Update _do_get() to return this information based on the search criteria. The LocationInfo data structure has 2 fields. We are only using the location for now, but the detail field will be filled in by changes later in this series. Change-Id: I3643c49b3de1850139913ce395199c238dbe6cf0 Signed-off-by: Doug Hellmann <doug@doughellmann.com>
This commit is contained in:
parent
c18ac34f5b
commit
a9625c78d3
@ -496,6 +496,7 @@ import string
|
||||
import sys
|
||||
|
||||
from debtcollector import removals
|
||||
import enum
|
||||
import six
|
||||
|
||||
|
||||
@ -505,6 +506,20 @@ from oslo_config import types
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Locations(enum.Enum):
|
||||
opt_default = (1, False)
|
||||
set_default = (2, False)
|
||||
set_override = (3, False)
|
||||
user = (4, True)
|
||||
|
||||
def __init__(self, num, is_user_controlled):
|
||||
self.num = num
|
||||
self.is_user_controlled = is_user_controlled
|
||||
|
||||
|
||||
LocationInfo = collections.namedtuple('LocationInfo', ['location', 'detail'])
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
"""Base class for cfg exceptions."""
|
||||
|
||||
@ -2937,7 +2952,7 @@ class ConfigOpts(collections.Mapping):
|
||||
return self.__cache[key]
|
||||
except KeyError: # nosec: Valid control flow instruction
|
||||
pass
|
||||
value = self._do_get(name, group, namespace)
|
||||
value, loc = self._do_get(name, group, namespace)
|
||||
self.__cache[key] = value
|
||||
return value
|
||||
|
||||
@ -2947,21 +2962,23 @@ class ConfigOpts(collections.Mapping):
|
||||
:param name: the opt name (or 'dest', more precisely)
|
||||
:param group: an OptGroup
|
||||
:param namespace: the namespace object to get the option value from
|
||||
:returns: the option value, or a GroupAttr object
|
||||
:returns: 2-tuple of the option value or a GroupAttr object
|
||||
and LocationInfo or None
|
||||
:raises: NoSuchOptError, NoSuchGroupError, ConfigFileValueError,
|
||||
TemplateSubstitutionError
|
||||
"""
|
||||
if group is None and name in self._groups:
|
||||
return self.GroupAttr(self, self._get_group(name))
|
||||
return (self.GroupAttr(self, self._get_group(name)), None)
|
||||
|
||||
info = self._get_opt_info(name, group)
|
||||
opt = info['opt']
|
||||
|
||||
if isinstance(opt, SubCommandOpt):
|
||||
return self.SubCommandAttr(self, group, opt.dest)
|
||||
return (self.SubCommandAttr(self, group, opt.dest), None)
|
||||
|
||||
if 'override' in info:
|
||||
return self._substitute(info['override'])
|
||||
return (self._substitute(info['override']),
|
||||
LocationInfo(Locations.set_override, ''))
|
||||
|
||||
def convert(value):
|
||||
return self._convert_value(
|
||||
@ -2974,7 +2991,10 @@ class ConfigOpts(collections.Mapping):
|
||||
if namespace is not None:
|
||||
group_name = group.name if group else None
|
||||
try:
|
||||
return convert(opt._get_from_namespace(namespace, group_name))
|
||||
return (
|
||||
convert(opt._get_from_namespace(namespace, group_name)),
|
||||
LocationInfo(Locations.user, ''),
|
||||
)
|
||||
except KeyError: # nosec: Valid control flow instruction
|
||||
pass
|
||||
except ValueError as ve:
|
||||
@ -2983,7 +3003,8 @@ class ConfigOpts(collections.Mapping):
|
||||
% (opt.name, str(ve)))
|
||||
|
||||
if 'default' in info:
|
||||
return self._substitute(info['default'])
|
||||
return (self._substitute(info['default']),
|
||||
LocationInfo(Locations.set_default, ''))
|
||||
|
||||
if self._validate_default_values:
|
||||
if opt.default is not None:
|
||||
@ -2995,9 +3016,10 @@ class ConfigOpts(collections.Mapping):
|
||||
% (opt.name, str(e)))
|
||||
|
||||
if opt.default is not None:
|
||||
return convert(opt.default)
|
||||
return (convert(opt.default),
|
||||
LocationInfo(Locations.opt_default, ''))
|
||||
|
||||
return None
|
||||
return (None, None)
|
||||
|
||||
def _substitute(self, value, group=None, namespace=None):
|
||||
"""Perform string template substitution.
|
||||
@ -3357,6 +3379,21 @@ class ConfigOpts(collections.Mapping):
|
||||
s |= set(self._namespace._sections())
|
||||
return sorted(s)
|
||||
|
||||
def get_location(self, name, group=None):
|
||||
"""Return the location where the option is being set.
|
||||
|
||||
:param name: The name of the option.
|
||||
:type name: str
|
||||
:param group: The name of the group of the option. Defaults to
|
||||
``'DEFAULT'``.
|
||||
:type group: str
|
||||
:return: LocationInfo
|
||||
|
||||
.. versionadded:: 5.3.0
|
||||
"""
|
||||
value, loc = self._do_get(name, group, None)
|
||||
return loc
|
||||
|
||||
class GroupAttr(collections.Mapping):
|
||||
|
||||
"""Helper class.
|
||||
|
85
oslo_config/tests/test_get_location.py
Normal file
85
oslo_config/tests/test_get_location.py
Normal file
@ -0,0 +1,85 @@
|
||||
# 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.
|
||||
|
||||
from oslotest import base
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
|
||||
class TestConfigOpts(cfg.ConfigOpts):
|
||||
def __call__(self, args=None, default_config_files=[],
|
||||
default_config_dirs=[]):
|
||||
return cfg.ConfigOpts.__call__(
|
||||
self,
|
||||
args=args,
|
||||
prog='test',
|
||||
version='1.0',
|
||||
usage='%(prog)s FOO BAR',
|
||||
description='somedesc',
|
||||
epilog='tepilog',
|
||||
default_config_files=default_config_files,
|
||||
default_config_dirs=default_config_dirs,
|
||||
validate_default_values=True)
|
||||
|
||||
|
||||
class LocationTestCase(base.BaseTestCase):
|
||||
|
||||
def test_user_controlled(self):
|
||||
self.assertTrue(cfg.Locations.user.is_user_controlled)
|
||||
|
||||
def test_not_user_controlled(self):
|
||||
self.assertFalse(cfg.Locations.opt_default.is_user_controlled)
|
||||
self.assertFalse(cfg.Locations.set_default.is_user_controlled)
|
||||
self.assertFalse(cfg.Locations.set_override.is_user_controlled)
|
||||
|
||||
|
||||
class GetLocationTestCase(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(GetLocationTestCase, self).setUp()
|
||||
self.conf = TestConfigOpts()
|
||||
self.normal_opt = cfg.StrOpt(
|
||||
'normal_opt',
|
||||
default='normal_opt_default',
|
||||
)
|
||||
self.conf.register_opt(self.normal_opt)
|
||||
self.cli_opt = cfg.StrOpt(
|
||||
'cli_opt',
|
||||
default='cli_opt_default',
|
||||
)
|
||||
self.conf.register_cli_opt(self.cli_opt)
|
||||
|
||||
def test_opt_default(self):
|
||||
self.conf([])
|
||||
loc = self.conf.get_location('normal_opt')
|
||||
self.assertEqual(
|
||||
cfg.Locations.opt_default,
|
||||
loc.location,
|
||||
)
|
||||
|
||||
def test_set_default_on_config_opt(self):
|
||||
self.conf.set_default('normal_opt', self.id())
|
||||
self.conf([])
|
||||
loc = self.conf.get_location('normal_opt')
|
||||
self.assertEqual(
|
||||
cfg.Locations.set_default,
|
||||
loc.location,
|
||||
)
|
||||
|
||||
def test_set_override(self):
|
||||
self.conf.set_override('normal_opt', self.id())
|
||||
self.conf([])
|
||||
loc = self.conf.get_location('normal_opt')
|
||||
self.assertEqual(
|
||||
cfg.Locations.set_override,
|
||||
loc.location,
|
||||
)
|
@ -9,3 +9,4 @@ stevedore>=1.20.0 # Apache-2.0
|
||||
oslo.i18n>=3.15.3 # Apache-2.0
|
||||
rfc3986>=0.3.1 # Apache-2.0
|
||||
PyYAML>=3.10 # MIT
|
||||
enum34>=1.0.4;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD
|
||||
|
Loading…
x
Reference in New Issue
Block a user