Merge "add generator hook for apps to update option defaults"

This commit is contained in:
Jenkins 2016-02-15 00:59:39 +00:00 committed by Gerrit Code Review
commit 1482e5c601
3 changed files with 102 additions and 5 deletions

View File

@ -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
----------------------------------

View File

@ -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)

View File

@ -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()