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
|
import sys
|
||||||
|
|
||||||
from debtcollector import removals
|
from debtcollector import removals
|
||||||
|
import enum
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
|
||||||
|
@ -505,6 +506,20 @@ from oslo_config import types
|
||||||
LOG = logging.getLogger(__name__)
|
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):
|
class Error(Exception):
|
||||||
"""Base class for cfg exceptions."""
|
"""Base class for cfg exceptions."""
|
||||||
|
|
||||||
|
@ -2937,7 +2952,7 @@ class ConfigOpts(collections.Mapping):
|
||||||
return self.__cache[key]
|
return self.__cache[key]
|
||||||
except KeyError: # nosec: Valid control flow instruction
|
except KeyError: # nosec: Valid control flow instruction
|
||||||
pass
|
pass
|
||||||
value = self._do_get(name, group, namespace)
|
value, loc = self._do_get(name, group, namespace)
|
||||||
self.__cache[key] = value
|
self.__cache[key] = value
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -2947,21 +2962,23 @@ class ConfigOpts(collections.Mapping):
|
||||||
:param name: the opt name (or 'dest', more precisely)
|
:param name: the opt name (or 'dest', more precisely)
|
||||||
:param group: an OptGroup
|
:param group: an OptGroup
|
||||||
:param namespace: the namespace object to get the option value from
|
: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,
|
:raises: NoSuchOptError, NoSuchGroupError, ConfigFileValueError,
|
||||||
TemplateSubstitutionError
|
TemplateSubstitutionError
|
||||||
"""
|
"""
|
||||||
if group is None and name in self._groups:
|
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)
|
info = self._get_opt_info(name, group)
|
||||||
opt = info['opt']
|
opt = info['opt']
|
||||||
|
|
||||||
if isinstance(opt, SubCommandOpt):
|
if isinstance(opt, SubCommandOpt):
|
||||||
return self.SubCommandAttr(self, group, opt.dest)
|
return (self.SubCommandAttr(self, group, opt.dest), None)
|
||||||
|
|
||||||
if 'override' in info:
|
if 'override' in info:
|
||||||
return self._substitute(info['override'])
|
return (self._substitute(info['override']),
|
||||||
|
LocationInfo(Locations.set_override, ''))
|
||||||
|
|
||||||
def convert(value):
|
def convert(value):
|
||||||
return self._convert_value(
|
return self._convert_value(
|
||||||
|
@ -2974,7 +2991,10 @@ class ConfigOpts(collections.Mapping):
|
||||||
if namespace is not None:
|
if namespace is not None:
|
||||||
group_name = group.name if group else None
|
group_name = group.name if group else None
|
||||||
try:
|
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
|
except KeyError: # nosec: Valid control flow instruction
|
||||||
pass
|
pass
|
||||||
except ValueError as ve:
|
except ValueError as ve:
|
||||||
|
@ -2983,7 +3003,8 @@ class ConfigOpts(collections.Mapping):
|
||||||
% (opt.name, str(ve)))
|
% (opt.name, str(ve)))
|
||||||
|
|
||||||
if 'default' in info:
|
if 'default' in info:
|
||||||
return self._substitute(info['default'])
|
return (self._substitute(info['default']),
|
||||||
|
LocationInfo(Locations.set_default, ''))
|
||||||
|
|
||||||
if self._validate_default_values:
|
if self._validate_default_values:
|
||||||
if opt.default is not None:
|
if opt.default is not None:
|
||||||
|
@ -2995,9 +3016,10 @@ class ConfigOpts(collections.Mapping):
|
||||||
% (opt.name, str(e)))
|
% (opt.name, str(e)))
|
||||||
|
|
||||||
if opt.default is not None:
|
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):
|
def _substitute(self, value, group=None, namespace=None):
|
||||||
"""Perform string template substitution.
|
"""Perform string template substitution.
|
||||||
|
@ -3357,6 +3379,21 @@ class ConfigOpts(collections.Mapping):
|
||||||
s |= set(self._namespace._sections())
|
s |= set(self._namespace._sections())
|
||||||
return sorted(s)
|
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):
|
class GroupAttr(collections.Mapping):
|
||||||
|
|
||||||
"""Helper class.
|
"""Helper class.
|
||||||
|
|
|
@ -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
|
oslo.i18n>=3.15.3 # Apache-2.0
|
||||||
rfc3986>=0.3.1 # Apache-2.0
|
rfc3986>=0.3.1 # Apache-2.0
|
||||||
PyYAML>=3.10 # MIT
|
PyYAML>=3.10 # MIT
|
||||||
|
enum34>=1.0.4;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD
|
||||||
|
|
Loading…
Reference in New Issue