diff options
| author | Dolph Mathews <dolph.mathews@gmail.com> | 2012-12-19 10:04:21 -0600 |
|---|---|---|
| committer | Dolph Mathews <dolph.mathews@gmail.com> | 2012-12-21 11:57:44 -0600 |
| commit | 03eb2801a3ad38a39e9cf127c05ab710bf38ee1d (patch) | |
| tree | c91ae80657c574cabcecf5abfa78f3940392c517 | |
| parent | ac2d92ca2eea1070f765be320acb62fd5bef6dd3 (diff) | |
| download | keystone-03eb2801a3ad38a39e9cf127c05ab710bf38ee1d.tar.gz keystone-03eb2801a3ad38a39e9cf127c05ab710bf38ee1d.tar.xz keystone-03eb2801a3ad38a39e9cf127c05ab710bf38ee1d.zip | |
Driver registry
Uses automatic dependency injection to provide controllers with driver
interfaces (identity_api, token_api, etc).
See tests/test_injection.py for a self-contained example.
Change-Id: I255087de534292fbf57a45b19f97488f831f607c
| -rw-r--r-- | keystone/catalog/controllers.py | 11 | ||||
| -rw-r--r-- | keystone/catalog/core.py | 2 | ||||
| -rw-r--r-- | keystone/catalog/routers.py | 8 | ||||
| -rw-r--r-- | keystone/common/controller.py | 10 | ||||
| -rw-r--r-- | keystone/common/dependency.py | 67 | ||||
| -rw-r--r-- | keystone/contrib/admin_crud/core.py | 17 | ||||
| -rw-r--r-- | keystone/contrib/ec2/core.py | 17 | ||||
| -rw-r--r-- | keystone/contrib/user_crud/core.py | 11 | ||||
| -rw-r--r-- | keystone/identity/controllers.py | 4 | ||||
| -rw-r--r-- | keystone/identity/core.py | 8 | ||||
| -rw-r--r-- | keystone/identity/routers.py | 28 | ||||
| -rw-r--r-- | keystone/policy/core.py | 2 | ||||
| -rw-r--r-- | keystone/policy/routers.py | 4 | ||||
| -rw-r--r-- | keystone/routers.py | 13 | ||||
| -rw-r--r-- | keystone/service.py | 32 | ||||
| -rw-r--r-- | keystone/test.py | 22 | ||||
| -rw-r--r-- | keystone/token/controllers.py | 2 | ||||
| -rw-r--r-- | keystone/token/core.py | 4 | ||||
| -rw-r--r-- | keystone/token/routers.py | 6 | ||||
| -rw-r--r-- | tests/test_auth.py | 14 | ||||
| -rw-r--r-- | tests/test_injection.py | 141 |
21 files changed, 297 insertions, 126 deletions
diff --git a/keystone/catalog/controllers.py b/keystone/catalog/controllers.py index 42ba4ed7..78afffe7 100644 --- a/keystone/catalog/controllers.py +++ b/keystone/catalog/controllers.py @@ -17,20 +17,16 @@ import uuid -from keystone.catalog import core from keystone.common import controller -from keystone.common import wsgi +from keystone.common import dependency from keystone import exception -from keystone import identity -from keystone import policy -from keystone import token INTERFACES = ['public', 'internal', 'admin'] +@dependency.requires('catalog_api') class Service(controller.V2Controller): - def get_services(self, context): self.assert_admin(context) service_list = self.catalog_api.list_services(context) @@ -55,6 +51,7 @@ class Service(controller.V2Controller): return {'OS-KSADM:service': new_service_ref} +@dependency.requires('catalog_api') class Endpoint(controller.V2Controller): def get_endpoints(self, context): """Merge matching v3 endpoint refs into legacy refs.""" @@ -115,6 +112,7 @@ class Endpoint(controller.V2Controller): raise exception.EndpointNotFound(endpoint_id=endpoint_id) +@dependency.requires('catalog_api') class ServiceV3(controller.V3Controller): @controller.protected def create_service(self, context, service): @@ -147,6 +145,7 @@ class ServiceV3(controller.V3Controller): return self.catalog_api.delete_service(context, service_id) +@dependency.requires('catalog_api') class EndpointV3(controller.V3Controller): @controller.protected def create_endpoint(self, context, endpoint): diff --git a/keystone/catalog/core.py b/keystone/catalog/core.py index 7a8e8be7..7afeed95 100644 --- a/keystone/catalog/core.py +++ b/keystone/catalog/core.py @@ -17,6 +17,7 @@ """Main entry point into the Catalog service.""" +from keystone.common import dependency from keystone.common import logging from keystone.common import manager from keystone import config @@ -51,6 +52,7 @@ def format_url(url, data): return result +@dependency.provider('catalog_api') class Manager(manager.Manager): """Default pivot point for the Catalog backend. diff --git a/keystone/catalog/routers.py b/keystone/catalog/routers.py index f0c547f8..b3af4bc1 100644 --- a/keystone/catalog/routers.py +++ b/keystone/catalog/routers.py @@ -13,13 +13,13 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. + from keystone.catalog import controllers from keystone.common import router -from keystone.common import wsgi -def append_v3_routers(mapper, routers, apis): - routers.append(router.Router(controllers.ServiceV3(**apis), +def append_v3_routers(mapper, routers): + routers.append(router.Router(controllers.ServiceV3(), 'services', 'service')) - routers.append(router.Router(controllers.EndpointV3(**apis), + routers.append(router.Router(controllers.EndpointV3(), 'endpoints', 'endpoint')) diff --git a/keystone/common/controller.py b/keystone/common/controller.py index 5f351411..ee7ce5b5 100644 --- a/keystone/common/controller.py +++ b/keystone/common/controller.py @@ -1,6 +1,7 @@ import uuid import functools +from keystone.common import dependency from keystone.common import logging from keystone.common import wsgi from keystone import exception @@ -55,15 +56,10 @@ def protected(f): return wrapper +@dependency.requires('identity_api', 'policy_api', 'token_api') class V2Controller(wsgi.Application): """Base controller class for Identity API v2.""" - - def __init__(self, catalog_api, identity_api, policy_api, token_api): - self.catalog_api = catalog_api - self.identity_api = identity_api - self.policy_api = policy_api - self.token_api = token_api - super(V2Controller, self).__init__() + pass class V3Controller(V2Controller): diff --git a/keystone/common/dependency.py b/keystone/common/dependency.py new file mode 100644 index 00000000..dc3e4ac4 --- /dev/null +++ b/keystone/common/dependency.py @@ -0,0 +1,67 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +REGISTRY = {} + + +class UnresolvableDependencyException(Exception): + def __init__(self, name): + msg = 'Unregistered dependency: %s' % name + super(UnresolvableDependencyException, self).__init__(msg) + + +def provider(name): + """Register the wrapped dependency provider under the specified name.""" + def wrapper(cls): + def wrapped(init): + def __wrapped_init__(self, *args, **kwargs): + """Initialize the wrapped object and add it to the registry.""" + init(self, *args, **kwargs) + REGISTRY[name] = self + + return __wrapped_init__ + + cls.__init__ = wrapped(cls.__init__) + return cls + + return wrapper + + +def requires(*dependencies): + """Inject specified dependencies from the registry into the instance.""" + def wrapper(self, *args, **kwargs): + """Inject each dependency from the registry.""" + self.__wrapped_init__(*args, **kwargs) + + for dependency in self._dependencies: + if dependency not in REGISTRY: + raise UnresolvableDependencyException(dependency) + setattr(self, dependency, REGISTRY[dependency]) + + def wrapped(cls): + """Note the required dependencies on the object for later injection. + + The dependencies of the parent class are combined with that of the + child class to create a new set of dependencies. + """ + existing_dependencies = getattr(cls, '_dependencies', set()) + cls._dependencies = existing_dependencies.union(dependencies) + if not hasattr(cls, '__wrapped_init__'): + cls.__wrapped_init__ = cls.__init__ + cls.__init__ = wrapper + return cls + + return wrapped diff --git a/keystone/contrib/admin_crud/core.py b/keystone/contrib/admin_crud/core.py index fe8c33b0..a5cfe392 100644 --- a/keystone/contrib/admin_crud/core.py +++ b/keystone/contrib/admin_crud/core.py @@ -16,8 +16,6 @@ from keystone import catalog from keystone.common import wsgi from keystone import identity -from keystone import policy -from keystone import token class CrudExtension(wsgi.ExtensionRouter): @@ -28,16 +26,11 @@ class CrudExtension(wsgi.ExtensionRouter): """ def add_routes(self, mapper): - apis = dict(catalog_api=catalog.Manager(), - identity_api=identity.Manager(), - policy_api=policy.Manager(), - token_api=token.Manager()) - - tenant_controller = identity.controllers.Tenant(**apis) - user_controller = identity.controllers.User(**apis) - role_controller = identity.controllers.Role(**apis) - service_controller = catalog.controllers.Service(**apis) - endpoint_controller = catalog.controllers.Endpoint(**apis) + tenant_controller = identity.controllers.Tenant() + user_controller = identity.controllers.User() + role_controller = identity.controllers.Role() + service_controller = catalog.controllers.Service() + endpoint_controller = catalog.controllers.Endpoint() # Tenant Operations mapper.connect( diff --git a/keystone/contrib/ec2/core.py b/keystone/contrib/ec2/core.py index 078a845c..baef0bd9 100644 --- a/keystone/contrib/ec2/core.py +++ b/keystone/contrib/ec2/core.py @@ -36,20 +36,20 @@ glance to list images needed to perform the requested task. import uuid -from keystone import catalog +from keystone.common import controller +from keystone.common import dependency from keystone.common import manager from keystone.common import utils from keystone.common import wsgi from keystone import config from keystone import exception -from keystone import identity -from keystone import policy from keystone import token CONF = config.CONF +@dependency.provider('ec2_api') class Manager(manager.Manager): """Default pivot point for the EC2 Credentials backend. @@ -95,15 +95,8 @@ class Ec2Extension(wsgi.ExtensionRouter): conditions=dict(method=['DELETE'])) -class Ec2Controller(wsgi.Application): - def __init__(self): - self.catalog_api = catalog.Manager() - self.identity_api = identity.Manager() - self.token_api = token.Manager() - self.policy_api = policy.Manager() - self.ec2_api = Manager() - super(Ec2Controller, self).__init__() - +@dependency.requires('catalog_api', 'ec2_api') +class Ec2Controller(controller.V2Controller): def check_signature(self, creds_ref, credentials): signer = utils.Ec2Signer(creds_ref['secret']) signature = signer.generate(credentials) diff --git a/keystone/contrib/user_crud/core.py b/keystone/contrib/user_crud/core.py index e1238509..ae9b4c59 100644 --- a/keystone/contrib/user_crud/core.py +++ b/keystone/contrib/user_crud/core.py @@ -18,13 +18,9 @@ import copy import uuid from keystone import exception -from keystone.common import controller from keystone.common import logging from keystone.common import wsgi -from keystone import catalog from keystone import identity -from keystone import policy -from keystone import token LOG = logging.getLogger(__name__) @@ -81,12 +77,7 @@ class CrudExtension(wsgi.ExtensionRouter): """ def add_routes(self, mapper): - apis = dict(catalog_api=catalog.Manager(), - identity_api=identity.Manager(), - policy_api=policy.Manager(), - token_api=token.Manager()) - - user_controller = UserController(**apis) + user_controller = UserController() mapper.connect('/OS-KSCRUD/users/{user_id}', controller=user_controller, diff --git a/keystone/identity/controllers.py b/keystone/identity/controllers.py index 6ba04a92..4e325264 100644 --- a/keystone/identity/controllers.py +++ b/keystone/identity/controllers.py @@ -22,11 +22,7 @@ import uuid from keystone.common import controller from keystone.common import logging -from keystone.common import wsgi from keystone import exception -from keystone.identity import core -from keystone import policy -from keystone import token LOG = logging.getLogger(__name__) diff --git a/keystone/identity/core.py b/keystone/identity/core.py index e189df23..6d54f500 100644 --- a/keystone/identity/core.py +++ b/keystone/identity/core.py @@ -16,14 +16,9 @@ """Main entry point into the Identity service.""" -import urllib -import urlparse -import uuid - -from keystone.common import controller +from keystone.common import dependency from keystone.common import logging from keystone.common import manager -from keystone.common import wsgi from keystone import config from keystone import exception @@ -51,6 +46,7 @@ def filter_user(user_ref): return user_ref +@dependency.provider('identity_api') class Manager(manager.Manager): """Default pivot point for the Identity backend. diff --git a/keystone/identity/routers.py b/keystone/identity/routers.py index 8e251805..e4385c33 100644 --- a/keystone/identity/routers.py +++ b/keystone/identity/routers.py @@ -20,12 +20,8 @@ from keystone.common import router class Public(wsgi.ComposableRouter): - def __init__(self, apis): - self.apis = apis - super(Public, self).__init__() - def add_routes(self, mapper): - tenant_controller = controllers.Tenant(**self.apis) + tenant_controller = controllers.Tenant() mapper.connect('/tenants', controller=tenant_controller, action='get_tenants_for_token', @@ -33,13 +29,9 @@ class Public(wsgi.ComposableRouter): class Admin(wsgi.ComposableRouter): - def __init__(self, apis): - self.apis = apis - super(Admin, self).__init__() - def add_routes(self, mapper): # Tenant Operations - tenant_controller = controllers.Tenant(**self.apis) + tenant_controller = controllers.Tenant() mapper.connect('/tenants', controller=tenant_controller, action='get_all_tenants', @@ -50,14 +42,14 @@ class Admin(wsgi.ComposableRouter): conditions=dict(method=['GET'])) # User Operations - user_controller = controllers.User(**self.apis) + user_controller = controllers.User() mapper.connect('/users/{user_id}', controller=user_controller, action='get_user', conditions=dict(method=['GET'])) # Role Operations - roles_controller = controllers.Role(**self.apis) + roles_controller = controllers.Role() mapper.connect('/tenants/{tenant_id}/users/{user_id}/roles', controller=roles_controller, action='get_user_roles', @@ -68,11 +60,11 @@ class Admin(wsgi.ComposableRouter): conditions=dict(method=['GET'])) -def append_v3_routers(mapper, routers, apis): +def append_v3_routers(mapper, routers): routers.append( - router.Router(controllers.DomainV3(**apis), + router.Router(controllers.DomainV3(), 'domains', 'domain')) - project_controller = controllers.ProjectV3(**apis) + project_controller = controllers.ProjectV3() routers.append( router.Router(project_controller, 'projects', 'project')) @@ -81,12 +73,12 @@ def append_v3_routers(mapper, routers, apis): action='list_user_projects', conditions=dict(method=['GET'])) routers.append( - router.Router(controllers.UserV3(**apis), + router.Router(controllers.UserV3(), 'users', 'user')) routers.append( - router.Router(controllers.CredentialV3(**apis), + router.Router(controllers.CredentialV3(), 'credentials', 'credential')) - role_controller = controllers.RoleV3(**apis) + role_controller = controllers.RoleV3() routers.append(router.Router(role_controller, 'roles', 'role')) mapper.connect('/projects/{project_id}/users/{user_id}/roles/{role_id}', controller=role_controller, diff --git a/keystone/policy/core.py b/keystone/policy/core.py index a8d01f2f..e3abb3ba 100644 --- a/keystone/policy/core.py +++ b/keystone/policy/core.py @@ -17,6 +17,7 @@ """Main entry point into the Policy service.""" +from keystone.common import dependency from keystone.common import manager from keystone import config from keystone import exception @@ -25,6 +26,7 @@ from keystone import exception CONF = config.CONF +@dependency.provider('policy_api') class Manager(manager.Manager): """Default pivot point for the Policy backend. diff --git a/keystone/policy/routers.py b/keystone/policy/routers.py index 6c050757..dbb53e9c 100644 --- a/keystone/policy/routers.py +++ b/keystone/policy/routers.py @@ -17,6 +17,6 @@ from keystone.policy import controllers from keystone.common import router -def append_v3_routers(mapper, routers, apis): - policy_controller = controllers.PolicyV3(**apis) +def append_v3_routers(mapper, routers): + policy_controller = controllers.PolicyV3() routers.append(router.Router(policy_controller, 'policies', 'policy')) diff --git a/keystone/routers.py b/keystone/routers.py index fa980c2c..83f277dd 100644 --- a/keystone/routers.py +++ b/keystone/routers.py @@ -14,17 +14,18 @@ # License for the specific language governing permissions and limitations # under the License. """ -The only types of routers in this file should be ComposingRouters. -The routers for the submodules should be in the module specific router files -for example, the Composable Router for identity belongs in -keystone/identity/routers.py +The only types of routers in this file should be ``ComposingRouters``. + +The routers for the backends should be in the backend-specific router modules. +For example, the ``ComposableRouter`` for ``identity`` belongs in:: + + keystone.identity.routers + """ from keystone.common import wsgi -from keystone import catalog from keystone import controllers -from keystone import exception class Extension(wsgi.ComposableRouter): diff --git a/keystone/service.py b/keystone/service.py index 33bc4cc6..cf245d3e 100644 --- a/keystone/service.py +++ b/keystone/service.py @@ -17,22 +17,22 @@ import routes from keystone import catalog +from keystone.contrib import ec2 from keystone.common import logging from keystone.common import wsgi -from keystone import exception from keystone import identity from keystone import policy from keystone import routers from keystone import token -LOG = logging.getLogger(__name__) - -def _apis(): - return dict(catalog_api=catalog.Manager(), - identity_api=identity.Manager(), - policy_api=policy.Manager(), - token_api=token.Manager()) +LOG = logging.getLogger(__name__) +DRIVERS = dict( + catalog_api=catalog.Manager(), + ec2_api=ec2.Manager(), + identity_api=identity.Manager(), + policy_api=policy.Manager(), + token_api=token.Manager()) @logging.fail_gracefully @@ -40,10 +40,10 @@ def public_app_factory(global_conf, **local_conf): conf = global_conf.copy() conf.update(local_conf) return wsgi.ComposingRouter(routes.Mapper(), - [identity.routers.Public(_apis()), - token.routers.Router(_apis()), - routers.Version('public'), - routers.Extension(False)]) + [identity.routers.Public(), + token.routers.Router(), + routers.Version('public'), + routers.Extension(False)]) @logging.fail_gracefully @@ -51,8 +51,8 @@ def admin_app_factory(global_conf, **local_conf): conf = global_conf.copy() conf.update(local_conf) return wsgi.ComposingRouter(routes.Mapper(), - [identity.routers.Admin(_apis()), - token.routers.Router(_apis()), + [identity.routers.Admin(), + token.routers.Router(), routers.Version('admin'), routers.Extension()]) @@ -80,6 +80,6 @@ def v3_app_factory(global_conf, **local_conf): mapper = routes.Mapper() v3routers = [] for module in [catalog, identity, policy]: - module.routers.append_v3_routers(mapper, v3routers, _apis()) - #TODO put token routes here + module.routers.append_v3_routers(mapper, v3routers) + # TODO(ayoung): put token routes here return wsgi.ComposingRouter(mapper, v3routers) diff --git a/keystone/test.py b/keystone/test.py index 97ca95fa..8c8520d0 100644 --- a/keystone/test.py +++ b/keystone/test.py @@ -31,7 +31,10 @@ from keystone.common import logging from keystone.common import utils from keystone.common import wsgi from keystone import config -from keystone.openstack.common import importutils +from keystone import catalog +from keystone import identity +from keystone import policy +from keystone import token do_monkeypatch = not os.getenv('STANDARD_THREADS') @@ -44,6 +47,7 @@ VENDOR = os.path.join(ROOTDIR, 'vendor') TESTSDIR = os.path.join(ROOTDIR, 'tests') ETCDIR = os.path.join(ROOTDIR, 'etc') CONF = config.CONF +DRIVERS = {} cd = os.chdir @@ -61,6 +65,14 @@ def testsdir(*p): return os.path.join(TESTSDIR, *p) +def initialize_drivers(): + DRIVERS['catalog_api'] = catalog.Manager() + DRIVERS['identity_api'] = identity.Manager() + DRIVERS['policy_api'] = policy.Manager() + DRIVERS['token_api'] = token.Manager() + return DRIVERS + + def checkout_vendor(repo, rev): # TODO(termie): this function is a good target for some optimizations :PERF name = repo.split('/')[-1] @@ -202,11 +214,9 @@ class TestCase(NoModule, unittest.TestCase): CONF.set_override(k, v) def load_backends(self): - """Hacky shortcut to load the backends for data manipulation.""" - self.identity_api = importutils.import_object(CONF.identity.driver) - self.token_api = importutils.import_object(CONF.token.driver) - self.catalog_api = importutils.import_object(CONF.catalog.driver) - self.policy_api = importutils.import_object(CONF.policy.driver) + """Create shortcut references to each driver for data manipulation.""" + for name, manager in initialize_drivers().iteritems(): + setattr(self, name, manager.driver) def load_fixtures(self, fixtures): """Hacky basic and naive fixture loading based on a python module. diff --git a/keystone/token/controllers.py b/keystone/token/controllers.py index 3a2d710d..105fc9a0 100644 --- a/keystone/token/controllers.py +++ b/keystone/token/controllers.py @@ -4,6 +4,7 @@ import json from keystone import config from keystone.common import cms from keystone.common import controller +from keystone.common import dependency from keystone.common import logging from keystone import exception from keystone.openstack.common import timeutils @@ -18,6 +19,7 @@ class ExternalAuthNotApplicable(Exception): pass +@dependency.requires('catalog_api') class Auth(controller.V2Controller): def ca_cert(self, context, auth=None): ca_file = open(config.CONF.signing.ca_certs, 'r') diff --git a/keystone/token/core.py b/keystone/token/core.py index bb7a705b..e8da9f1d 100644 --- a/keystone/token/core.py +++ b/keystone/token/core.py @@ -18,8 +18,9 @@ import datetime -from keystone.common import manager from keystone.common import cms +from keystone.common import dependency +from keystone.common import manager from keystone import config from keystone import exception from keystone.openstack.common import timeutils @@ -54,6 +55,7 @@ def default_expire_time(): return timeutils.utcnow() + expire_delta +@dependency.provider('token_api') class Manager(manager.Manager): """Default pivot point for the Token backend. diff --git a/keystone/token/routers.py b/keystone/token/routers.py index 7ff883f6..6b22f648 100644 --- a/keystone/token/routers.py +++ b/keystone/token/routers.py @@ -18,12 +18,8 @@ from keystone.token import controllers class Router(wsgi.ComposableRouter): - def __init__(self, apis): - self.apis = apis - super(Router, self).__init__() - def add_routes(self, mapper): - token_controller = controllers.Auth(**self.apis) + token_controller = controllers.Auth() mapper.connect('/tokens', controller=token_controller, action='authenticate', diff --git a/tests/test_auth.py b/tests/test_auth.py index c0dbbd2c..75f06174 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -17,13 +17,9 @@ import uuid import default_fixtures -from keystone import catalog from keystone import config from keystone import exception -from keystone import identity -from keystone.identity.backends import kvs as kvs_identity from keystone.openstack.common import timeutils -from keystone import policy from keystone import test from keystone import token @@ -56,15 +52,11 @@ class AuthTest(test.TestCase): def setUp(self): super(AuthTest, self).setUp() - # load_fixtures checks for 'identity_api' to be defined - self.identity_api = kvs_identity.Identity() + CONF.identity.driver = 'keystone.identity.backends.kvs.Identity' + self.load_backends() self.load_fixtures(default_fixtures) - self.api = token.controllers.Auth( - catalog_api=catalog.Manager(), - identity_api=identity.Manager(), - policy_api=policy.Manager(), - token_api=token.Manager()) + self.api = token.controllers.Auth() def assertEqualTokens(self, a, b): """Assert that two tokens are equal. diff --git a/tests/test_injection.py b/tests/test_injection.py new file mode 100644 index 00000000..4953cfca --- /dev/null +++ b/tests/test_injection.py @@ -0,0 +1,141 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import unittest2 as unittest +import uuid + +from keystone.common import dependency + + +class TestDependencyInjection(unittest.TestCase): + def test_dependency_injection(self): + class Interface(object): + def do_work(self): + assert False + + @dependency.provider('first_api') + class FirstImplementation(Interface): + def do_work(self): + return True + + @dependency.provider('second_api') + class SecondImplementation(Interface): + def do_work(self): + return True + + @dependency.requires('first_api', 'second_api') + class Consumer(object): + def do_work_with_dependencies(self): + assert self.first_api.do_work() + assert self.second_api.do_work() + + # initialize dependency providers + first_api = FirstImplementation() + second_api = SecondImplementation() + + # ... sometime later, initialize a dependency consumer + consumer = Consumer() + + # the expected dependencies should be available to the consumer + self.assertIs(consumer.first_api, first_api) + self.assertIs(consumer.second_api, second_api) + self.assertIsInstance(consumer.first_api, Interface) + self.assertIsInstance(consumer.second_api, Interface) + consumer.do_work_with_dependencies() + + def test_dependency_configuration(self): + @dependency.provider('api') + class Configurable(object): + def __init__(self, value=None): + self.value = value + + def get_value(self): + return self.value + + @dependency.requires('api') + class Consumer(object): + def get_value(self): + return self.api.get_value() + + # initialize dependency providers + api = Configurable(value=True) + + # ... sometime later, initialize a dependency consumer + consumer = Consumer() + + # the expected dependencies should be available to the consumer + self.assertIs(consumer.api, api) + self.assertIsInstance(consumer.api, Configurable) + self.assertTrue(consumer.get_value()) + + def test_inherited_dependency(self): + class Interface(object): + def do_work(self): + assert False + + @dependency.provider('first_api') + class FirstImplementation(Interface): + def do_work(self): + return True + + @dependency.provider('second_api') + class SecondImplementation(Interface): + def do_work(self): + return True + + @dependency.requires('first_api') + class ParentConsumer(object): + def do_work_with_dependencies(self): + assert self.first_api.do_work() + + @dependency.requires('second_api') + class ChildConsumer(ParentConsumer): + def do_work_with_dependencies(self): + assert self.second_api.do_work() + super(ChildConsumer, self).do_work_with_dependencies() + + # initialize dependency providers + first_api = FirstImplementation() + second_api = SecondImplementation() + + # ... sometime later, initialize a dependency consumer + consumer = ChildConsumer() + + # dependencies should be naturally inherited + self.assertEqual( + ParentConsumer._dependencies, + set(['first_api'])) + self.assertEqual( + ChildConsumer._dependencies, + set(['first_api', 'second_api'])) + self.assertEqual( + consumer._dependencies, + set(['first_api', 'second_api'])) + + # the expected dependencies should be available to the consumer + self.assertIs(consumer.first_api, first_api) + self.assertIs(consumer.second_api, second_api) + self.assertIsInstance(consumer.first_api, Interface) + self.assertIsInstance(consumer.second_api, Interface) + consumer.do_work_with_dependencies() + + def test_unresolvable_dependency(self): + @dependency.requires(uuid.uuid4().hex) + class Consumer(object): + pass + + with self.assertRaises(dependency.UnresolvableDependencyException): + Consumer() |
