diff options
author | Jenkins <jenkins@review.openstack.org> | 2016-02-15 00:59:39 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2016-02-15 00:59:39 +0000 |
commit | 1482e5c601d0f75a13972cc808c5fc5a98513624 (patch) | |
tree | 0fcce57c3115af0e6d24217d4b1060796a4a9433 | |
parent | 3195602efde47b5600c0f624bf29ff80e0148475 (diff) | |
parent | b09ba4a3e044d163b129a20421684d557105c4fe (diff) | |
download | oslo-config-3.7.0.tar.gz |
Merge "add generator hook for apps to update option defaults"3.7.0
-rw-r--r-- | doc/source/generator.rst | 44 | ||||
-rw-r--r-- | oslo_config/generator.py | 24 | ||||
-rw-r--r-- | oslo_config/tests/test_generator.py | 39 |
3 files changed, 102 insertions, 5 deletions
diff --git a/doc/source/generator.rst b/doc/source/generator.rst index 79a5b89..c160ffd 100644 --- a/doc/source/generator.rst +++ b/doc/source/generator.rst @@ -58,11 +58,11 @@ group help. An example, using both styles:: # text is generated for the 'blaa' group. return [('blaa', opts1), (baz_group, opts2)] -You might choose to return a copy of the options so that the return value can't -be modified for nefarious purposes, though this is not strictly necessary:: +.. note:: - def list_opts(): - return [('blaa', copy.deepcopy(opts))] + You should return the original options, not a copy, because the + default update hooks depend on the original option object being + returned. The module holding the entry point *must* be importable, even if the dependencies of that module are not installed. For example, driver @@ -73,6 +73,42 @@ imported using :func:`oslo.utils.importutils.try_import` or the option definitions can be placed in a file that does not try to import the optional dependency. +Modifying Defaults from Other Namespaces +---------------------------------------- + +Occasionally applications need to override the defaults for options +defined in libraries. At runtime this is done using an API within the +library. Since the config generator cannot guarantee the order in +which namespaces will be imported, we can't ensure that application +code can change the option defaults before the generator loads the +options from a library. Instead, a separate optional processing hook +is provided for applications to register a function to update default +values after *all* options are loaded. + +The hooks are registered in a separate entry point namespace +(``oslo.config.opts.defaults``), using the same entry point name as +the application's ``list_opts()`` function. + +:: + + [entry_points] + oslo.config.opts.defaults = + keystone = keystone.common.config:update_opt_defaults + +The update function should take no arguments. It should invoke the +public :func:`set_defaults` functions in any libraries for which it +has option defaults to override, just as the application does during +its normal startup process. + +:: + + from oslo_log import log + + def update_opt_defaults(): + log.set_defaults( + default_log_levels=log.get_default_log_levels() + ['noisy=WARN'], + ) + Generating Multiple Sample Configs ---------------------------------- diff --git a/oslo_config/generator.py b/oslo_config/generator.py index 0e050f4..4ade144 100644 --- a/oslo_config/generator.py +++ b/oslo_config/generator.py @@ -281,6 +281,21 @@ def _get_raw_opts_loaders(namespaces): return [(e.name, e.plugin) for e in mgr] +def _get_opt_default_updaters(namespaces): + mgr = stevedore.named.NamedExtensionManager( + 'oslo.config.opts.defaults', + names=namespaces, + on_load_failure_callback=on_load_failure_callback, + invoke_on_load=False) + return [ep.plugin for ep in mgr] + + +def _update_defaults(namespaces): + "Let application hooks update defaults inside libraries." + for update in _get_opt_default_updaters(namespaces): + update() + + def _list_opts(namespaces): """List the options available via the given namespaces. @@ -289,9 +304,16 @@ def _list_opts(namespaces): :param namespaces: a list of namespaces registered under 'oslo.config.opts' :returns: a list of (namespace, [(group, [opt_1, opt_2])]) tuples """ + # Load the functions to get the options. + loaders = _get_raw_opts_loaders(namespaces) + # Update defaults, which might change global settings in library + # modules. + _update_defaults(namespaces) + # Ask for the option definitions. At this point any global default + # changes made by the updaters should be in effect. opts = [ (namespace, loader()) - for namespace, loader in _get_raw_opts_loaders(namespaces) + for namespace, loader in loaders ] return _cleanup_opts(opts) diff --git a/oslo_config/tests/test_generator.py b/oslo_config/tests/test_generator.py index a81fd41..3127a99 100644 --- a/oslo_config/tests/test_generator.py +++ b/oslo_config/tests/test_generator.py @@ -957,4 +957,43 @@ class GeneratorRaiseErrorTestCase(base.BaseTestCase): self.assertRaises(cfg.RequiredOptError, generator.main, []) +class ChangeDefaultsTestCase(base.BaseTestCase): + + @mock.patch.object(generator, '_get_opt_default_updaters') + @mock.patch.object(generator, '_get_raw_opts_loaders') + def test_no_modifiers_registered(self, raw_opts_loaders, get_updaters): + orig_opt = cfg.StrOpt('foo', default='bar') + raw_opts_loaders.return_value = [ + ('namespace', lambda: [(None, [orig_opt])]), + ] + get_updaters.return_value = [] + + opts = generator._list_opts(['namespace']) + # NOTE(dhellmann): Who designed this data structure? + the_opt = opts[0][1][0][1][0] + + self.assertEqual('bar', the_opt.default) + self.assertIs(orig_opt, the_opt) + + @mock.patch.object(generator, '_get_opt_default_updaters') + @mock.patch.object(generator, '_get_raw_opts_loaders') + def test_change_default(self, raw_opts_loaders, get_updaters): + orig_opt = cfg.StrOpt('foo', default='bar') + raw_opts_loaders.return_value = [ + ('namespace', lambda: [(None, [orig_opt])]), + ] + + def updater(): + cfg.set_defaults([orig_opt], foo='blah') + + get_updaters.return_value = [updater] + + opts = generator._list_opts(['namespace']) + # NOTE(dhellmann): Who designed this data structure? + the_opt = opts[0][1][0][1][0] + + self.assertEqual('blah', the_opt.default) + self.assertIs(orig_opt, the_opt) + + GeneratorTestCase.generate_scenarios() |