Skip to content

Commit

Permalink
Add callbacks for set_global_engine
Browse files Browse the repository at this point in the history
This adds functionality where a class can monitor for the global
engine changing.

This is useful for a class that caches the global engine and
wants to know when its cached global engine isn't valid anymore.

Part of fix for bug 1179259

Change-Id: I5736a05308c63de9fccb8af7720ddd70530f4270
  • Loading branch information
Brant Knudson authored and openstack-gerrit committed Jul 3, 2013
1 parent 62d948a commit eb930fd
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 1 deletion.
44 changes: 43 additions & 1 deletion keystone/common/sql/core.py
Expand Up @@ -32,10 +32,12 @@
from keystone.openstack.common import jsonutils


LOG = logging.getLogger(__name__)
CONF = config.CONF

# maintain a single engine reference for sqlite in-memory
# maintain a single engine reference for sqlalchemy engine
GLOBAL_ENGINE = None
GLOBAL_ENGINE_CALLBACKS = set()


ModelBase = declarative.declarative_base()
Expand Down Expand Up @@ -95,9 +97,49 @@ def initialize(self, *args, **kwargs):


def set_global_engine(engine):
"""Set the global engine.
This sets the current global engine, which is returned by
Base.get_engine(allow_global_engine=True).
When the global engine is changed, all of the callbacks registered via
register_global_engine_callback since the last time set_global_engine was
changed are called. The callback functions are invoked with no arguments.
"""

global GLOBAL_ENGINE
global GLOBAL_ENGINE_CALLBACKS

if engine is GLOBAL_ENGINE:
# It's the same engine so nothing to do.
return

GLOBAL_ENGINE = engine

cbs = GLOBAL_ENGINE_CALLBACKS
GLOBAL_ENGINE_CALLBACKS = set()
for cb in cbs:
try:
cb()
except Exception:
LOG.exception(_("Global engine callback raised."))
# Just logging the exception so can process other callbacks.


def register_global_engine_callback(cb_fn):
"""Register a function to be called when the global engine is set.
Note that the callback will be called only once or not at all, so to get
called each time the global engine is changed the function must be
re-registered.
"""

global GLOBAL_ENGINE_CALLBACKS

GLOBAL_ENGINE_CALLBACKS.add(cb_fn)


# Special Fields
class JsonBlob(sql_types.TypeDecorator):
Expand Down
124 changes: 124 additions & 0 deletions tests/test_sql_core.py
Expand Up @@ -17,6 +17,130 @@
from keystone import test


class CallbackMonitor:
def __init__(self, expect_called=True, raise_=False):
self.expect_called = expect_called
self.called = False
self._complete = False
self._raise = raise_

def call_this(self):
if self._complete:
return

if not self.expect_called:
raise Exception("Did not expect callback.")

if self.called:
raise Exception("Callback already called.")

self.called = True

if self._raise:
raise Exception("When called, raises.")

def check(self):
if self.expect_called:
if not self.called:
raise Exception("Expected function to be called.")
self._complete = True


class TestGlobalEngine(test.TestCase):

def tearDown(self):
sql.set_global_engine(None)
super(TestGlobalEngine, self).tearDown()

def test_notify_on_set(self):
# If call sql.set_global_engine(), notify callbacks get called.

cb_mon = CallbackMonitor()

sql.register_global_engine_callback(cb_mon.call_this)
fake_engine = object()
sql.set_global_engine(fake_engine)

cb_mon.check()

def test_multi_notify(self):
# You can also set multiple notify callbacks and they each get called.

cb_mon1 = CallbackMonitor()
cb_mon2 = CallbackMonitor()

sql.register_global_engine_callback(cb_mon1.call_this)
sql.register_global_engine_callback(cb_mon2.call_this)

fake_engine = object()
sql.set_global_engine(fake_engine)

cb_mon1.check()
cb_mon2.check()

def test_notify_once(self):
# After a callback is called, it's not called again if set global
# engine again.

cb_mon = CallbackMonitor()

sql.register_global_engine_callback(cb_mon.call_this)
fake_engine = object()
sql.set_global_engine(fake_engine)

fake_engine = object()
# Note that cb_mon.call_this would raise if it's called again.
sql.set_global_engine(fake_engine)

cb_mon.check()

def test_set_same_engine(self):
# If you set the global engine to the same engine, callbacks don't get
# called.

fake_engine = object()

sql.set_global_engine(fake_engine)

cb_mon = CallbackMonitor(expect_called=False)
sql.register_global_engine_callback(cb_mon.call_this)

# Note that cb_mon.call_this would raise if it's called.
sql.set_global_engine(fake_engine)

cb_mon.check()

def test_notify_register_same(self):
# If you register the same callback twice, only gets called once.
cb_mon = CallbackMonitor()

sql.register_global_engine_callback(cb_mon.call_this)
sql.register_global_engine_callback(cb_mon.call_this)

fake_engine = object()
# Note that cb_mon.call_this would raise if it's called twice.
sql.set_global_engine(fake_engine)

cb_mon.check()

def test_callback_throws(self):
# If a callback function raises,
# a) the caller doesn't know about it,
# b) other callbacks are still called

cb_mon1 = CallbackMonitor(raise_=True)
cb_mon2 = CallbackMonitor()

sql.register_global_engine_callback(cb_mon1.call_this)
sql.register_global_engine_callback(cb_mon2.call_this)

fake_engine = object()
sql.set_global_engine(fake_engine)

cb_mon1.check()
cb_mon2.check()


class TestBase(test.TestCase):

def tearDown(self):
Expand Down

0 comments on commit eb930fd

Please sign in to comment.