Merge "Add config_source option"
This commit is contained in:
commit
084ac31f4c
@ -37,7 +37,8 @@ python-subunit==1.0.0
|
||||
pytz==2013.6
|
||||
PyYAML==3.12
|
||||
reno==2.5.0
|
||||
requests==2.14.2
|
||||
requests==2.18.0
|
||||
requests_mock==1.5.0
|
||||
requestsexceptions==1.2.0
|
||||
rfc3986==0.3.1
|
||||
six==1.10.0
|
||||
|
@ -26,7 +26,8 @@ def list_opts():
|
||||
'/etc/project/project.conf.d/',
|
||||
'/etc/project.conf.d/',
|
||||
]
|
||||
return [
|
||||
(None, cfg.ConfigOpts._make_config_options(default_config_files,
|
||||
default_config_dirs)),
|
||||
]
|
||||
options = cfg.ConfigOpts._list_options_for_discovery(
|
||||
default_config_files,
|
||||
default_config_dirs,
|
||||
)
|
||||
return [(None, options)]
|
||||
|
@ -507,8 +507,11 @@ except ImportError:
|
||||
oslo_log = None
|
||||
|
||||
from oslo_config import iniparser
|
||||
from oslo_config import sources
|
||||
from oslo_config import types
|
||||
|
||||
from stevedore.extension import ExtensionManager
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -2377,6 +2380,16 @@ class ConfigOpts(collections.Mapping):
|
||||
disallow_names = ('project', 'prog', 'version',
|
||||
'usage', 'default_config_files', 'default_config_dirs')
|
||||
|
||||
# NOTE(dhellmann): This instance is reused by list_opts().
|
||||
_config_source_opt = ListOpt(
|
||||
'config_source',
|
||||
metavar='SOURCE',
|
||||
default=[],
|
||||
help=('Lists configuration groups that provide more '
|
||||
'details for accessing configuration settings '
|
||||
'from locations other than local files.'),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
"""Construct a ConfigOpts object."""
|
||||
self._opts = {} # dict of dicts of (opt:, override:, default:)
|
||||
@ -2393,6 +2406,10 @@ class ConfigOpts(collections.Mapping):
|
||||
self._config_opts = []
|
||||
self._cli_opts = collections.deque()
|
||||
self._validate_default_values = False
|
||||
self._sources = []
|
||||
self._ext_mgr = None
|
||||
|
||||
self.register_opt(self._config_source_opt)
|
||||
|
||||
def _pre_setup(self, project, prog, version, usage, description, epilog,
|
||||
default_config_files, default_config_dirs):
|
||||
@ -2443,6 +2460,16 @@ class ConfigOpts(collections.Mapping):
|
||||
'precedence.'),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _list_options_for_discovery(cls,
|
||||
default_config_files,
|
||||
default_config_dirs):
|
||||
"Return options to be used by list_opts() for the sample generator."
|
||||
options = cls._make_config_options(default_config_files,
|
||||
default_config_dirs)
|
||||
options.append(cls._config_source_opt)
|
||||
return options
|
||||
|
||||
def _setup(self, project, prog, version, usage, default_config_files,
|
||||
default_config_dirs):
|
||||
"""Initialize a ConfigOpts object for option parsing."""
|
||||
@ -2530,8 +2557,57 @@ class ConfigOpts(collections.Mapping):
|
||||
raise ConfigFilesPermissionDeniedError(
|
||||
self._namespace._files_permission_denied)
|
||||
|
||||
self._load_alternative_sources()
|
||||
|
||||
self._check_required_opts()
|
||||
|
||||
def _load_alternative_sources(self):
|
||||
# Look for other sources of option data.
|
||||
for source_group_name in self.config_source:
|
||||
source = self._open_source_from_opt_group(source_group_name)
|
||||
if source is not None:
|
||||
self._sources.append(source)
|
||||
|
||||
def _open_source_from_opt_group(self, group_name):
|
||||
if not self._ext_mgr:
|
||||
self._ext_mgr = ExtensionManager(
|
||||
"oslo.config.driver",
|
||||
invoke_on_load=True)
|
||||
|
||||
self.register_opt(
|
||||
StrOpt('driver',
|
||||
choices=self._ext_mgr.names(),
|
||||
help=('The name of the driver that can load this '
|
||||
'configuration source.')),
|
||||
group=group_name)
|
||||
|
||||
try:
|
||||
driver_name = self[group_name].driver
|
||||
except ConfigFileValueError as err:
|
||||
LOG.error(
|
||||
"could not load configuration from %r. %s",
|
||||
group_name, err.msg)
|
||||
return None
|
||||
|
||||
if driver_name is None:
|
||||
LOG.error(
|
||||
"could not load configuration from %r, no 'driver' is set.",
|
||||
group_name)
|
||||
return None
|
||||
|
||||
LOG.info('loading configuration from %r using %r',
|
||||
group_name, driver_name)
|
||||
|
||||
driver = self._ext_mgr[driver_name].obj
|
||||
|
||||
try:
|
||||
return driver.open_source_from_opt_group(self, group_name)
|
||||
except Exception as err:
|
||||
LOG.error(
|
||||
"could not load configuration from %r using %s driver: %s",
|
||||
group_name, driver_name, err)
|
||||
return None
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Look up an option value and perform string substitution.
|
||||
|
||||
@ -3008,12 +3084,13 @@ class ConfigOpts(collections.Mapping):
|
||||
return self._convert_value(
|
||||
self._substitute(value, group, namespace), opt)
|
||||
|
||||
group_name = group.name if group else None
|
||||
|
||||
if opt.mutable and namespace is None:
|
||||
namespace = self._mutable_ns
|
||||
if namespace is None:
|
||||
namespace = self._namespace
|
||||
if namespace is not None:
|
||||
group_name = group.name if group else None
|
||||
try:
|
||||
val, alt_loc = opt._get_from_namespace(namespace, group_name)
|
||||
return (convert(val), alt_loc)
|
||||
@ -3024,6 +3101,11 @@ class ConfigOpts(collections.Mapping):
|
||||
"Value for option %s is not valid: %s"
|
||||
% (opt.name, str(ve)))
|
||||
|
||||
for source in self._sources:
|
||||
val = source.get(group_name, name, opt)
|
||||
if val[0] != sources._NoValue:
|
||||
return (convert(val[0]), val[1])
|
||||
|
||||
if 'default' in info:
|
||||
return (self._substitute(info['default']), loc)
|
||||
|
||||
|
@ -17,8 +17,8 @@ from oslo_config import cfg
|
||||
from oslo_config import sources
|
||||
|
||||
|
||||
class INIConfigurationSourceDriver(sources.ConfigurationSourceDriver):
|
||||
"""A configuration source driver for INI files served through http[s].
|
||||
class URIConfigurationSourceDriver(sources.ConfigurationSourceDriver):
|
||||
"""A configuration source driver for remote files served through http[s].
|
||||
|
||||
Required options:
|
||||
- uri: URI containing the file location.
|
||||
@ -47,15 +47,15 @@ class INIConfigurationSourceDriver(sources.ConfigurationSourceDriver):
|
||||
conf.register_opt(cfg.StrOpt("client_cert"), group)
|
||||
conf.register_opt(cfg.StrOpt("client_key"), group)
|
||||
|
||||
return INIConfigurationSource(
|
||||
return URIConfigurationSource(
|
||||
conf[group_name].uri,
|
||||
conf[group_name].ca_path,
|
||||
conf[group_name].client_cert,
|
||||
conf[group_name].client_key)
|
||||
|
||||
|
||||
class INIConfigurationSource(sources.ConfigurationSource):
|
||||
"""A configuration source for INI files server through http[s].
|
||||
class URIConfigurationSource(sources.ConfigurationSource):
|
||||
"""A configuration source for remote files served through http[s].
|
||||
|
||||
:uri: The Uniform Resource Identifier of the configuration to be
|
||||
retrieved.
|
||||
@ -71,7 +71,7 @@ class INIConfigurationSource(sources.ConfigurationSource):
|
||||
specified but does not includes the private key.
|
||||
"""
|
||||
|
||||
def __init__(self, uri, ca_path, client_cert, client_key):
|
||||
def __init__(self, uri, ca_path=None, client_cert=None, client_key=None):
|
||||
self._uri = uri
|
||||
self._namespace = cfg._Namespace(cfg.ConfigOpts())
|
||||
|
||||
@ -102,7 +102,9 @@ class INIConfigurationSource(sources.ConfigurationSource):
|
||||
:type option_name: str
|
||||
:param opt: The option definition.
|
||||
:type opt: Opt
|
||||
:return: Option value or NoValue.
|
||||
:returns: A tuple (value, location) where value is the option value
|
||||
or oslo_config.sources._NoValue if the (group, option) is
|
||||
not present in the source, and location is a LocationInfo.
|
||||
"""
|
||||
try:
|
||||
return self._namespace._get_value(
|
@ -2481,7 +2481,7 @@ class MappingInterfaceTestCase(BaseTestCase):
|
||||
|
||||
self.assertIn('foo', self.conf)
|
||||
self.assertIn('config_file', self.conf)
|
||||
self.assertEqual(len(self.conf), 3)
|
||||
self.assertEqual(len(self.conf), 4)
|
||||
self.assertEqual('bar', self.conf['foo'])
|
||||
self.assertEqual('bar', self.conf.get('foo'))
|
||||
self.assertIn('bar', list(self.conf.values()))
|
||||
@ -3883,6 +3883,7 @@ class OptDumpingTestCase(BaseTestCase):
|
||||
"=" * 80,
|
||||
"config_dir = []",
|
||||
"config_file = []",
|
||||
"config_source = []",
|
||||
"foo = this",
|
||||
"passwd = ****",
|
||||
"blaa.bar = that",
|
||||
@ -3909,6 +3910,7 @@ class OptDumpingTestCase(BaseTestCase):
|
||||
"command line args: None",
|
||||
"config files: []",
|
||||
"=" * 80,
|
||||
"config_source = []",
|
||||
"*" * 80,
|
||||
], logger.logged)
|
||||
|
||||
|
@ -38,11 +38,11 @@ class RegisterTestCase(BaseTestCase):
|
||||
self.assertEqual('bar', self.fconf.foo)
|
||||
self.assertEqual('bar', self.fconf['foo'])
|
||||
self.assertIn('foo', self.fconf)
|
||||
self.assertEqual(['foo'], list(self.fconf))
|
||||
self.assertEqual(1, len(self.fconf))
|
||||
self.assertEqual(set(['config_source', 'foo']), set(self.fconf))
|
||||
self.assertEqual(2, len(self.fconf))
|
||||
|
||||
self.assertNotIn('foo', self.conf)
|
||||
self.assertEqual(0, len(self.conf))
|
||||
self.assertEqual(1, len(self.conf))
|
||||
self.assertRaises(cfg.NoSuchOptError, getattr, self.conf, 'foo')
|
||||
|
||||
def test_register_opt_none_default(self):
|
||||
@ -51,11 +51,11 @@ class RegisterTestCase(BaseTestCase):
|
||||
self.assertIsNone(self.fconf.foo)
|
||||
self.assertIsNone(self.fconf['foo'])
|
||||
self.assertIn('foo', self.fconf)
|
||||
self.assertEqual(['foo'], list(self.fconf))
|
||||
self.assertEqual(1, len(self.fconf))
|
||||
self.assertEqual(set(['config_source', 'foo']), set(self.fconf))
|
||||
self.assertEqual(2, len(self.fconf))
|
||||
|
||||
self.assertNotIn('foo', self.conf)
|
||||
self.assertEqual(0, len(self.conf))
|
||||
self.assertEqual(1, len(self.conf))
|
||||
self.assertRaises(cfg.NoSuchOptError, getattr, self.conf, 'foo')
|
||||
|
||||
def test_register_grouped_opt_default(self):
|
||||
@ -66,13 +66,13 @@ class RegisterTestCase(BaseTestCase):
|
||||
self.assertEqual('bar', self.fconf['blaa']['foo'])
|
||||
self.assertIn('blaa', self.fconf)
|
||||
self.assertIn('foo', self.fconf.blaa)
|
||||
self.assertEqual(['blaa'], list(self.fconf))
|
||||
self.assertEqual(set(['config_source', 'blaa']), set(self.fconf))
|
||||
self.assertEqual(['foo'], list(self.fconf.blaa))
|
||||
self.assertEqual(1, len(self.fconf))
|
||||
self.assertEqual(2, len(self.fconf))
|
||||
self.assertEqual(1, len(self.fconf.blaa))
|
||||
|
||||
self.assertNotIn('blaa', self.conf)
|
||||
self.assertEqual(0, len(self.conf))
|
||||
self.assertEqual(1, len(self.conf))
|
||||
self.assertRaises(cfg.NoSuchOptError, getattr, self.conf, 'blaa')
|
||||
|
||||
def test_register_grouped_opt_none_default(self):
|
||||
@ -82,13 +82,13 @@ class RegisterTestCase(BaseTestCase):
|
||||
self.assertIsNone(self.fconf['blaa']['foo'])
|
||||
self.assertIn('blaa', self.fconf)
|
||||
self.assertIn('foo', self.fconf.blaa)
|
||||
self.assertEqual(['blaa'], list(self.fconf))
|
||||
self.assertEqual(set(['config_source', 'blaa']), set(self.fconf))
|
||||
self.assertEqual(['foo'], list(self.fconf.blaa))
|
||||
self.assertEqual(1, len(self.fconf))
|
||||
self.assertEqual(2, len(self.fconf))
|
||||
self.assertEqual(1, len(self.fconf.blaa))
|
||||
|
||||
self.assertNotIn('blaa', self.conf)
|
||||
self.assertEqual(0, len(self.conf))
|
||||
self.assertEqual(1, len(self.conf))
|
||||
self.assertRaises(cfg.NoSuchOptError, getattr, self.conf, 'blaa')
|
||||
|
||||
def test_register_group(self):
|
||||
@ -100,13 +100,13 @@ class RegisterTestCase(BaseTestCase):
|
||||
self.assertIsNone(self.fconf['blaa']['foo'])
|
||||
self.assertIn('blaa', self.fconf)
|
||||
self.assertIn('foo', self.fconf.blaa)
|
||||
self.assertEqual(['blaa'], list(self.fconf))
|
||||
self.assertEqual(set(['config_source', 'blaa']), set(self.fconf))
|
||||
self.assertEqual(['foo'], list(self.fconf.blaa))
|
||||
self.assertEqual(1, len(self.fconf))
|
||||
self.assertEqual(2, len(self.fconf))
|
||||
self.assertEqual(1, len(self.fconf.blaa))
|
||||
|
||||
self.assertNotIn('blaa', self.conf)
|
||||
self.assertEqual(0, len(self.conf))
|
||||
self.assertEqual(1, len(self.conf))
|
||||
self.assertRaises(cfg.NoSuchOptError, getattr, self.conf, 'blaa')
|
||||
|
||||
def test_register_opts(self):
|
||||
@ -181,7 +181,7 @@ class RegisterTestCase(BaseTestCase):
|
||||
|
||||
def test_unknown_opt(self):
|
||||
self.assertNotIn('foo', self.fconf)
|
||||
self.assertEqual(0, len(self.fconf))
|
||||
self.assertEqual(1, len(self.fconf))
|
||||
self.assertRaises(cfg.NoSuchOptError, getattr, self.fconf, 'foo')
|
||||
self.assertNotIn('blaa', self.conf)
|
||||
|
||||
@ -189,10 +189,10 @@ class RegisterTestCase(BaseTestCase):
|
||||
self.conf.register_opt(cfg.StrOpt('foo'))
|
||||
|
||||
self.assertIn('foo', self.conf)
|
||||
self.assertEqual(1, len(self.conf))
|
||||
self.assertEqual(2, len(self.conf))
|
||||
self.assertIsNone(self.conf.foo)
|
||||
self.assertNotIn('foo', self.fconf)
|
||||
self.assertEqual(0, len(self.fconf))
|
||||
self.assertEqual(1, len(self.fconf))
|
||||
self.assertRaises(cfg.NoSuchOptError, getattr, self.fconf, 'foo')
|
||||
|
||||
def test_already_registered_opt(self):
|
||||
@ -200,10 +200,10 @@ class RegisterTestCase(BaseTestCase):
|
||||
self.fconf.register_opt(cfg.StrOpt('foo'))
|
||||
|
||||
self.assertIn('foo', self.conf)
|
||||
self.assertEqual(1, len(self.conf))
|
||||
self.assertEqual(2, len(self.conf))
|
||||
self.assertIsNone(self.conf.foo)
|
||||
self.assertIn('foo', self.fconf)
|
||||
self.assertEqual(1, len(self.fconf))
|
||||
self.assertEqual(2, len(self.fconf))
|
||||
self.assertIsNone(self.fconf.foo)
|
||||
|
||||
self.conf.set_override('foo', 'bar')
|
||||
@ -220,13 +220,13 @@ class RegisterTestCase(BaseTestCase):
|
||||
self.assertIn('foo', self.conf)
|
||||
self.assertIn('fu', self.conf)
|
||||
self.assertNotIn('bu', self.conf)
|
||||
self.assertEqual(2, len(self.conf))
|
||||
self.assertEqual(3, len(self.conf))
|
||||
self.assertIsNone(self.conf.foo)
|
||||
self.assertIsNone(self.conf.fu)
|
||||
self.assertIn('foo', self.fconf)
|
||||
self.assertIn('bu', self.fconf)
|
||||
self.assertNotIn('fu', self.fconf)
|
||||
self.assertEqual(2, len(self.fconf))
|
||||
self.assertEqual(3, len(self.fconf))
|
||||
self.assertIsNone(self.fconf.foo)
|
||||
self.assertIsNone(self.fconf.bu)
|
||||
|
||||
@ -240,10 +240,10 @@ class RegisterTestCase(BaseTestCase):
|
||||
self.fconf.register_cli_opt(cfg.StrOpt('foo'))
|
||||
|
||||
self.assertIn('foo', self.conf)
|
||||
self.assertEqual(1, len(self.conf))
|
||||
self.assertEqual(2, len(self.conf))
|
||||
self.assertIsNone(self.conf.foo)
|
||||
self.assertIn('foo', self.fconf)
|
||||
self.assertEqual(1, len(self.fconf))
|
||||
self.assertEqual(2, len(self.fconf))
|
||||
self.assertIsNone(self.fconf.foo)
|
||||
|
||||
self.conf.set_override('foo', 'bar')
|
||||
@ -259,12 +259,12 @@ class RegisterTestCase(BaseTestCase):
|
||||
|
||||
self.assertIn('foo', self.conf)
|
||||
self.assertIn('fu', self.conf)
|
||||
self.assertEqual(2, len(self.conf))
|
||||
self.assertEqual(3, len(self.conf))
|
||||
self.assertIsNone(self.conf.foo)
|
||||
self.assertIsNone(self.conf.fu)
|
||||
self.assertIn('foo', self.fconf)
|
||||
self.assertIn('fu', self.fconf)
|
||||
self.assertEqual(2, len(self.fconf))
|
||||
self.assertEqual(3, len(self.fconf))
|
||||
self.assertIsNone(self.fconf.foo)
|
||||
self.assertIsNone(self.fconf.fu)
|
||||
|
||||
|
@ -1085,7 +1085,8 @@ GENERATOR_OPTS = {'format_': 'yaml',
|
||||
'namespace': ['test'],
|
||||
'output_file': None,
|
||||
'summarize': False,
|
||||
'wrap_width': 70}
|
||||
'wrap_width': 70,
|
||||
'config_source': []}
|
||||
|
||||
|
||||
class MachineReadableGeneratorTestCase(base.BaseTestCase):
|
||||
|
@ -10,59 +10,163 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import tempfile
|
||||
from requests import HTTPError
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_config import fixture
|
||||
from oslo_config import sources
|
||||
from oslo_config.sources import _uri
|
||||
|
||||
from oslotest import base
|
||||
import requests_mock
|
||||
|
||||
_GROUP = "group"
|
||||
_OPTIONS = "options"
|
||||
_DEFAULT = "DEFAULT"
|
||||
|
||||
_extra_ini_opt_group = "extra_conf_from_ini"
|
||||
_extra_conf_url = "https://oslo.config/extra.conf"
|
||||
class TestProcessingSources(base.BaseTestCase):
|
||||
|
||||
_conf_data = {
|
||||
_DEFAULT: {
|
||||
"config_sources": (cfg.StrOpt, _extra_ini_opt_group)
|
||||
# NOTE(dhellmann): These tests use the config() method of the
|
||||
# fixture because that invokes set_override() on the option. The
|
||||
# load_raw_values() method injects data underneath the option, but
|
||||
# only after invoking __call__ on the ConfigOpts instance, which
|
||||
# is when the 'config_source' option is processed.
|
||||
|
||||
def setUp(self):
|
||||
super(TestProcessingSources, self).setUp()
|
||||
self.conf = cfg.ConfigOpts()
|
||||
self.conf_fixture = self.useFixture(fixture.Config(self.conf))
|
||||
|
||||
def test_no_sources_default(self):
|
||||
with base.mock.patch.object(
|
||||
self.conf,
|
||||
'_open_source_from_opt_group') as open_source:
|
||||
open_source.side_effect = AssertionError('should not be called')
|
||||
self.conf([])
|
||||
|
||||
def test_no_sources(self):
|
||||
self.conf_fixture.config(
|
||||
config_source=[],
|
||||
)
|
||||
with base.mock.patch.object(
|
||||
self.conf,
|
||||
'_open_source_from_opt_group') as open_source:
|
||||
open_source.side_effect = AssertionError('should not be called')
|
||||
self.conf([])
|
||||
|
||||
def test_source_named(self):
|
||||
self.conf_fixture.config(
|
||||
config_source=['missing_source'],
|
||||
)
|
||||
with base.mock.patch.object(
|
||||
self.conf,
|
||||
'_open_source_from_opt_group') as open_source:
|
||||
self.conf([])
|
||||
open_source.assert_called_once_with('missing_source')
|
||||
|
||||
def test_multiple_sources_named(self):
|
||||
self.conf_fixture.config(
|
||||
config_source=['source1', 'source2'],
|
||||
)
|
||||
with base.mock.patch.object(
|
||||
self.conf,
|
||||
'_open_source_from_opt_group') as open_source:
|
||||
self.conf([])
|
||||
open_source.assert_has_calls([
|
||||
base.mock.call('source1'),
|
||||
base.mock.call('source2'),
|
||||
])
|
||||
|
||||
|
||||
class TestLoading(base.BaseTestCase):
|
||||
|
||||
# NOTE(dhellmann): These tests can use load_raw_values() because
|
||||
# they explicitly call _open_source_from_opt_group() after the
|
||||
# ConfigOpts setup is done in __call__().
|
||||
|
||||
def setUp(self):
|
||||
super(TestLoading, self).setUp()
|
||||
self.conf = cfg.ConfigOpts()
|
||||
self.conf_fixture = self.useFixture(fixture.Config(self.conf))
|
||||
|
||||
def test_source_missing(self):
|
||||
# The group being loaded does not exist at all.
|
||||
source = self.conf._open_source_from_opt_group('missing_source')
|
||||
self.assertIsNone(source)
|
||||
|
||||
def test_driver_missing(self):
|
||||
# The group exists and has other options, but does not specify
|
||||
# a driver.
|
||||
self.conf_fixture.load_raw_values(
|
||||
group='missing_driver',
|
||||
not_driver='foo',
|
||||
)
|
||||
source = self.conf._open_source_from_opt_group('missing_driver')
|
||||
self.assertIsNone(source)
|
||||
|
||||
def test_unknown_driver(self):
|
||||
# The group exists, but does not specify a valid driver.
|
||||
self.conf_fixture.load_raw_values(
|
||||
group='unknown_driver',
|
||||
driver='very_unlikely_to_exist_driver_name',
|
||||
)
|
||||
source = self.conf._open_source_from_opt_group('unknown_driver')
|
||||
self.assertIsNone(source)
|
||||
|
||||
|
||||
def make_uri(name):
|
||||
return "https://oslo.config/{}.conf".format(name)
|
||||
|
||||
|
||||
_extra_configs = {
|
||||
make_uri("types"): {
|
||||
"name": "types",
|
||||
"data": {
|
||||
"DEFAULT": {
|
||||
"foo": (cfg.StrOpt, "bar")
|
||||
},
|
||||
"test": {
|
||||
"opt_str": (cfg.StrOpt, "a nice string"),
|
||||
"opt_bool": (cfg.BoolOpt, True),
|
||||
"opt_int": (cfg.IntOpt, 42),
|
||||
"opt_float": (cfg.FloatOpt, 3.14),
|
||||
"opt_ip": (cfg.IPOpt, "127.0.0.1"),
|
||||
"opt_port": (cfg.PortOpt, 443),
|
||||
"opt_host": (cfg.HostnameOpt, "www.openstack.org"),
|
||||
"opt_uri": (cfg.URIOpt, "https://www.openstack.org"),
|
||||
"opt_multi": (cfg.MultiStrOpt, ["abc", "def", "ghi"])
|
||||
}
|
||||
}
|
||||
},
|
||||
_extra_ini_opt_group: {
|
||||
"driver": (cfg.StrOpt, "ini"),
|
||||
"uri": (cfg.URIOpt, _extra_conf_url)
|
||||
}
|
||||
}
|
||||
|
||||
_extra_conf_data = {
|
||||
_DEFAULT: {
|
||||
"foo": (cfg.StrOpt, "bar")
|
||||
make_uri("ini_1"): {
|
||||
"name": "ini_1",
|
||||
"data": {
|
||||
"DEFAULT": {
|
||||
"abc": (cfg.StrOpt, "abc")
|
||||
}
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"opt_str": (cfg.StrOpt, "a nice string"),
|
||||
"opt_bool": (cfg.BoolOpt, True),
|
||||
"opt_int": (cfg.IntOpt, 42),
|
||||
"opt_float": (cfg.FloatOpt, 3.14),
|
||||
"opt_ip": (cfg.IPOpt, "127.0.0.1"),
|
||||
"opt_port": (cfg.PortOpt, 443),
|
||||
"opt_host": (cfg.HostnameOpt, "www.openstack.org"),
|
||||
"opt_uri": (cfg.URIOpt, "https://www.openstack.org"),
|
||||
"opt_multi": (cfg.MultiStrOpt, ["abc", "def", "ghi"])
|
||||
make_uri("ini_2"): {
|
||||
"name": "ini_2",
|
||||
"data": {
|
||||
"DEFAULT": {
|
||||
"abc": (cfg.StrOpt, "foo"),
|
||||
"def": (cfg.StrOpt, "def")
|
||||
}
|
||||
}
|
||||
},
|
||||
make_uri("ini_3"): {
|
||||
"name": "ini_3",
|
||||
"data": {
|
||||
"DEFAULT": {
|
||||
"abc": (cfg.StrOpt, "bar"),
|
||||
"def": (cfg.StrOpt, "bar"),
|
||||
"ghi": (cfg.StrOpt, "ghi")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def register_opts(conf, opts):
|
||||
# 'g': group, 'o': option, and 't': type
|
||||
for g in opts.keys():
|
||||
for o, (t, _) in opts[g].items():
|
||||
try:
|
||||
conf.register_opt(t(o), g if g != "DEFAULT" else None)
|
||||
except cfg.DuplicateOptError:
|
||||
pass
|
||||
|
||||
|
||||
def opts_to_ini(opts):
|
||||
def opts_to_ini(uri, *args, **kwargs):
|
||||
opts = _extra_configs[uri]["data"]
|
||||
result = ""
|
||||
|
||||
# 'g': group, 'o': option, 't': type, and 'v': value
|
||||
@ -78,53 +182,100 @@ def opts_to_ini(opts):
|
||||
return result
|
||||
|
||||
|
||||
def mocked_get(*args, **kwargs):
|
||||
class MockResponse(object):
|
||||
def __init__(self, text_data, status_code):
|
||||
self.text = text_data
|
||||
self.status_code = status_code
|
||||
|
||||
def __enter__(self, *args, **kwargs):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def raise_for_status(self):
|
||||
if self.status_code != 200:
|
||||
raise
|
||||
|
||||
if args[0] in _extra_conf_url:
|
||||
return MockResponse(opts_to_ini(_extra_conf_data), 200)
|
||||
|
||||
return MockResponse(None, 404)
|
||||
|
||||
|
||||
class INISourceTestCase(base.BaseTestCase):
|
||||
class URISourceTestCase(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(INISourceTestCase, self).setUp()
|
||||
|
||||
super(URISourceTestCase, self).setUp()
|
||||
self.conf = cfg.ConfigOpts()
|
||||
self.conf_fixture = self.useFixture(fixture.Config(self.conf))
|
||||
|
||||
with tempfile.NamedTemporaryFile() as tmp_file:
|
||||
tmp_file.write(opts_to_ini(_conf_data).encode("utf-8"))
|
||||
tmp_file.flush()
|
||||
def _register_opts(self, opts):
|
||||
# 'g': group, 'o': option, and 't': type
|
||||
for g in opts.keys():
|
||||
for o, (t, _) in opts[g].items():
|
||||
self.conf.register_opt(t(o), g if g != "DEFAULT" else None)
|
||||
|
||||
self.conf(["--config-file", tmp_file.name])
|
||||
def test_incomplete_driver(self):
|
||||
# The group exists, but does not specify the
|
||||
# required options for this driver.
|
||||
self.conf_fixture.load_raw_values(
|
||||
group='incomplete_ini_driver',
|
||||
driver='remote_file',
|
||||
)
|
||||
source = self.conf._open_source_from_opt_group('incomplete_ini_driver')
|
||||
self.assertIsNone(source)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_fetch_uri(self, m):
|
||||
m.get("https://bad.uri", status_code=404)
|
||||
|
||||
self.assertRaises(
|
||||
HTTPError, _uri.URIConfigurationSource, "https://bad.uri")
|
||||
|
||||
m.get("https://good.uri", text="[DEFAULT]\nfoo=bar\n")
|
||||
source = _uri.URIConfigurationSource("https://good.uri")
|
||||
|
||||
self.assertEqual(
|
||||
"bar", source.get("DEFAULT", "foo", cfg.StrOpt("foo"))[0])
|
||||
|
||||
@base.mock.patch(
|
||||
"oslo_config.sources.ini.requests.get", side_effect=mocked_get)
|
||||
def test_configuration_source(self, mock_requests_get):
|
||||
driver = sources.ini.INIConfigurationSourceDriver()
|
||||
source = driver.open_source_from_opt_group(
|
||||
self.conf, _extra_ini_opt_group)
|
||||
"oslo_config.sources._uri.URIConfigurationSource._fetch_uri",
|
||||
side_effect=opts_to_ini)
|
||||
def test_configuration_source(self, mock_fetch_uri):
|
||||
group = "types"
|
||||
uri = make_uri(group)
|
||||
|
||||
self.conf_fixture.load_raw_values(
|
||||
group=group,
|
||||
driver='remote_file',
|
||||
uri=uri
|
||||
)
|
||||
self.conf_fixture.config(config_source=[group])
|
||||
|
||||
# testing driver loading
|
||||
self.assertEqual(self.conf._sources, [])
|
||||
self.conf._load_alternative_sources()
|
||||
self.assertEqual(type(self.conf._sources[0]),
|
||||
_uri.URIConfigurationSource)
|
||||
|
||||
source = self.conf._open_source_from_opt_group(group)
|
||||
|
||||
self._register_opts(_extra_configs[uri]["data"])
|
||||
|
||||
# non-existing option
|
||||
self.assertIs(sources._NoValue,
|
||||
source.get("DEFAULT", "bar", cfg.StrOpt("bar"))[0])
|
||||
|
||||
# 'g': group, 'o': option, 't': type, and 'v': value
|
||||
for g in _extra_conf_data:
|
||||
for o, (t, v) in _extra_conf_data[g].items():
|
||||
for g in _extra_configs[uri]["data"]:
|
||||
for o, (t, v) in _extra_configs[uri]["data"][g].items():
|
||||
self.assertEqual(str(v), str(source.get(g, o, t(o))[0]))
|
||||
self.assertEqual(v,
|
||||
self.conf[g][o] if g != "DEFAULT" else
|
||||
self.conf[o])
|
||||
|
||||
@base.mock.patch(
|
||||
"oslo_config.sources._uri.URIConfigurationSource._fetch_uri",
|
||||
side_effect=opts_to_ini)
|
||||
def test_multiple_configuration_sources(self, mock_fetch_uri):
|
||||
groups = ["ini_1", "ini_2", "ini_3"]
|
||||
uri = make_uri("ini_3")
|
||||
|
||||
for group in groups:
|
||||
self.conf_fixture.load_raw_values(
|
||||
group=group,
|
||||
driver='remote_file',
|
||||
uri=make_uri(group)
|
||||
)
|
||||
|
||||
self.conf_fixture.config(config_source=groups)
|
||||
self.conf._load_alternative_sources()
|
||||
|
||||
# ini_3 contains all options to be tested
|
||||
self._register_opts(_extra_configs[uri]["data"])
|
||||
|
||||
# where options are 'abc', 'def', and 'ghi'
|
||||
# if the extra configs are loaded in the right order
|
||||
# the option name and option value will match correctly
|
||||
for option in _extra_configs[uri]["data"]["DEFAULT"]:
|
||||
self.assertEqual(option, self.conf[option])
|
||||
|
@ -10,3 +10,4 @@ oslo.i18n>=3.15.3 # Apache-2.0
|
||||
rfc3986>=0.3.1 # Apache-2.0
|
||||
PyYAML>=3.12 # MIT
|
||||
enum34>=1.0.4;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD
|
||||
requests>=2.18.0 # Apache-2.0
|
||||
|
@ -32,6 +32,8 @@ console_scripts =
|
||||
oslo-config-generator = oslo_config.generator:main
|
||||
oslo.config.opts =
|
||||
oslo.config = oslo_config._list_opts:list_opts
|
||||
oslo.config.driver =
|
||||
remote_file = oslo_config.sources._uri:URIConfigurationSourceDriver
|
||||
|
||||
[wheel]
|
||||
universal = 1
|
||||
|
@ -24,6 +24,7 @@ sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
|
||||
|
||||
# mocking framework
|
||||
mock>=2.0.0 # BSD
|
||||
requests_mock>=1.5.0 # Apache-2.0
|
||||
|
||||
# Bandit security code scanner
|
||||
bandit>=1.1.0 # Apache-2.0
|
||||
|
Loading…
x
Reference in New Issue
Block a user