diff options
author | Sean Mooney <work@seanmooney.info> | 2018-10-12 14:26:14 +0100 |
---|---|---|
committer | Sean Mooney <work@seanmooney.info> | 2018-11-08 12:25:46 +0000 |
commit | 00d08a3288f61268f8a823991fadf0d83c36c04d (patch) | |
tree | b5a2ac9d8faadabc421904de8cbfd0e23df061a3 /nova/utils.py | |
parent | 6e8a69daf14a15fe0b0633f21f4112f19487e9da (diff) | |
download | nova-00d08a3288f61268f8a823991fadf0d83c36c04d.tar.gz |
Harden placement init under wsgi
- This change tries to address an edge case discovered
when running placement under mod_wsgi where
if the placement wsgi application is re-initialize the db_api
configure method attempts to reconfigure a started transaction
factory.
- This since oslo.db transaction factories do not support
reconfiguration at runtime this result in an exception being
raised preventing reloading of the Placement API without
restarting apache to force mod_wsgi to recreate the
python interpreter.
- This change introduces a run once decorator to allow annotating
functions that should only be executed once for the lifetime fo
an interpreter.
- This change applies the run_once decorator to the db_api configure
method, to suppress the attempt to reconfigure the current
TransactionFactory on application reload.
Co-Authored-By: Balazs Gibizer <balazs.gibizer@ericsson.com>
Closes-Bug: #1799246
Related-Bug: #1784155
Change-Id: I704196711d30c1124e713ac31111a8ea6fa2f1ba
Diffstat (limited to 'nova/utils.py')
-rw-r--r-- | nova/utils.py | 43 |
1 files changed, 43 insertions, 0 deletions
diff --git a/nova/utils.py b/nova/utils.py index f3eb785702..a890d3ea67 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -1312,3 +1312,46 @@ else: def nested_contexts(*contexts): with contextlib.ExitStack() as stack: yield [stack.enter_context(c) for c in contexts] + + +def run_once(message, logger, cleanup=None): + """This is a utility function decorator to ensure a function + is run once and only once in an interpreter instance. + The decorated function object can be reset by calling its + reset function. All exceptions raised by the wrapped function, + logger and cleanup function will be propagated to the caller. + """ + def outer_wrapper(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + if not wrapper.called: + # Note(sean-k-mooney): the called state is always + # updated even if the wrapped function completes + # by raising an exception. If the caller catches + # the exception it is their responsibility to call + # reset if they want to re-execute the wrapped function. + try: + return func(*args, **kwargs) + finally: + wrapper.called = True + else: + logger(message) + + wrapper.called = False + + def reset(wrapper, *args, **kwargs): + # Note(sean-k-mooney): we conditionally call the + # cleanup function if one is provided only when the + # wrapped function has been called previously. We catch + # and reraise any exception that may be raised and update + # the called state in a finally block to ensure its + # always updated if reset is called. + try: + if cleanup and wrapper.called: + return cleanup(*args, **kwargs) + finally: + wrapper.called = False + + wrapper.reset = functools.partial(reset, wrapper) + return wrapper + return outer_wrapper |