Merge "add generator hook for apps to update option defaults"
This commit is contained in:
commit
1482e5c601
|
@ -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
|
||||
----------------------------------
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue