From 81f9fe6fed62ec629804c9367fbb9ebfd584388c Mon Sep 17 00:00:00 2001 From: Morgan Fainberg Date: Mon, 4 Dec 2017 15:49:48 -0800 Subject: [PATCH] Remove Dependency Injection Refactors all of keystone's dependency injection to maintain a single centralized repository of instantiated objects. This means that we are no longer having to resolve order. All objects that need to reference the various manager APIs simply do so via the __getattr__ built into the Manager common object or the ProviderAPIMixin object. This is also the first step towards correcting our tests to where they cannot run "load_backends" multiple times. This forces any/all managers to properly run super() as the way to register the api is via __init__. This eliminates all use of the @dependency.requires and @dependency.provides decorators, simplifying the objects all around. Any instantiations of a Manager after keystone is running will now generate an error, ensuring everything for keystone is running before handling requests. An exception is for CLI and CLI tests, as the CLI may directly instantiate managers and will not lock the registry. Change-Id: I4ba17855efd797c0db9f4824936b49e4bff54b6a --- keystone/assignment/backends/sql.py | 1 + keystone/assignment/controllers.py | 8 - keystone/assignment/core.py | 16 +- keystone/auth/controllers.py | 3 - keystone/auth/core.py | 8 +- keystone/auth/plugins/base.py | 3 +- keystone/auth/plugins/core.py | 5 +- keystone/auth/plugins/external.py | 3 - keystone/auth/plugins/mapped.py | 3 - keystone/auth/plugins/oauth1.py | 2 - keystone/auth/plugins/password.py | 2 - keystone/auth/plugins/token.py | 2 - keystone/auth/plugins/totp.py | 2 - keystone/catalog/backends/base.py | 3 +- keystone/catalog/backends/sql.py | 2 - keystone/catalog/controllers.py | 7 - keystone/catalog/core.py | 4 +- keystone/common/controller.py | 8 +- keystone/common/dependency.py | 170 +------------ keystone/common/manager.py | 26 +- keystone/common/provider_api.py | 87 +++++++ keystone/common/tokenless_auth.py | 6 +- keystone/contrib/ec2/controllers.py | 9 +- keystone/credential/controllers.py | 2 - keystone/credential/core.py | 4 +- keystone/credential/provider.py | 3 +- keystone/endpoint_policy/controllers.py | 2 - keystone/endpoint_policy/core.py | 4 +- keystone/federation/controllers.py | 8 - keystone/federation/core.py | 4 +- keystone/identity/controllers.py | 3 - keystone/identity/core.py | 13 +- keystone/identity/generator.py | 3 +- keystone/identity/mapping_backends/base.py | 3 +- keystone/identity/mapping_backends/sql.py | 2 - keystone/middleware/auth.py | 6 +- keystone/notifications.py | 5 +- keystone/oauth1/controllers.py | 6 - keystone/oauth1/core.py | 3 +- keystone/oauth1/validator.py | 5 +- keystone/policy/controllers.py | 2 - keystone/policy/core.py | 3 +- keystone/resource/controllers.py | 6 - keystone/resource/core.py | 9 +- keystone/revoke/controllers.py | 2 - keystone/revoke/core.py | 3 +- keystone/server/backends.py | 40 ++- keystone/server/common.py | 2 - keystone/tests/unit/common/test_injection.py | 238 ------------------ .../tests/unit/common/test_provider_api.py | 64 +++++ keystone/tests/unit/core.py | 8 +- .../tests/unit/ksfixtures/backendloader.py | 6 - keystone/tests/unit/test_cli.py | 25 +- keystone/tests/unit/test_token_provider.py | 18 -- keystone/tests/unit/test_v3_auth.py | 3 +- keystone/tests/unit/test_v3_oauth1.py | 10 +- keystone/token/_simple_cert.py | 2 - keystone/token/controllers.py | 10 +- keystone/token/persistence/__init__.py | 2 +- keystone/token/persistence/core.py | 35 +-- keystone/token/provider.py | 7 +- keystone/token/providers/common.py | 11 +- keystone/token/providers/fernet/core.py | 2 - keystone/trust/controllers.py | 3 - keystone/trust/core.py | 4 +- 65 files changed, 293 insertions(+), 678 deletions(-) create mode 100644 keystone/common/provider_api.py delete mode 100644 keystone/tests/unit/common/test_injection.py create mode 100644 keystone/tests/unit/common/test_provider_api.py diff --git a/keystone/assignment/backends/sql.py b/keystone/assignment/backends/sql.py index ce0778daa1..541f294805 100644 --- a/keystone/assignment/backends/sql.py +++ b/keystone/assignment/backends/sql.py @@ -42,6 +42,7 @@ class AssignmentType(object): class Assignment(base.AssignmentDriverBase): + @classmethod def default_role_driver(self): return 'sql' diff --git a/keystone/assignment/controllers.py b/keystone/assignment/controllers.py index 248dd0ba9f..aa9229dc69 100644 --- a/keystone/assignment/controllers.py +++ b/keystone/assignment/controllers.py @@ -22,7 +22,6 @@ from oslo_log import log from keystone.assignment import schema from keystone.common import authorization from keystone.common import controller -from keystone.common import dependency from keystone.common import validation from keystone.common import wsgi import keystone.conf @@ -34,7 +33,6 @@ CONF = keystone.conf.CONF LOG = log.getLogger(__name__) -@dependency.requires('assignment_api', 'identity_api', 'token_provider_api') class TenantAssignment(controller.V2Controller): """The V2 Project APIs that are processing assignments.""" @@ -61,7 +59,6 @@ class TenantAssignment(controller.V2Controller): return self.format_project_list(tenant_refs, **params) -@dependency.requires('assignment_api', 'resource_api') class ProjectAssignmentV3(controller.V3Controller): """The V3 Project APIs that are processing assignments.""" @@ -81,7 +78,6 @@ class ProjectAssignmentV3(controller.V3Controller): hints=hints) -@dependency.requires('role_api') class RoleV3(controller.V3Controller): """The V3 Role CRUD APIs. @@ -246,7 +242,6 @@ class RoleV3(controller.V3Controller): return hints -@dependency.requires('role_api') class ImpliedRolesV3(controller.V3Controller): """The V3 ImpliedRoles CRD APIs. There is no Update.""" @@ -383,8 +378,6 @@ class ImpliedRolesV3(controller.V3Controller): return results -@dependency.requires('assignment_api', 'identity_api', 'resource_api', - 'role_api') class GrantAssignmentV3(controller.V3Controller): """The V3 Grant Assignment APIs.""" @@ -503,7 +496,6 @@ class GrantAssignmentV3(controller.V3Controller): context=request.context_dict) -@dependency.requires('assignment_api', 'identity_api', 'resource_api') class RoleAssignmentV3(controller.V3Controller): """The V3 Role Assignment APIs, really just list_role_assignment().""" diff --git a/keystone/assignment/core.py b/keystone/assignment/core.py index 1fa50bab25..29fcfef301 100644 --- a/keystone/assignment/core.py +++ b/keystone/assignment/core.py @@ -20,7 +20,6 @@ import functools from oslo_log import log from keystone.common import cache -from keystone.common import dependency from keystone.common import driver_hints from keystone.common import manager import keystone.conf @@ -46,9 +45,6 @@ MEMOIZE_COMPUTED_ASSIGNMENTS = cache.get_memoization_decorator( @notifications.listener -@dependency.provider('assignment_api') -@dependency.requires('credential_api', 'identity_api', 'resource_api', - 'role_api') class Manager(manager.Manager): """Default pivot point for the Assignment backend. @@ -58,6 +54,7 @@ class Manager(manager.Manager): """ driver_namespace = 'keystone.assignment' + _provides_api = 'assignment_api' _PROJECT = 'project' _ROLE_REMOVED_FROM_USER = 'role_removed_from_user' @@ -1057,12 +1054,11 @@ class Manager(manager.Manager): ) -@dependency.provider('role_api') -@dependency.requires('assignment_api') class RoleManager(manager.Manager): """Default pivot point for the Role backend.""" driver_namespace = 'keystone.role' + _provides_api = 'role_api' _ROLE = 'role' @@ -1072,8 +1068,12 @@ class RoleManager(manager.Manager): role_driver = CONF.role.driver if role_driver is None: - assignment_manager = dependency.get_provider('assignment_api') - role_driver = assignment_manager.default_role_driver() + # Explicitly load the assignment manager object + assignment_driver = CONF.assignment.driver + assignment_manager_obj = manager.load_driver( + Manager.driver_namespace, + assignment_driver) + role_driver = assignment_manager_obj.default_role_driver() super(RoleManager, self).__init__(role_driver) diff --git a/keystone/auth/controllers.py b/keystone/auth/controllers.py index 1cc47d8cf8..09debf454b 100644 --- a/keystone/auth/controllers.py +++ b/keystone/auth/controllers.py @@ -21,7 +21,6 @@ from keystone.auth import core from keystone.auth import schema from keystone.common import authorization from keystone.common import controller -from keystone.common import dependency from keystone.common import utils from keystone.common import validation from keystone.common import wsgi @@ -82,8 +81,6 @@ def validate_issue_token_auth(auth=None): raise exception.SchemaValidationError(detail=msg) -@dependency.requires('assignment_api', 'catalog_api', 'identity_api', - 'resource_api', 'token_provider_api', 'trust_api') class Auth(controller.V3Controller): # Note(atiwari): From V3 auth controller code we are diff --git a/keystone/auth/core.py b/keystone/auth/core.py index 4860cdf762..63fc82fa46 100644 --- a/keystone/auth/core.py +++ b/keystone/auth/core.py @@ -18,7 +18,7 @@ from oslo_utils import importutils import six import stevedore -from keystone.common import dependency +from keystone.common import provider_api from keystone.common import utils import keystone.conf from keystone import exception @@ -132,8 +132,7 @@ class AuthContext(dict): self[key] = val -@dependency.requires('resource_api', 'trust_api') -class AuthInfo(object): +class AuthInfo(provider_api.ProviderAPIMixin, object): """Encapsulation of "auth" request.""" @staticmethod @@ -353,8 +352,7 @@ class AuthInfo(object): self._scope_data = (domain_id, project_id, trust, unscoped) -@dependency.requires('identity_api') -class UserMFARulesValidator(object): +class UserMFARulesValidator(provider_api.ProviderAPIMixin, object): """Helper object that can validate the MFA Rules.""" @property diff --git a/keystone/auth/plugins/base.py b/keystone/auth/plugins/base.py index 374a813a9d..d4bbca2783 100644 --- a/keystone/auth/plugins/base.py +++ b/keystone/auth/plugins/base.py @@ -17,6 +17,7 @@ import collections import six +from keystone.common import provider_api from keystone import exception @@ -25,7 +26,7 @@ AuthHandlerResponse = collections.namedtuple( @six.add_metaclass(abc.ABCMeta) -class AuthMethodHandler(object): +class AuthMethodHandler(provider_api.ProviderAPIMixin, object): """Abstract base class for an authentication plugin.""" def __init__(self): diff --git a/keystone/auth/plugins/core.py b/keystone/auth/plugins/core.py index f50349831c..5dcef88a97 100644 --- a/keystone/auth/plugins/core.py +++ b/keystone/auth/plugins/core.py @@ -17,7 +17,7 @@ import sys from oslo_log import log import six -from keystone.common import dependency +from keystone.common import provider_api import keystone.conf from keystone import exception @@ -95,8 +95,7 @@ def convert_integer_to_method_list(method_int): return methods -@dependency.requires('identity_api', 'resource_api') -class BaseUserInfo(object): +class BaseUserInfo(provider_api.ProviderAPIMixin, object): @classmethod def create(cls, auth_payload, method_name): diff --git a/keystone/auth/plugins/external.py b/keystone/auth/plugins/external.py index 3f856bf713..b3edac6ed0 100644 --- a/keystone/auth/plugins/external.py +++ b/keystone/auth/plugins/external.py @@ -19,7 +19,6 @@ import abc import six from keystone.auth.plugins import base -from keystone.common import dependency import keystone.conf from keystone import exception from keystone.i18n import _ @@ -65,7 +64,6 @@ class Base(base.AuthMethodHandler): pass -@dependency.requires('identity_api') class DefaultDomain(Base): def _authenticate(self, request): """Use remote_user to look up the user in the identity backend.""" @@ -74,7 +72,6 @@ class DefaultDomain(Base): CONF.identity.default_domain_id) -@dependency.requires('identity_api', 'resource_api') class Domain(Base): def _authenticate(self, request): """Use remote_user to look up the user in the identity backend. diff --git a/keystone/auth/plugins/mapped.py b/keystone/auth/plugins/mapped.py index 17a5147076..988b97bf5a 100644 --- a/keystone/auth/plugins/mapped.py +++ b/keystone/auth/plugins/mapped.py @@ -19,7 +19,6 @@ from six.moves.urllib import parse from keystone.auth import plugins as auth_plugins from keystone.auth.plugins import base -from keystone.common import dependency from keystone import exception from keystone.federation import constants as federation_constants from keystone.federation import utils @@ -32,8 +31,6 @@ LOG = log.getLogger(__name__) METHOD_NAME = 'mapped' -@dependency.requires('assignment_api', 'federation_api', 'identity_api', - 'resource_api', 'token_provider_api', 'role_api') class Mapped(base.AuthMethodHandler): def _get_token_ref(self, auth_payload): diff --git a/keystone/auth/plugins/oauth1.py b/keystone/auth/plugins/oauth1.py index ec936e0704..a046578463 100644 --- a/keystone/auth/plugins/oauth1.py +++ b/keystone/auth/plugins/oauth1.py @@ -16,14 +16,12 @@ from oslo_utils import timeutils from keystone.auth.plugins import base from keystone.common import controller -from keystone.common import dependency from keystone import exception from keystone.i18n import _ from keystone.oauth1 import core as oauth from keystone.oauth1 import validator -@dependency.requires('oauth_api') class OAuth(base.AuthMethodHandler): def authenticate(self, request, auth_payload): """Turn a signed request with an access key into a keystone token.""" diff --git a/keystone/auth/plugins/password.py b/keystone/auth/plugins/password.py index 33d2039f2c..4c5290ae4c 100644 --- a/keystone/auth/plugins/password.py +++ b/keystone/auth/plugins/password.py @@ -14,7 +14,6 @@ from keystone.auth import plugins as auth_plugins from keystone.auth.plugins import base -from keystone.common import dependency from keystone import exception from keystone.i18n import _ @@ -22,7 +21,6 @@ from keystone.i18n import _ METHOD_NAME = 'password' -@dependency.requires('identity_api') class Password(base.AuthMethodHandler): def authenticate(self, request, auth_payload): diff --git a/keystone/auth/plugins/token.py b/keystone/auth/plugins/token.py index 6cc737202e..67041afc5f 100644 --- a/keystone/auth/plugins/token.py +++ b/keystone/auth/plugins/token.py @@ -17,7 +17,6 @@ import six from keystone.auth.plugins import base from keystone.auth.plugins import mapped -from keystone.common import dependency from keystone.common import wsgi import keystone.conf from keystone import exception @@ -30,7 +29,6 @@ LOG = log.getLogger(__name__) CONF = keystone.conf.CONF -@dependency.requires('federation_api', 'identity_api', 'token_provider_api') class Token(base.AuthMethodHandler): def _get_token_ref(self, auth_payload): diff --git a/keystone/auth/plugins/totp.py b/keystone/auth/plugins/totp.py index 40561c7e84..806b673ca7 100644 --- a/keystone/auth/plugins/totp.py +++ b/keystone/auth/plugins/totp.py @@ -33,7 +33,6 @@ import six from keystone.auth import plugins from keystone.auth.plugins import base -from keystone.common import dependency from keystone import exception from keystone.i18n import _ @@ -69,7 +68,6 @@ def _generate_totp_passcode(secret): return totp.generate(timeutils.utcnow_ts(microsecond=True)).decode('utf-8') -@dependency.requires('credential_api') class TOTP(base.AuthMethodHandler): def authenticate(self, request, auth_payload): diff --git a/keystone/catalog/backends/base.py b/keystone/catalog/backends/base.py index 74e1b54659..03e805ec5b 100644 --- a/keystone/catalog/backends/base.py +++ b/keystone/catalog/backends/base.py @@ -16,6 +16,7 @@ import abc import six +from keystone.common import provider_api import keystone.conf from keystone import exception @@ -24,7 +25,7 @@ CONF = keystone.conf.CONF @six.add_metaclass(abc.ABCMeta) -class CatalogDriverBase(object): +class CatalogDriverBase(provider_api.ProviderAPIMixin, object): """Interface description for the Catalog driver.""" def _get_list_limit(self): diff --git a/keystone/catalog/backends/sql.py b/keystone/catalog/backends/sql.py index 329481c6c6..6e423294d9 100644 --- a/keystone/catalog/backends/sql.py +++ b/keystone/catalog/backends/sql.py @@ -19,7 +19,6 @@ import sqlalchemy from sqlalchemy.sql import true from keystone.catalog.backends import base -from keystone.common import dependency from keystone.common import driver_hints from keystone.common import sql from keystone.common import utils @@ -90,7 +89,6 @@ class Endpoint(sql.ModelBase, sql.ModelDictMixinWithExtras): return super(Endpoint, cls).from_dict(new_dict) -@dependency.requires('catalog_api') class Catalog(base.CatalogDriverBase): # Regions def list_regions(self, hints): diff --git a/keystone/catalog/controllers.py b/keystone/catalog/controllers.py index b5bfb4e73c..aadcf24dcb 100644 --- a/keystone/catalog/controllers.py +++ b/keystone/catalog/controllers.py @@ -17,7 +17,6 @@ from six.moves import http_client from keystone.catalog import schema from keystone.common import controller -from keystone.common import dependency from keystone.common import utils from keystone.common import validation from keystone.common import wsgi @@ -30,7 +29,6 @@ from keystone import resource INTERFACES = ['public', 'internal', 'admin'] -@dependency.requires('catalog_api') class RegionV3(controller.V3Controller): collection_name = 'regions' member_name = 'region' @@ -95,7 +93,6 @@ class RegionV3(controller.V3Controller): ) -@dependency.requires('catalog_api') class ServiceV3(controller.V3Controller): collection_name = 'services' member_name = 'service' @@ -142,7 +139,6 @@ class ServiceV3(controller.V3Controller): ) -@dependency.requires('catalog_api') class EndpointV3(controller.V3Controller): collection_name = 'endpoints' member_name = 'endpoint' @@ -232,7 +228,6 @@ class EndpointV3(controller.V3Controller): ) -@dependency.requires('catalog_api', 'resource_api') class EndpointFilterV3Controller(controller.V3Controller): def __init__(self): @@ -303,7 +298,6 @@ class EndpointFilterV3Controller(controller.V3Controller): request.context_dict, projects) -@dependency.requires('catalog_api', 'resource_api') class EndpointGroupV3Controller(controller.V3Controller): collection_name = 'endpoint_groups' member_name = 'endpoint_group' @@ -408,7 +402,6 @@ class EndpointGroupV3Controller(controller.V3Controller): filtered_endpoints) -@dependency.requires('catalog_api', 'resource_api') class ProjectEndpointGroupV3Controller(controller.V3Controller): collection_name = 'project_endpoint_groups' member_name = 'project_endpoint_group' diff --git a/keystone/catalog/core.py b/keystone/catalog/core.py index fb1c6f2fa6..6bfaaaa0b9 100644 --- a/keystone/catalog/core.py +++ b/keystone/catalog/core.py @@ -16,7 +16,6 @@ """Main entry point into the Catalog service.""" from keystone.common import cache -from keystone.common import dependency from keystone.common import driver_hints from keystone.common import manager import keystone.conf @@ -41,8 +40,6 @@ MEMOIZE_COMPUTED_CATALOG = cache.get_memoization_decorator( region=COMPUTED_CATALOG_REGION) -@dependency.provider('catalog_api') -@dependency.requires('resource_api') class Manager(manager.Manager): """Default pivot point for the Catalog backend. @@ -52,6 +49,7 @@ class Manager(manager.Manager): """ driver_namespace = 'keystone.catalog' + _provides_api = 'catalog_api' _ENDPOINT = 'endpoint' _SERVICE = 'service' diff --git a/keystone/common/controller.py b/keystone/common/controller.py index 2f85eb0a86..985c7ac79e 100644 --- a/keystone/common/controller.py +++ b/keystone/common/controller.py @@ -20,8 +20,8 @@ from oslo_log import versionutils import six from keystone.common import authorization -from keystone.common import dependency from keystone.common import driver_hints +from keystone.common import provider_api from keystone.common import utils from keystone.common import wsgi import keystone.conf @@ -146,8 +146,7 @@ def protected_wrapper(self, f, check_function, request, filter_attr, check_function(self, request, prep_info, *args, **kwargs) -@dependency.requires('policy_api') -class V2Controller(wsgi.Application): +class V2Controller(provider_api.ProviderAPIMixin, wsgi.Application): """Base controller class for Identity API v2.""" @staticmethod @@ -218,8 +217,7 @@ class V2Controller(wsgi.Application): raise ValueError(_('Expected dict or list: %s') % type(ref)) -@dependency.requires('policy_api', 'token_provider_api') -class V3Controller(wsgi.Application): +class V3Controller(provider_api.ProviderAPIMixin, wsgi.Application): """Base controller class for Identity API v3. Child classes should set the ``collection_name`` and ``member_name`` class diff --git a/keystone/common/dependency.py b/keystone/common/dependency.py index 9a75db9d22..356c219dc4 100644 --- a/keystone/common/dependency.py +++ b/keystone/common/dependency.py @@ -25,23 +25,11 @@ See also: """ -import traceback - +from keystone.common import provider_api from keystone.i18n import _ -_REGISTRY = {} - -_future_dependencies = {} -_factories = {} - - -def _set_provider(name, provider): - _original_provider, where_registered = _REGISTRY.get(name, (None, None)) - if where_registered: - raise Exception('%s already has a registered provider, at\n%s' % - (name, ''.join(where_registered))) - _REGISTRY[name] = (provider, traceback.format_stack()) +REGISTRY = provider_api.ProviderAPIs GET_REQUIRED = object() @@ -49,9 +37,7 @@ GET_OPTIONAL = object() def get_provider(name, optional=GET_REQUIRED): - if optional is GET_REQUIRED: - return _REGISTRY[name][0] - return _REGISTRY.get(name, (None, None))[0] + return None class UnresolvableDependencyException(Exception): @@ -68,163 +54,23 @@ class UnresolvableDependencyException(Exception): def provider(name): - """A class decorator used to register providers. - - When ``@provider()`` is used to decorate a class, members of that class - will register themselves as providers for the named dependency. As an - example, In the code fragment:: - - @dependency.provider('foo_api') - class Foo: - def __init__(self): - ... - - ... - - foo = Foo() - - The object ``foo`` will be registered as a provider for ``foo_api``. No - more than one such instance should be created; additional instances will - replace the previous ones, possibly resulting in different instances being - used by different consumers. - - """ + """Deprecated, does nothing.""" 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) - _set_provider(name, self) - resolve_future_dependencies(__provider_name=name) - - return __wrapped_init__ - - cls.__init__ = wrapped(cls.__init__) - _factories[name] = cls return cls return wrapper -def _process_dependencies(obj): - # Any dependencies that can be resolved immediately are resolved. - # Dependencies that cannot be resolved immediately are stored for - # resolution in resolve_future_dependencies. - - def process(obj, attr_name, unresolved_in_out): - for dependency in getattr(obj, attr_name, []): - if dependency not in _REGISTRY: - # We don't know about this dependency, so save it for later. - unresolved_in_out.setdefault(dependency, []).append(obj) - continue - - setattr(obj, dependency, get_provider(dependency)) - - process(obj, '_dependencies', _future_dependencies) - - def requires(*dependencies): - """A class decorator used to inject providers into consumers. - - The required providers will be made available to instances of the decorated - class via an attribute with the same name as the provider. For example, in - the code fragment:: - - @dependency.requires('foo_api', 'bar_api') - class FooBarClient: - def __init__(self): - ... - - ... - - client = FooBarClient() - - The object ``client`` will have attributes named ``foo_api`` and - ``bar_api``, which are instances of the named providers. - - Objects must not rely on the existence of these attributes until after - ``resolve_future_dependencies()`` has been called; they may not exist - beforehand. - - Dependencies registered via ``@required()`` must have providers; if not, - an ``UnresolvableDependencyException`` will be raised when - ``resolve_future_dependencies()`` is called. - - """ - def wrapper(self, *args, **kwargs): - """Inject each dependency from the registry.""" - self.__wrapped_init__(*args, **kwargs) - _process_dependencies(self) - + """Deprecated, does nothing.""" 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 def resolve_future_dependencies(__provider_name=None): - """Force injection of all dependencies. - - Before this function is called, circular dependencies may not have been - injected. This function should be called only once, after all global - providers are registered. If an object needs to be created after this - call, it must not have circular dependencies. - - If any required dependencies are unresolvable, this function will raise an - ``UnresolvableDependencyException``. - - Outside of this module, this function should be called with no arguments; - the optional argument, ``__provider_name`` is used internally, and should - be treated as an implementation detail. - - """ - new_providers = dict() - if __provider_name: - # A provider was registered, so take care of any objects depending on - # it. - targets = _future_dependencies.pop(__provider_name, []) - - for target in targets: - setattr(target, __provider_name, get_provider(__provider_name)) - - return - - # Resolve future dependencies, raises UnresolvableDependencyException if - # there's no provider registered. - try: - for dependency, targets in _future_dependencies.copy().items(): - if dependency not in _REGISTRY: - # a Class was registered that could fulfill the dependency, but - # it has not yet been initialized. - factory = _factories.get(dependency) - if factory: - provider = factory() - new_providers[dependency] = provider - else: - raise UnresolvableDependencyException(dependency, targets) - - for target in targets: - setattr(target, dependency, get_provider(dependency)) - finally: - _future_dependencies.clear() - return new_providers + """Deprecated, does nothing.""" + return {} def reset(): - """Reset the registry of providers. - - This is useful for unit testing to ensure that tests don't use providers - from previous tests. - """ - _REGISTRY.clear() - _future_dependencies.clear() + """Deprecated, does nothing.""" diff --git a/keystone/common/manager.py b/keystone/common/manager.py index daec40a8d9..4db41516dc 100644 --- a/keystone/common/manager.py +++ b/keystone/common/manager.py @@ -21,6 +21,7 @@ from oslo_log import log import six import stevedore +from keystone.common import provider_api from keystone.i18n import _ @@ -164,12 +165,33 @@ class Manager(object): """ driver_namespace = None + _provides_api = None def __init__(self, driver_name): - self.driver = load_driver(self.driver_namespace, driver_name) + if self._provides_api is None: + raise ValueError('Programming Error: All managers must provide an ' + 'API that can be referenced by other components ' + 'of Keystone.') + if driver_name is not None: + self.driver = load_driver(self.driver_namespace, driver_name) + self.__register_provider_api() + + def __register_provider_api(self): + provider_api.ProviderAPIs._register_provider_api( + name=self._provides_api, obj=self) def __getattr__(self, name): - """Forward calls to the underlying driver.""" + """Forward calls to the underlying driver. + + This method checks for a provider api before forwarding. + """ + try: + return getattr(provider_api.ProviderAPIs, name) + except AttributeError: + # NOTE(morgan): We didn't find a provider api, move on and + # forward to the driver as expected. + pass + f = getattr(self.driver, name) if callable(f): # NOTE(dstanek): only if this is callable (class or function) diff --git a/keystone/common/provider_api.py b/keystone/common/provider_api.py new file mode 100644 index 0000000000..02d15d8785 --- /dev/null +++ b/keystone/common/provider_api.py @@ -0,0 +1,87 @@ +# 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. + +ProviderAPIs = None + + +def _create_provider_api_instance(): + class _ProviderAPIs(object): + + def __init__(self): + self.__registry = {} + self.__locked = False + + self.__iter__ = self.__registry.__iter__ + self.__getitem__ = self.__registry.__getitem__ + + def __getattr__(self, item): + """Do attr lookup.""" + try: + return self.__registry[item] + except KeyError: + raise AttributeError( + "'ProviderAPIs' has no attribute %s" % item) + + def _register_provider_api(self, name, obj): + """Register an instance of a class as a provider api.""" + if name == 'driver': + raise ValueError('A provider may not be named "driver".') + + if self.__locked: + raise RuntimeError( + 'Programming Error: The provider api registry has been ' + 'locked (post configuration). Ensure all provider api ' + 'managers are instantiated before locking.') + + if name in self.__registry: + raise DuplicateProviderError( + '`%(name)s` has already been registered as an api ' + 'provider by `%(prov)r`' % {'name': name, + 'prov': self.__registry[name]}) + self.__registry[name] = obj + + def _clear_registry_instances(self): + """ONLY USED FOR TESTING.""" + self.__registry.clear() + self.__locked = False + + def lock_provider_registry(self): + self.__locked = True + + global ProviderAPIs + if ProviderAPIs is None: + ProviderAPIs = _ProviderAPIs() + else: + raise RuntimeError('Programming Error: ProviderAPIs object cannot be ' + 'instatiated more than one time. It is meant to ' + 'act as a singleton.') + + +class DuplicateProviderError(Exception): + """Attempting to register a duplicate API provider.""" + + +class ProviderAPIMixin(object): + """Allow referencing provider apis on self via __getattr__. + + Be sure this class is first in the class definition for inheritance. + """ + + def __getattr__(self, item): + """Magic getattr method.""" + try: + return getattr(ProviderAPIs, item) + except AttributeError: + return self.__getattribute__(item) + + +_create_provider_api_instance() diff --git a/keystone/common/tokenless_auth.py b/keystone/common/tokenless_auth.py index 09ce367e64..f702fee5a1 100644 --- a/keystone/common/tokenless_auth.py +++ b/keystone/common/tokenless_auth.py @@ -18,7 +18,7 @@ import hashlib from oslo_log import log from keystone.auth import core -from keystone.common import dependency +from keystone.common import provider_api import keystone.conf from keystone import exception from keystone.federation import constants as federation_constants @@ -30,9 +30,7 @@ CONF = keystone.conf.CONF LOG = log.getLogger(__name__) -@dependency.requires('assignment_api', 'federation_api', - 'identity_api', 'resource_api') -class TokenlessAuthHelper(object): +class TokenlessAuthHelper(provider_api.ProviderAPIMixin, object): def __init__(self, env): """A init class for TokenlessAuthHelper. diff --git a/keystone/contrib/ec2/controllers.py b/keystone/contrib/ec2/controllers.py index 3883a16251..d0227f5706 100644 --- a/keystone/contrib/ec2/controllers.py +++ b/keystone/contrib/ec2/controllers.py @@ -43,7 +43,7 @@ from six.moves import http_client from keystone.common import authorization from keystone.common import controller -from keystone.common import dependency +from keystone.common import provider_api from keystone.common import utils from keystone.common import wsgi from keystone import exception @@ -53,11 +53,8 @@ from keystone.token import controllers as token_controllers CRED_TYPE_EC2 = 'ec2' -@dependency.requires('assignment_api', 'catalog_api', 'credential_api', - 'identity_api', 'resource_api', 'role_api', - 'token_provider_api') @six.add_metaclass(abc.ABCMeta) -class Ec2ControllerCommon(object): +class Ec2ControllerCommon(provider_api.ProviderAPIMixin, object): def check_signature(self, creds_ref, credentials): signer = ec2_utils.Ec2Signer(creds_ref['secret']) signature = signer.generate(credentials) @@ -270,7 +267,6 @@ class Ec2ControllerCommon(object): headers=headers) -@dependency.requires('policy_api', 'token_provider_api') class Ec2Controller(Ec2ControllerCommon, controller.V2Controller): @controller.v2_ec2_deprecated @@ -358,7 +354,6 @@ class Ec2Controller(Ec2ControllerCommon, controller.V2Controller): raise exception.Forbidden(_('Credential belongs to another user')) -@dependency.requires('policy_api', 'token_provider_api') class Ec2ControllerV3(Ec2ControllerCommon, controller.V3Controller): collection_name = 'credentials' diff --git a/keystone/credential/controllers.py b/keystone/credential/controllers.py index 867694e319..93e215742f 100644 --- a/keystone/credential/controllers.py +++ b/keystone/credential/controllers.py @@ -17,14 +17,12 @@ import hashlib from oslo_serialization import jsonutils from keystone.common import controller -from keystone.common import dependency from keystone.common import validation from keystone.credential import schema from keystone import exception from keystone.i18n import _ -@dependency.requires('credential_api') class CredentialV3(controller.V3Controller): collection_name = 'credentials' member_name = 'credential' diff --git a/keystone/credential/core.py b/keystone/credential/core.py index 978fda20a1..b849cf1613 100644 --- a/keystone/credential/core.py +++ b/keystone/credential/core.py @@ -16,7 +16,6 @@ import json -from keystone.common import dependency from keystone.common import driver_hints from keystone.common import manager import keystone.conf @@ -26,8 +25,6 @@ from keystone import exception CONF = keystone.conf.CONF -@dependency.provider('credential_api') -@dependency.requires('credential_provider_api') class Manager(manager.Manager): """Default pivot point for the Credential backend. @@ -37,6 +34,7 @@ class Manager(manager.Manager): """ driver_namespace = 'keystone.credential' + _provides_api = 'credential_api' def __init__(self): super(Manager, self).__init__(CONF.credential.driver) diff --git a/keystone/credential/provider.py b/keystone/credential/provider.py index 60653145e9..defd49f239 100644 --- a/keystone/credential/provider.py +++ b/keystone/credential/provider.py @@ -10,7 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -from keystone.common import dependency from keystone.common import manager import keystone.conf @@ -18,10 +17,10 @@ import keystone.conf CONF = keystone.conf.CONF -@dependency.provider('credential_provider_api') class Manager(manager.Manager): driver_namespace = 'keystone.credential.provider' + _provides_api = 'credential_provider_api' def __init__(self): super(Manager, self).__init__(CONF.credential.provider) diff --git a/keystone/endpoint_policy/controllers.py b/keystone/endpoint_policy/controllers.py index 02dfbcf1cc..9bde3b32d1 100644 --- a/keystone/endpoint_policy/controllers.py +++ b/keystone/endpoint_policy/controllers.py @@ -13,11 +13,9 @@ # under the License. from keystone.common import controller -from keystone.common import dependency from keystone import notifications -@dependency.requires('policy_api', 'catalog_api', 'endpoint_policy_api') class EndpointPolicyV3Controller(controller.V3Controller): collection_name = 'endpoints' member_name = 'endpoint' diff --git a/keystone/endpoint_policy/core.py b/keystone/endpoint_policy/core.py index 196b90c032..04c5d4b6e4 100644 --- a/keystone/endpoint_policy/core.py +++ b/keystone/endpoint_policy/core.py @@ -14,7 +14,6 @@ from oslo_log import log -from keystone.common import dependency from keystone.common import manager import keystone.conf from keystone import exception @@ -25,8 +24,6 @@ CONF = keystone.conf.CONF LOG = log.getLogger(__name__) -@dependency.provider('endpoint_policy_api') -@dependency.requires('catalog_api', 'policy_api') class Manager(manager.Manager): """Default pivot point for the Endpoint Policy backend. @@ -36,6 +33,7 @@ class Manager(manager.Manager): """ driver_namespace = 'keystone.endpoint_policy' + _provides_api = 'endpoint_policy_api' def __init__(self): super(Manager, self).__init__(CONF.endpoint_policy.driver) diff --git a/keystone/federation/controllers.py b/keystone/federation/controllers.py index 0b8b542afb..cd39b9d517 100644 --- a/keystone/federation/controllers.py +++ b/keystone/federation/controllers.py @@ -22,7 +22,6 @@ import webob from keystone.auth import controllers as auth_controllers from keystone.common import controller -from keystone.common import dependency from keystone.common import utils as k_utils from keystone.common import validation from keystone.common import wsgi @@ -49,7 +48,6 @@ class _ControllerBase(controller.V3Controller): return super(_ControllerBase, cls).base_url(context, path=path) -@dependency.requires('federation_api') class IdentityProvider(_ControllerBase): """Identity Provider representation.""" @@ -128,7 +126,6 @@ class IdentityProvider(_ControllerBase): return IdentityProvider.wrap_member(request.context_dict, idp_ref) -@dependency.requires('federation_api') class FederationProtocol(_ControllerBase): """A federation protocol representation. @@ -219,7 +216,6 @@ class FederationProtocol(_ControllerBase): self.federation_api.delete_protocol(idp_id, protocol_id) -@dependency.requires('federation_api') class MappingController(_ControllerBase): collection_name = 'mappings' member_name = 'mapping' @@ -257,7 +253,6 @@ class MappingController(_ControllerBase): return MappingController.wrap_member(request.context_dict, mapping_ref) -@dependency.requires('federation_api') class Auth(auth_controllers.Auth): def _get_sso_origin_host(self, request): @@ -432,7 +427,6 @@ class Auth(auth_controllers.Auth): headers=headers) -@dependency.requires('assignment_api', 'resource_api') class DomainV3(controller.V3Controller): collection_name = 'domains' member_name = 'domain' @@ -462,7 +456,6 @@ class DomainV3(controller.V3Controller): return DomainV3.wrap_collection(request.context_dict, domains) -@dependency.requires('assignment_api', 'resource_api') class ProjectAssignmentV3(controller.V3Controller): collection_name = 'projects' member_name = 'project' @@ -493,7 +486,6 @@ class ProjectAssignmentV3(controller.V3Controller): projects) -@dependency.requires('federation_api') class ServiceProvider(_ControllerBase): """Service Provider representation.""" diff --git a/keystone/federation/core.py b/keystone/federation/core.py index 03a408d916..bda5691e2e 100644 --- a/keystone/federation/core.py +++ b/keystone/federation/core.py @@ -15,7 +15,6 @@ import uuid from keystone.common import cache -from keystone.common import dependency from keystone.common import extension from keystone.common import manager import keystone.conf @@ -44,8 +43,6 @@ extension.register_admin_extension(EXTENSION_DATA['alias'], EXTENSION_DATA) extension.register_public_extension(EXTENSION_DATA['alias'], EXTENSION_DATA) -@dependency.provider('federation_api') -@dependency.requires('resource_api') class Manager(manager.Manager): """Default pivot point for the Federation backend. @@ -55,6 +52,7 @@ class Manager(manager.Manager): """ driver_namespace = 'keystone.federation' + _provides_api = 'federation_api' def __init__(self): super(Manager, self).__init__(CONF.federation.driver) diff --git a/keystone/identity/controllers.py b/keystone/identity/controllers.py index b79247792c..481c1fa1fd 100644 --- a/keystone/identity/controllers.py +++ b/keystone/identity/controllers.py @@ -17,7 +17,6 @@ from oslo_log import log from keystone.common import controller -from keystone.common import dependency from keystone.common import validation import keystone.conf from keystone import exception @@ -29,7 +28,6 @@ CONF = keystone.conf.CONF LOG = log.getLogger(__name__) -@dependency.requires('identity_api') class UserV3(controller.V3Controller): collection_name = 'users' member_name = 'user' @@ -139,7 +137,6 @@ class UserV3(controller.V3Controller): 'Error when changing user password: %s') % e) -@dependency.requires('identity_api') class GroupV3(controller.V3Controller): collection_name = 'groups' member_name = 'group' diff --git a/keystone/identity/core.py b/keystone/identity/core.py index b53d269aae..06d3e1322b 100644 --- a/keystone/identity/core.py +++ b/keystone/identity/core.py @@ -28,9 +28,9 @@ from pycadf import reason from keystone import assignment # TODO(lbragstad): Decouple this dependency from keystone.common import cache from keystone.common import clean -from keystone.common import dependency from keystone.common import driver_hints from keystone.common import manager +from keystone.common import provider_api from keystone.common.validation import validators import keystone.conf from keystone import exception @@ -62,8 +62,7 @@ REGISTRATION_ATTEMPTS = 10 SQL_DRIVER = 'SQL' -@dependency.requires('domain_config_api', 'resource_api') -class DomainConfigs(dict): +class DomainConfigs(provider_api.ProviderAPIMixin, dict): """Discover, store and provide access to domain specific configs. The setup_domain_drivers() call will be made via the wrapper from @@ -436,9 +435,6 @@ def exception_translated(exception_type): @notifications.listener -@dependency.provider('identity_api') -@dependency.requires('assignment_api', 'credential_api', 'id_mapping_api', - 'resource_api', 'shadow_users_api', 'federation_api') class Manager(manager.Manager): """Default pivot point for the Identity backend. @@ -479,6 +475,7 @@ class Manager(manager.Manager): """ driver_namespace = 'keystone.identity' + _provides_api = 'identity_api' _USER = 'user' _GROUP = 'group' @@ -1423,11 +1420,11 @@ class Manager(manager.Manager): return user_dict -@dependency.provider('id_mapping_api') class MappingManager(manager.Manager): """Default pivot point for the ID Mapping backend.""" driver_namespace = 'keystone.identity.id_mapping' + _provides_api = 'id_mapping_api' def __init__(self): super(MappingManager, self).__init__(CONF.identity_mapping.driver) @@ -1475,11 +1472,11 @@ class MappingManager(manager.Manager): ID_MAPPING_REGION.invalidate() -@dependency.provider('shadow_users_api') class ShadowUsersManager(manager.Manager): """Default pivot point for the Shadow Users backend.""" driver_namespace = 'keystone.identity.shadow_users' + _provides_api = 'shadow_users_api' def __init__(self): shadow_driver = CONF.shadow_users.driver diff --git a/keystone/identity/generator.py b/keystone/identity/generator.py index 7879f63b31..015d296947 100644 --- a/keystone/identity/generator.py +++ b/keystone/identity/generator.py @@ -18,7 +18,6 @@ import abc import six -from keystone.common import dependency from keystone.common import manager import keystone.conf from keystone import exception @@ -27,11 +26,11 @@ from keystone import exception CONF = keystone.conf.CONF -@dependency.provider('id_generator_api') class Manager(manager.Manager): """Default pivot point for the identifier generator backend.""" driver_namespace = 'keystone.identity.id_generator' + _provides_api = 'id_generator_api' def __init__(self): super(Manager, self).__init__(CONF.identity_mapping.generator) diff --git a/keystone/identity/mapping_backends/base.py b/keystone/identity/mapping_backends/base.py index 97553616be..320d60ac16 100644 --- a/keystone/identity/mapping_backends/base.py +++ b/keystone/identity/mapping_backends/base.py @@ -16,11 +16,12 @@ import abc import six +from keystone.common import provider_api from keystone import exception @six.add_metaclass(abc.ABCMeta) -class MappingDriverBase(object): +class MappingDriverBase(provider_api.ProviderAPIMixin, object): """Interface description for an ID Mapping driver.""" @abc.abstractmethod diff --git a/keystone/identity/mapping_backends/sql.py b/keystone/identity/mapping_backends/sql.py index 9c82de237d..991a890ae8 100644 --- a/keystone/identity/mapping_backends/sql.py +++ b/keystone/identity/mapping_backends/sql.py @@ -12,7 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. -from keystone.common import dependency from keystone.common import sql from keystone.identity.mapping_backends import base from keystone.identity.mapping_backends import mapping as identity_mapping @@ -35,7 +34,6 @@ class IDMapping(sql.ModelBase, sql.ModelDictMixin): sql.UniqueConstraint('domain_id', 'local_id', 'entity_type'),) -@dependency.requires('id_generator_api') class Mapping(base.MappingDriverBase): def get_public_id(self, local_entity): diff --git a/keystone/middleware/auth.py b/keystone/middleware/auth.py index 6f8b7d440a..8f5ffc9ada 100644 --- a/keystone/middleware/auth.py +++ b/keystone/middleware/auth.py @@ -15,7 +15,7 @@ from oslo_log import log from keystone.common import authorization from keystone.common import context -from keystone.common import dependency +from keystone.common import provider_api from keystone.common import tokenless_auth from keystone.common import wsgi import keystone.conf @@ -32,8 +32,8 @@ LOG = log.getLogger(__name__) __all__ = ('AuthContextMiddleware',) -@dependency.requires('token_provider_api') -class AuthContextMiddleware(auth_token.BaseAuthProtocol): +class AuthContextMiddleware(provider_api.ProviderAPIMixin, + auth_token.BaseAuthProtocol): """Build the authentication context from the request auth token.""" kwargs_to_fetch_token = True diff --git a/keystone/notifications.py b/keystone/notifications.py index 7dc27d1dbb..7fc41ae040 100644 --- a/keystone/notifications.py +++ b/keystone/notifications.py @@ -30,7 +30,7 @@ from pycadf import eventfactory from pycadf import reason from pycadf import resource -from keystone.common import dependency +from keystone.common import provider_api from keystone.common import utils import keystone.conf from keystone import exception @@ -655,8 +655,7 @@ def send_saml_audit_notification(action, request, user_id, group_ids, _send_audit_notification(action, initiator, outcome, target, event_type) -@dependency.requires('catalog_api') -class _CatalogHelperObj(object): +class _CatalogHelperObj(provider_api.ProviderAPIMixin, object): """A helper object to allow lookups of identity service id.""" diff --git a/keystone/oauth1/controllers.py b/keystone/oauth1/controllers.py index cffb6ebff1..f1378043b7 100644 --- a/keystone/oauth1/controllers.py +++ b/keystone/oauth1/controllers.py @@ -22,7 +22,6 @@ from six.moves.urllib import parse as urlparse from keystone.common import authorization from keystone.common import controller -from keystone.common import dependency from keystone.common import validation from keystone.common import wsgi import keystone.conf @@ -49,7 +48,6 @@ def _emit_user_oauth_consumer_token_invalidate(payload): ) -@dependency.requires('oauth_api', 'token_provider_api') class ConsumerCrudV3(controller.V3Controller): collection_name = 'consumers' member_name = 'consumer' @@ -102,7 +100,6 @@ class ConsumerCrudV3(controller.V3Controller): ) -@dependency.requires('oauth_api') class AccessTokenCrudV3(controller.V3Controller): collection_name = 'access_tokens' member_name = 'access_token' @@ -172,7 +169,6 @@ class AccessTokenCrudV3(controller.V3Controller): return formatted_entity -@dependency.requires('oauth_api', 'role_api') class AccessTokenRolesV3(controller.V3Controller): collection_name = 'roles' member_name = 'role' @@ -212,8 +208,6 @@ class AccessTokenRolesV3(controller.V3Controller): return formatted_entity -@dependency.requires('assignment_api', 'oauth_api', - 'resource_api', 'token_provider_api') class OAuthControllerV3(controller.V3Controller): collection_name = 'not_used' member_name = 'not_used' diff --git a/keystone/oauth1/core.py b/keystone/oauth1/core.py index 63d06be58d..7f6e50bd80 100644 --- a/keystone/oauth1/core.py +++ b/keystone/oauth1/core.py @@ -22,7 +22,6 @@ import oauthlib.common from oauthlib import oauth1 from oslo_log import log -from keystone.common import dependency from keystone.common import extension from keystone.common import manager import keystone.conf @@ -123,7 +122,6 @@ def validate_oauth_params(query_string): raise exception.ValidationError(message=msg) -@dependency.provider('oauth_api') class Manager(manager.Manager): """Default pivot point for the OAuth1 backend. @@ -133,6 +131,7 @@ class Manager(manager.Manager): """ driver_namespace = 'keystone.oauth1' + _provides_api = 'oauth_api' _ACCESS_TOKEN = "OS-OAUTH1:access_token" _REQUEST_TOKEN = "OS-OAUTH1:request_token" diff --git a/keystone/oauth1/validator.py b/keystone/oauth1/validator.py index 84761185d5..77a2245fab 100644 --- a/keystone/oauth1/validator.py +++ b/keystone/oauth1/validator.py @@ -16,7 +16,7 @@ import six -from keystone.common import dependency +from keystone.common import provider_api from keystone import exception from keystone.oauth1.backends import base from keystone.oauth1 import core as oauth1 @@ -25,8 +25,7 @@ from keystone.oauth1 import core as oauth1 METHOD_NAME = 'oauth_validator' -@dependency.requires('oauth_api') -class OAuthValidator(oauth1.RequestValidator): +class OAuthValidator(provider_api.ProviderAPIMixin, oauth1.RequestValidator): # TODO(mhu) set as option probably? @property diff --git a/keystone/policy/controllers.py b/keystone/policy/controllers.py index e061873e11..c9cfd876c5 100644 --- a/keystone/policy/controllers.py +++ b/keystone/policy/controllers.py @@ -16,7 +16,6 @@ from oslo_log import versionutils import six from keystone.common import controller -from keystone.common import dependency from keystone.common import validation from keystone.policy import schema @@ -31,7 +30,6 @@ def policy_deprecated(f): return wrapper() -@dependency.requires('policy_api') class PolicyV3(controller.V3Controller): collection_name = 'policies' member_name = 'policy' diff --git a/keystone/policy/core.py b/keystone/policy/core.py index 3f11f9d146..e4578a9954 100644 --- a/keystone/policy/core.py +++ b/keystone/policy/core.py @@ -14,7 +14,6 @@ """Main entry point into the Policy service.""" -from keystone.common import dependency from keystone.common import manager import keystone.conf from keystone import exception @@ -24,7 +23,6 @@ from keystone import notifications CONF = keystone.conf.CONF -@dependency.provider('policy_api') class Manager(manager.Manager): """Default pivot point for the Policy backend. @@ -34,6 +32,7 @@ class Manager(manager.Manager): """ driver_namespace = 'keystone.policy' + _provides_api = 'policy_api' _POLICY = 'policy' diff --git a/keystone/resource/controllers.py b/keystone/resource/controllers.py index c397f6d38e..b51cc7b6fe 100644 --- a/keystone/resource/controllers.py +++ b/keystone/resource/controllers.py @@ -18,7 +18,6 @@ from six.moves import http_client from keystone.common import controller -from keystone.common import dependency from keystone.common import validation from keystone.common import wsgi import keystone.conf @@ -30,7 +29,6 @@ from keystone.resource import schema CONF = keystone.conf.CONF -@dependency.requires('resource_api') class DomainV3(controller.V3Controller): collection_name = 'domains' member_name = 'domain' @@ -76,8 +74,6 @@ class DomainV3(controller.V3Controller): ) -@dependency.requires('domain_config_api') -@dependency.requires('resource_api') class DomainConfigV3(controller.V3Controller): member_name = 'config' @@ -151,7 +147,6 @@ class DomainConfigV3(controller.V3Controller): return {self.member_name: ref} -@dependency.requires('resource_api') class ProjectV3(controller.V3Controller): collection_name = 'projects' member_name = 'project' @@ -266,7 +261,6 @@ class ProjectV3(controller.V3Controller): initiator=request.audit_initiator) -@dependency.requires('resource_api') class ProjectTagV3(controller.V3Controller): collection_name = 'projects' member_name = 'tags' diff --git a/keystone/resource/core.py b/keystone/resource/core.py index e3304b7b00..d8335e4fe7 100644 --- a/keystone/resource/core.py +++ b/keystone/resource/core.py @@ -18,7 +18,6 @@ import six from keystone import assignment from keystone.common import cache from keystone.common import clean -from keystone.common import dependency from keystone.common import driver_hints from keystone.common import manager from keystone.common import utils @@ -37,9 +36,6 @@ MEMOIZE = cache.get_memoization_decorator(group='resource') TAG_SEARCH_FILTERS = ('tags', 'tags-any', 'not-tags', 'not-tags-any') -@dependency.provider('resource_api') -@dependency.requires('assignment_api', 'credential_api', 'domain_config_api', - 'identity_api', 'trust_api') class Manager(manager.Manager): """Default pivot point for the Resource backend. @@ -49,6 +45,7 @@ class Manager(manager.Manager): """ driver_namespace = 'keystone.resource' + _provides_api = 'resource_api' _DOMAIN = 'domain' _PROJECT = 'project' @@ -60,7 +57,9 @@ class Manager(manager.Manager): # SQL Identity in some form. Even if SQL Identity is not used, there # is almost no reason to have non-SQL Resource. Keystone requires # SQL in a number of ways, this simply codifies it plainly for resource + # the driver_name = None simply implies we don't need to load a driver. self.driver = resource_sql.Resource() + super(Manager, self).__init__(driver_name=None) def _get_hierarchy_depth(self, parents_list): return len(parents_list) + 1 @@ -946,7 +945,6 @@ class Manager(manager.Manager): MEMOIZE_CONFIG = cache.get_memoization_decorator(group='domain_config') -@dependency.provider('domain_config_api') class DomainConfigManager(manager.Manager): """Default pivot point for the Domain Config backend.""" @@ -960,6 +958,7 @@ class DomainConfigManager(manager.Manager): # the identity manager are supported. driver_namespace = 'keystone.resource.domain_config' + _provides_api = 'domain_config_api' # We explicitly state each whitelisted option instead of pulling all ldap # options from CONF and selectively pruning them to prevent a security diff --git a/keystone/revoke/controllers.py b/keystone/revoke/controllers.py index 997f70a052..6a8dc297c4 100644 --- a/keystone/revoke/controllers.py +++ b/keystone/revoke/controllers.py @@ -13,12 +13,10 @@ from oslo_utils import timeutils from keystone.common import controller -from keystone.common import dependency from keystone import exception from keystone.i18n import _ -@dependency.requires('revoke_api') class RevokeController(controller.V3Controller): @controller.protected() def list_revoke_events(self, request): diff --git a/keystone/revoke/core.py b/keystone/revoke/core.py index 321c7eb4a1..a4c0e2ddf2 100644 --- a/keystone/revoke/core.py +++ b/keystone/revoke/core.py @@ -13,7 +13,6 @@ """Main entry point into the Revoke service.""" from keystone.common import cache -from keystone.common import dependency from keystone.common import extension from keystone.common import manager import keystone.conf @@ -53,7 +52,6 @@ MEMOIZE = cache.get_memoization_decorator( region=REVOKE_REGION) -@dependency.provider('revoke_api') class Manager(manager.Manager): """Default pivot point for the Revoke backend. @@ -65,6 +63,7 @@ class Manager(manager.Manager): """ driver_namespace = 'keystone.revoke' + _provides_api = 'revoke_api' def __init__(self): super(Manager, self).__init__(CONF.revoke.driver) diff --git a/keystone/server/backends.py b/keystone/server/backends.py index eb8181c916..565dd619e7 100644 --- a/keystone/server/backends.py +++ b/keystone/server/backends.py @@ -14,6 +14,7 @@ from keystone import assignment from keystone import auth from keystone import catalog from keystone.common import cache +from keystone.common import provider_api from keystone import credential from keystone import endpoint_policy from keystone import federation @@ -23,6 +24,7 @@ from keystone import policy from keystone import resource from keystone import revoke from keystone import token +from keystone.token import persistence from keystone import trust @@ -37,29 +39,21 @@ def load_backends(): cache.configure_cache(region=identity.ID_MAPPING_REGION) cache.configure_invalidation_region() - # NOTE(knikolla): The assignment manager must be instantiated before the - # resource manager. The current dictionary ordering ensures that. - DRIVERS = dict( - assignment_api=assignment.Manager(), - catalog_api=catalog.Manager(), - credential_api=credential.Manager(), - credential_provider_api=credential.provider.Manager(), - domain_config_api=resource.DomainConfigManager(), - endpoint_policy_api=endpoint_policy.Manager(), - federation_api=federation.Manager(), - id_generator_api=identity.generator.Manager(), - id_mapping_api=identity.MappingManager(), - identity_api=identity.Manager(), - shadow_users_api=identity.ShadowUsersManager(), - oauth_api=oauth1.Manager(), - policy_api=policy.Manager(), - resource_api=resource.Manager(), - revoke_api=revoke.Manager(), - role_api=assignment.RoleManager(), - token_api=token.persistence.Manager(), - trust_api=trust.Manager(), - token_provider_api=token.provider.Manager()) + managers = [assignment.Manager, catalog.Manager, credential.Manager, + credential.provider.Manager, resource.DomainConfigManager, + endpoint_policy.Manager, federation.Manager, + identity.generator.Manager, identity.MappingManager, + identity.Manager, identity.ShadowUsersManager, oauth1.Manager, + policy.Manager, resource.Manager, revoke.Manager, + assignment.RoleManager, trust.Manager, token.provider.Manager, + persistence.PersistenceManager] + + drivers = {d._provides_api: d() for d in managers} + + # NOTE(morgan): lock the APIs, these should only ever be instantiated + # before running keystone. + provider_api.ProviderAPIs.lock_provider_registry() auth.core.load_auth_methods() - return DRIVERS + return drivers diff --git a/keystone/server/common.py b/keystone/server/common.py index d83afdf497..33e13a9d40 100644 --- a/keystone/server/common.py +++ b/keystone/server/common.py @@ -14,7 +14,6 @@ from oslo_log import log -from keystone.common import dependency from keystone.common import sql import keystone.conf from keystone.server import backends @@ -47,5 +46,4 @@ def setup_backends(load_extra_backends_fn=lambda: {}, drivers = backends.load_backends() drivers.update(load_extra_backends_fn()) res = startup_application_fn() - drivers.update(dependency.resolve_future_dependencies()) return drivers, res diff --git a/keystone/tests/unit/common/test_injection.py b/keystone/tests/unit/common/test_injection.py deleted file mode 100644 index 9a5d1e7d2e..0000000000 --- a/keystone/tests/unit/common/test_injection.py +++ /dev/null @@ -1,238 +0,0 @@ -# Copyright 2012 OpenStack Foundation -# -# 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 uuid - -from keystone.common import dependency -from keystone.tests import unit - - -class TestDependencyInjection(unit.BaseTestCase): - def setUp(self): - super(TestDependencyInjection, self).setUp() - dependency.reset() - self.addCleanup(dependency.reset) - - 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_provider_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_dependency_consumer_configuration(self): - @dependency.provider('api') - class Provider(object): - def get_value(self): - return True - - @dependency.requires('api') - class Configurable(object): - def __init__(self, value=None): - self.value = value - - def get_value(self): - if self.value: - return self.api.get_value() - - # initialize dependency providers - api = Provider() - - # ... sometime later, initialize a dependency consumer - consumer = Configurable(value=True) - - # the expected dependencies should be available to the consumer - self.assertIs(consumer.api, api) - self.assertIsInstance(consumer.api, Provider) - 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( - set(['first_api']), - ParentConsumer._dependencies) - self.assertEqual( - set(['first_api', 'second_api']), - ChildConsumer._dependencies) - self.assertEqual( - set(['first_api', 'second_api']), - consumer._dependencies) - - # 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 - - def for_test(): - Consumer() - dependency.resolve_future_dependencies() - - self.assertRaises(dependency.UnresolvableDependencyException, for_test) - - 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. - - p_id = uuid.uuid4().hex - - @dependency.provider(p_id) - class P(object): - pass - - p_inst = P() - - self.assertIs(dependency.get_provider(p_id), p_inst) - - dependency.reset() - - self.assertFalse(dependency._REGISTRY) - - def test_get_provider(self): - # Can get the instance of a provider using get_provider - - provider_name = uuid.uuid4().hex - - @dependency.provider(provider_name) - class P(object): - pass - - provider_instance = P() - retrieved_provider_instance = dependency.get_provider(provider_name) - self.assertIs(provider_instance, retrieved_provider_instance) - - def test_get_provider_not_provided_error(self): - # If no provider and provider is required then fails. - - provider_name = uuid.uuid4().hex - self.assertRaises(KeyError, dependency.get_provider, provider_name) - - def test_get_provider_not_provided_optional(self): - # If no provider and provider is optional then returns None. - - provider_name = uuid.uuid4().hex - self.assertIsNone(dependency.get_provider(provider_name, - dependency.GET_OPTIONAL)) diff --git a/keystone/tests/unit/common/test_provider_api.py b/keystone/tests/unit/common/test_provider_api.py new file mode 100644 index 0000000000..285ae1c319 --- /dev/null +++ b/keystone/tests/unit/common/test_provider_api.py @@ -0,0 +1,64 @@ +# Copyright 2012 OpenStack Foundation +# +# 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 uuid + +from keystone.common import manager +from keystone.common import provider_api +from keystone.tests import unit + + +class TestProviderAPIRegistry(unit.BaseTestCase): + def setUp(self): + super(TestProviderAPIRegistry, self).setUp() + provider_api.ProviderAPIs._clear_registry_instances() + self.addCleanup(provider_api.ProviderAPIs._clear_registry_instances) + + def _create_manager_instance(self, provides_api=None): + provides_api = provides_api or '%s_api' % uuid.uuid4().hex + + class TestManager(manager.Manager): + _provides_api = provides_api + driver_namespace = '_TEST_NOTHING' + + return TestManager(driver_name=None) + + def test_registry_lock(self): + provider_api.ProviderAPIs.lock_provider_registry() + self.assertRaises(RuntimeError, self._create_manager_instance) + + def test_registry_duplicate(self): + test_manager = self._create_manager_instance() + self.assertRaises( + provider_api.DuplicateProviderError, + self._create_manager_instance, + provides_api=test_manager._provides_api) + + def test_provider_api_mixin(self): + test_manager = self._create_manager_instance() + + class Testing(provider_api.ProviderAPIMixin, object): + pass + + instance = Testing() + self.assertIs(test_manager, getattr(instance, + test_manager._provides_api)) + + def test_manager_api_reference(self): + manager = self._create_manager_instance() + second_manager = self._create_manager_instance() + self.assertIs(second_manager, getattr(manager, + second_manager._provides_api)) + self.assertIs(manager, getattr(second_manager, + manager._provides_api)) diff --git a/keystone/tests/unit/core.py b/keystone/tests/unit/core.py index 4f18e8fcfe..5e3e69fa12 100644 --- a/keystone/tests/unit/core.py +++ b/keystone/tests/unit/core.py @@ -41,7 +41,7 @@ import testtools from testtools import testcase from keystone.common import context -from keystone.common import dependency +from keystone.common import provider_api from keystone.common import request from keystone.common import sql import keystone.conf @@ -677,7 +677,7 @@ class TestCase(BaseTestCase): # Clear the registry of providers so that providers from previous # tests aren't used. - self.addCleanup(dependency.reset) + self.addCleanup(provider_api.ProviderAPIs._clear_registry_instances) # Ensure Notification subscriptions and resource types are empty self.addCleanup(notifications.clear_subscribers) @@ -691,6 +691,10 @@ class TestCase(BaseTestCase): def load_backends(self): """Initialize each manager and assigns them to an attribute.""" + # TODO(morgan): Ensure our tests only ever call load_backends + # a single time via this method. for now just clear the registry + # if we are reloading. + provider_api.ProviderAPIs._clear_registry_instances() self.useFixture(ksfixtures.BackendLoader(self)) def load_fixtures(self, fixtures): diff --git a/keystone/tests/unit/ksfixtures/backendloader.py b/keystone/tests/unit/ksfixtures/backendloader.py index 565c2be5fd..217b090a56 100644 --- a/keystone/tests/unit/ksfixtures/backendloader.py +++ b/keystone/tests/unit/ksfixtures/backendloader.py @@ -14,7 +14,6 @@ import fixtures from keystone import auth -from keystone.common import dependency from keystone.server import common @@ -28,11 +27,6 @@ class BackendLoader(fixtures.Fixture): def setUp(self): super(BackendLoader, self).setUp() - # TODO(blk-u): Shouldn't need to clear the registry here, but some - # tests call load_backends multiple times. These should be fixed to - # only call load_backends once. - dependency.reset() - self.clear_auth_plugin_registry() drivers, _unused = common.setup_backends() diff --git a/keystone/tests/unit/test_cli.py b/keystone/tests/unit/test_cli.py index 0050af1673..83ef7dae0f 100644 --- a/keystone/tests/unit/test_cli.py +++ b/keystone/tests/unit/test_cli.py @@ -37,7 +37,7 @@ from keystone.cmd.doctor import ldap from keystone.cmd.doctor import security_compliance from keystone.cmd.doctor import tokens from keystone.cmd.doctor import tokens_fernet -from keystone.common import dependency +from keystone.common import provider_api from keystone.common.sql import upgrades import keystone.conf from keystone import exception @@ -61,6 +61,11 @@ class CliTestCase(unit.SQLDriverOverrides, unit.TestCase): def test_token_flush(self): self.useFixture(database.Database()) self.load_backends() + # NOTE(morgan): we are testing a direct instantiation of the + # persistence manager for flushing. We should clear this out so we + # don't error. CLI should never impact a running service + # and should never actually lock the registry for dependencies. + provider_api.ProviderAPIs._clear_registry_instances() cli.TokenFlush.main() @@ -463,7 +468,7 @@ class CliDomainConfigAllTestCase(unit.SQLDriverOverrides, unit.TestCase): } # Clear backend dependencies, since cli loads these manually - dependency.reset() + provider_api.ProviderAPIs._clear_registry_instances() cli.DomainConfigUpload.main() res = self.domain_config_api.get_config_with_sensitive_info( @@ -495,7 +500,7 @@ class CliDomainConfigSingleDomainTestCase(CliDomainConfigAllTestCase): } # Clear backend dependencies, since cli loads these manually - dependency.reset() + provider_api.ProviderAPIs._clear_registry_instances() cli.DomainConfigUpload.main() res = self.domain_config_api.get_config_with_sensitive_info( @@ -519,7 +524,7 @@ class CliDomainConfigSingleDomainTestCase(CliDomainConfigAllTestCase): # Now try and upload the settings in the configuration file for the # default domain - dependency.reset() + provider_api.ProviderAPIs._clear_registry_instances() with mock.patch('six.moves.builtins.print') as mock_print: self.assertRaises(unit.UnexpectedExit, cli.DomainConfigUpload.main) file_name = ('keystone.%s.conf' % self.default_domain['name']) @@ -544,7 +549,7 @@ class CliDomainConfigNoOptionsTestCase(CliDomainConfigAllTestCase): project='keystone', default_config_files=config_files) def test_config_upload(self): - dependency.reset() + provider_api.ProviderAPIs._clear_registry_instances() with mock.patch('six.moves.builtins.print') as mock_print: self.assertRaises(unit.UnexpectedExit, cli.DomainConfigUpload.main) mock_print.assert_has_calls( @@ -561,7 +566,7 @@ class CliDomainConfigTooManyOptionsTestCase(CliDomainConfigAllTestCase): project='keystone', default_config_files=config_files) def test_config_upload(self): - dependency.reset() + provider_api.ProviderAPIs._clear_registry_instances() with mock.patch('six.moves.builtins.print') as mock_print: self.assertRaises(unit.UnexpectedExit, cli.DomainConfigUpload.main) mock_print.assert_has_calls( @@ -578,7 +583,7 @@ class CliDomainConfigInvalidDomainTestCase(CliDomainConfigAllTestCase): project='keystone', default_config_files=config_files) def test_config_upload(self): - dependency.reset() + provider_api.ProviderAPIs._clear_registry_instances() with mock.patch('six.moves.builtins.print') as mock_print: self.assertRaises(unit.UnexpectedExit, cli.DomainConfigUpload.main) file_name = 'keystone.%s.conf' % self.invalid_domain_name @@ -742,7 +747,8 @@ class TestMappingPopulate(unit.SQLDriverOverrides, unit.TestCase): 'entity_type': identity_mapping.EntityType.USER} self.assertIsNone(self.id_mapping_api.get_public_id(local_entity)) - dependency.reset() # backends are loaded again in the command handler + # backends are loaded again in the command handler + provider_api.ProviderAPIs._clear_registry_instances() cli.MappingPopulate.main() for user in users: @@ -756,7 +762,8 @@ class TestMappingPopulate(unit.SQLDriverOverrides, unit.TestCase): def test_bad_domain_name(self): CONF(args=['mapping_populate', '--domain-name', uuid.uuid4().hex], project='keystone') - dependency.reset() # backends are loaded again in the command handler + # backends are loaded again in the command handler + provider_api.ProviderAPIs._clear_registry_instances() # NOTE: assertEqual is used on purpose. assertFalse passes with None. self.assertEqual(False, cli.MappingPopulate.main()) diff --git a/keystone/tests/unit/test_token_provider.py b/keystone/tests/unit/test_token_provider.py index 5ab5ae094a..3bca9a30bd 100644 --- a/keystone/tests/unit/test_token_provider.py +++ b/keystone/tests/unit/test_token_provider.py @@ -16,7 +16,6 @@ import datetime from oslo_utils import timeutils -from keystone.common import dependency from keystone.common import utils import keystone.conf from keystone import exception @@ -24,8 +23,6 @@ from keystone.tests import unit from keystone.tests.unit import ksfixtures from keystone.tests.unit.ksfixtures import database from keystone import token -from keystone.token.providers import fernet -from keystone.token.providers import uuid CONF = keystone.conf.CONF @@ -464,21 +461,6 @@ class TestTokenProvider(unit.TestCase): self.token_provider_api.get_token_version, 'bogus') - def test_supported_token_providers(self): - # test default config - - dependency.reset() - self.assertIsInstance(token.provider.Manager().driver, - fernet.Provider) - - dependency.reset() - self.config_fixture.config(group='token', provider='uuid') - self.assertIsInstance(token.provider.Manager().driver, uuid.Provider) - - dependency.reset() - self.config_fixture.config(group='token', provider='fernet') - self.assertIsInstance(token.provider.Manager().driver, fernet.Provider) - def test_unsupported_token_provider(self): self.config_fixture.config(group='token', provider='MyProvider') diff --git a/keystone/tests/unit/test_v3_auth.py b/keystone/tests/unit/test_v3_auth.py index 85ebb138bf..2abea1891c 100644 --- a/keystone/tests/unit/test_v3_auth.py +++ b/keystone/tests/unit/test_v3_auth.py @@ -2143,7 +2143,8 @@ class TokenAPITests(object): # flush the tokens, this will only have an effect on sql try: - self.token_provider_api._persistence.flush_expired_tokens() + provider_api = self.token_provider_api + provider_api._persistence.flush_expired_tokens() except exception.NotImplemented: pass diff --git a/keystone/tests/unit/test_v3_oauth1.py b/keystone/tests/unit/test_v3_oauth1.py index b5cb5025ae..f77951fae3 100644 --- a/keystone/tests/unit/test_v3_oauth1.py +++ b/keystone/tests/unit/test_v3_oauth1.py @@ -535,13 +535,15 @@ class AuthTokenTests(object): def test_delete_keystone_tokens_by_consumer_id(self): self.test_oauth_flow() - self.token_provider_api._persistence.get_token(self.keystone_token_id) + self.token_provider_api._persistence.get_token( + self.keystone_token_id) self.token_provider_api._persistence.delete_tokens( self.user_id, consumer_id=self.consumer['key']) - self.assertRaises(exception.TokenNotFound, - self.token_provider_api._persistence.get_token, - self.keystone_token_id) + self.assertRaises( + exception.TokenNotFound, + self.token_provider_api._persistence.get_token, + self.keystone_token_id) def _create_trust_get_token(self): ref = unit.new_trust_ref( diff --git a/keystone/token/_simple_cert.py b/keystone/token/_simple_cert.py index 301ea383cd..793b1820c5 100644 --- a/keystone/token/_simple_cert.py +++ b/keystone/token/_simple_cert.py @@ -17,7 +17,6 @@ import functools import webob from keystone.common import controller -from keystone.common import dependency from keystone.common import extension from keystone.common import json_home from keystone.common import wsgi @@ -69,7 +68,6 @@ class Routers(wsgi.RoutersBase): rel=build_resource_relation(resource_name='certificates')) -@dependency.requires('token_provider_api') class SimpleCert(controller.V3Controller): def _get_certificate(self, name): diff --git a/keystone/token/controllers.py b/keystone/token/controllers.py index db56a21c41..336cb7b3f4 100644 --- a/keystone/token/controllers.py +++ b/keystone/token/controllers.py @@ -13,7 +13,7 @@ # under the License. from keystone.common import controller -from keystone.common import dependency +from keystone.common import provider_api from keystone.common import utils from keystone.common import wsgi import keystone.conf @@ -58,8 +58,7 @@ class ExternalAuthNotApplicable(Exception): pass -@dependency.requires('resource_api', 'identity_api') -class BaseAuthenticationMethod(object): +class BaseAuthenticationMethod(provider_api.ProviderAPIMixin, object): """Common utilities/dependencies for all authentication method classes.""" def _get_project_id_from_auth(self, auth): @@ -99,7 +98,6 @@ class BaseAuthenticationMethod(object): return project_id -@dependency.requires('token_provider_api', 'trust_api') class TokenAuthenticationMethod(BaseAuthenticationMethod): """Authenticate using an existing token.""" @@ -280,9 +278,7 @@ class ExternalAuthenticationMethod(BaseAuthenticationMethod): return (user_ref, tenant_id, expiry, bind, audit_id) -@dependency.requires('catalog_api', 'resource_api', - 'trust_api', 'identity_api') -class V2TokenDataHelper(object): +class V2TokenDataHelper(provider_api.ProviderAPIMixin, object): """Create V2 token data.""" def v3_to_v2_token(self, v3_token_data, token_id): diff --git a/keystone/token/persistence/__init__.py b/keystone/token/persistence/__init__.py index 49a8e93ed3..1cb6f3a444 100644 --- a/keystone/token/persistence/__init__.py +++ b/keystone/token/persistence/__init__.py @@ -13,4 +13,4 @@ from keystone.token.persistence.core import * # noqa -__all__ = ('Manager', 'TokenDriverBase') +__all__ = ('PersistenceManager', 'TokenDriverBase') diff --git a/keystone/token/persistence/core.py b/keystone/token/persistence/core.py index 25bd45e48c..fb4b9072db 100644 --- a/keystone/token/persistence/core.py +++ b/keystone/token/persistence/core.py @@ -21,7 +21,6 @@ from oslo_log import log import six from keystone.common import cache -from keystone.common import dependency from keystone.common import manager import keystone.conf from keystone import exception @@ -34,8 +33,6 @@ REVOCATION_MEMOIZE = cache.get_memoization_decorator(group='token', expiration_group='revoke') -@dependency.requires('assignment_api', 'identity_api', 'resource_api', - 'trust_api') class PersistenceManager(manager.Manager): """Default pivot point for the Token Persistence backend. @@ -45,6 +42,7 @@ class PersistenceManager(manager.Manager): """ driver_namespace = 'keystone.token.persistence' + _provides_api = '_token_persistence_manager' def __init__(self): super(PersistenceManager, self).__init__(CONF.token.driver) @@ -167,37 +165,6 @@ class PersistenceManager(manager.Manager): self._get_token.invalidate(self, token_id) -@dependency.requires('token_provider_api') -@dependency.provider('token_api') -class Manager(object): - """The token_api provider. - - This class is a proxy class to the token_provider_api's persistence - manager. - """ - - def __init__(self): - # NOTE(morganfainberg): __init__ is required for dependency processing. - super(Manager, self).__init__() - - def __getattr__(self, item): - """Forward calls to the `token_provider_api` persistence manager.""" - # NOTE(morganfainberg): Prevent infinite recursion, raise an - # AttributeError for 'token_provider_api' ensuring that the dep - # injection doesn't infinitely try and lookup self.token_provider_api - # on _process_dependencies. This doesn't need an exception string as - # it should only ever be hit on instantiation. - if item == 'token_provider_api': - raise AttributeError() - - f = getattr(self.token_provider_api._persistence, item) - LOG.warning('`token_api.%s` is deprecated as of Juno in favor of ' - 'utilizing methods on `token_provider_api` and may be ' - 'removed in Kilo.', item) - setattr(self, item, f) - return f - - @six.add_metaclass(abc.ABCMeta) class TokenDriverBase(object): """Interface description for a Token driver.""" diff --git a/keystone/token/provider.py b/keystone/token/provider.py index df312fc849..b730419d17 100644 --- a/keystone/token/provider.py +++ b/keystone/token/provider.py @@ -22,14 +22,12 @@ from oslo_utils import timeutils import six from keystone.common import cache -from keystone.common import dependency from keystone.common import manager import keystone.conf from keystone import exception from keystone.i18n import _ from keystone.models import token_model from keystone import notifications -from keystone.token import persistence CONF = keystone.conf.CONF @@ -49,8 +47,6 @@ V3 = token_model.V3 VERSIONS = token_model.VERSIONS -@dependency.provider('token_provider_api') -@dependency.requires('assignment_api', 'revoke_api') class Manager(manager.Manager): """Default pivot point for the token provider backend. @@ -60,6 +56,7 @@ class Manager(manager.Manager): """ driver_namespace = 'keystone.token.provider' + _provides_api = 'token_provider_api' V3 = V3 VERSIONS = VERSIONS @@ -111,7 +108,7 @@ class Manager(manager.Manager): # the provider manager requires the token persistence manager, which # requires the token provider manager). if self._persistence_manager is None: - self._persistence_manager = persistence.PersistenceManager() + self._persistence_manager = self._token_persistence_manager return self._persistence_manager def _create_token(self, token_id, token_data): diff --git a/keystone/token/providers/common.py b/keystone/token/providers/common.py index d0e6512d52..eea8b0685a 100644 --- a/keystone/token/providers/common.py +++ b/keystone/token/providers/common.py @@ -24,7 +24,7 @@ from oslo_utils import timeutils import six from six.moves.urllib import parse -from keystone.common import dependency +from keystone.common import provider_api from keystone.common import utils import keystone.conf from keystone import exception @@ -82,10 +82,7 @@ def build_audit_info(parent_audit_id=None): return [audit_id] -@dependency.requires('assignment_api', 'catalog_api', 'federation_api', - 'identity_api', 'resource_api', 'role_api', 'trust_api', - 'oauth_api') -class V3TokenDataHelper(object): +class V3TokenDataHelper(provider_api.ProviderAPIMixin, object): """Token data helper.""" def __init__(self): @@ -451,9 +448,7 @@ class V3TokenDataHelper(object): return {'token': token_data} -@dependency.requires('catalog_api', 'identity_api', 'oauth_api', - 'resource_api', 'role_api', 'trust_api') -class BaseProvider(base.Provider): +class BaseProvider(provider_api.ProviderAPIMixin, base.Provider): def __init__(self, *args, **kwargs): super(BaseProvider, self).__init__(*args, **kwargs) self.v3_token_data_helper = V3TokenDataHelper() diff --git a/keystone/token/providers/fernet/core.py b/keystone/token/providers/fernet/core.py index bbcfdf4370..4696e71834 100644 --- a/keystone/token/providers/fernet/core.py +++ b/keystone/token/providers/fernet/core.py @@ -13,7 +13,6 @@ import os -from keystone.common import dependency from keystone.common import utils as ks_utils import keystone.conf from keystone.federation import constants as federation_constants @@ -25,7 +24,6 @@ from keystone.token.providers.fernet import token_formatters as tf CONF = keystone.conf.CONF -@dependency.requires('trust_api', 'oauth_api', 'identity_api') class Provider(common.BaseProvider): def __init__(self, *args, **kwargs): super(Provider, self).__init__(*args, **kwargs) diff --git a/keystone/trust/controllers.py b/keystone/trust/controllers.py index dac848c1f2..ebb5842abc 100644 --- a/keystone/trust/controllers.py +++ b/keystone/trust/controllers.py @@ -18,7 +18,6 @@ from oslo_utils import timeutils from keystone import assignment from keystone.common import controller -from keystone.common import dependency from keystone.common import driver_hints from keystone.common import utils from keystone.common import validation @@ -34,8 +33,6 @@ def _trustor_trustee_only(trust, user_id): action=_('Requested user has no relation to this trust')) -@dependency.requires('assignment_api', 'identity_api', 'resource_api', - 'role_api', 'token_provider_api', 'trust_api') class TrustV3(controller.V3Controller): collection_name = "trusts" member_name = "trust" diff --git a/keystone/trust/core.py b/keystone/trust/core.py index 84f3b707e2..d4d17266e1 100644 --- a/keystone/trust/core.py +++ b/keystone/trust/core.py @@ -16,7 +16,6 @@ from six.moves import zip -from keystone.common import dependency from keystone.common import manager import keystone.conf from keystone import exception @@ -27,8 +26,6 @@ from keystone import notifications CONF = keystone.conf.CONF -@dependency.requires('identity_api') -@dependency.provider('trust_api') class Manager(manager.Manager): """Default pivot point for the Trust backend. @@ -38,6 +35,7 @@ class Manager(manager.Manager): """ driver_namespace = 'keystone.trust' + _provides_api = 'trust_api' _TRUST = "OS-TRUST:trust"