summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2016-02-15 00:59:39 +0000
committerGerrit Code Review <review@openstack.org>2016-02-15 00:59:39 +0000
commit1482e5c601d0f75a13972cc808c5fc5a98513624 (patch)
tree0fcce57c3115af0e6d24217d4b1060796a4a9433
parent3195602efde47b5600c0f624bf29ff80e0148475 (diff)
parentb09ba4a3e044d163b129a20421684d557105c4fe (diff)
downloadoslo-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.rst44
-rw-r--r--oslo_config/generator.py24
-rw-r--r--oslo_config/tests/test_generator.py39
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()