summaryrefslogtreecommitdiff
path: root/nova/utils.py
diff options
context:
space:
mode:
authorSean Mooney <work@seanmooney.info>2018-10-12 14:26:14 +0100
committerSean Mooney <work@seanmooney.info>2018-11-08 12:25:46 +0000
commit00d08a3288f61268f8a823991fadf0d83c36c04d (patch)
treeb5a2ac9d8faadabc421904de8cbfd0e23df061a3 /nova/utils.py
parent6e8a69daf14a15fe0b0633f21f4112f19487e9da (diff)
downloadnova-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.py43
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