summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrant Knudson <bknudson@us.ibm.com>2013-07-19 16:40:35 -0500
committerBrant Knudson <bknudson@us.ibm.com>2013-07-31 12:03:41 -0500
commit3c6cc9e838cacd1f7c0a3cfc89b0f66b23851803 (patch)
treeb56b1eb1685222bedd149adf1793e459f3c5cbbd
parent19081b834991d263d84c761dcf422a8c9faf40a1 (diff)
downloadkeystone-3c6cc9e838cacd1f7c0a3cfc89b0f66b23851803.tar.gz
keystone-3c6cc9e838cacd1f7c0a3cfc89b0f66b23851803.tar.xz
keystone-3c6cc9e838cacd1f7c0a3cfc89b0f66b23851803.zip
Handle circular dependencies
The dependency injection code doesn't handle circular dependencies. This change makes it so that the dependency injection code allows circular dependencies. Part of fix for bug 1204605 Change-Id: I8de166a352ac727c7ddf27bae420b7c7ab22415f
-rw-r--r--keystone/common/dependency.py33
-rw-r--r--keystone/service.py3
-rw-r--r--keystone/test.py2
-rw-r--r--tests/test_injection.py27
4 files changed, 64 insertions, 1 deletions
diff --git a/keystone/common/dependency.py b/keystone/common/dependency.py
index 3ed261cc..a640031d 100644
--- a/keystone/common/dependency.py
+++ b/keystone/common/dependency.py
@@ -16,6 +16,8 @@
REGISTRY = {}
+_future_dependencies = {}
+
class UnresolvableDependencyException(Exception):
def __init__(self, name):
@@ -32,6 +34,8 @@ def provider(name):
init(self, *args, **kwargs)
REGISTRY[name] = self
+ resolve_future_dependencies(name)
+
return __wrapped_init__
cls.__init__ = wrapped(cls.__init__)
@@ -48,7 +52,13 @@ def requires(*dependencies):
for dependency in self._dependencies:
if dependency not in REGISTRY:
- raise UnresolvableDependencyException(dependency)
+ if dependency in _future_dependencies:
+ _future_dependencies[dependency] += [self]
+ else:
+ _future_dependencies[dependency] = [self]
+
+ continue
+
setattr(self, dependency, REGISTRY[dependency])
def wrapped(cls):
@@ -67,6 +77,26 @@ def requires(*dependencies):
return wrapped
+def resolve_future_dependencies(provider_name=None):
+ if provider_name:
+ targets = _future_dependencies.pop(provider_name, [])
+
+ for target in targets:
+ setattr(target, provider_name, REGISTRY[provider_name])
+
+ return
+
+ try:
+ for dependency, targets in _future_dependencies.iteritems():
+ if dependency not in REGISTRY:
+ raise UnresolvableDependencyException(dependency)
+
+ for target in targets:
+ setattr(target, dependency, REGISTRY[dependency])
+ finally:
+ _future_dependencies.clear()
+
+
def reset():
"""Reset the registry of providers.
@@ -75,3 +105,4 @@ def reset():
"""
REGISTRY.clear()
+ _future_dependencies.clear()
diff --git a/keystone/service.py b/keystone/service.py
index 6b0c3708..775dfe5d 100644
--- a/keystone/service.py
+++ b/keystone/service.py
@@ -18,6 +18,7 @@ import routes
from keystone import auth
from keystone import catalog
+from keystone.common import dependency
from keystone.common import logging
from keystone.common import wsgi
from keystone import config
@@ -44,6 +45,8 @@ DRIVERS = dict(
trust_api=trust.Manager(),
token_provider_api=token.provider.Manager())
+dependency.resolve_future_dependencies()
+
@logging.fail_gracefully
def public_app_factory(global_conf, **local_conf):
diff --git a/keystone/test.py b/keystone/test.py
index d06ea4c5..55aca3c6 100644
--- a/keystone/test.py
+++ b/keystone/test.py
@@ -271,6 +271,8 @@ class TestCase(NoModule, unittest.TestCase):
manager_name = '%s_api' % manager.__name__.split('.')[-1]
setattr(self, manager_name, manager.Manager())
+ dependency.resolve_future_dependencies()
+
def load_fixtures(self, fixtures):
"""Hacky basic and naive fixture loading based on a python module.
diff --git a/tests/test_injection.py b/tests/test_injection.py
index 08ccd7c7..36cd0126 100644
--- a/tests/test_injection.py
+++ b/tests/test_injection.py
@@ -21,6 +21,10 @@ from keystone.common import dependency
class TestDependencyInjection(unittest.TestCase):
+ def tearDown(self):
+ dependency.reset()
+ super(TestDependencyInjection, self).tearDown()
+
def test_dependency_injection(self):
class Interface(object):
def do_work(self):
@@ -165,6 +169,29 @@ class TestDependencyInjection(unittest.TestCase):
with self.assertRaises(dependency.UnresolvableDependencyException):
Consumer()
+ dependency.resolve_future_dependencies()
+
+ def test_circular_dependency(self):
+ p1_name = uuid.uuid4().hex
+ p2_name = uuid.uuid4().hex
+
+ @dependency.provider(p1_name)
+ @dependency.requires(p2_name)
+ class P1(object):
+ pass
+
+ @dependency.provider(p2_name)
+ @dependency.requires(p1_name)
+ class P2(object):
+ pass
+
+ p1 = P1()
+ p2 = P2()
+
+ dependency.resolve_future_dependencies()
+
+ self.assertIs(getattr(p1, p2_name), p2)
+ self.assertIs(getattr(p2, p1_name), p1)
def test_reset(self):
# Can reset the registry of providers.