Browse Source

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
changes/03/499703/6
Morgan Fainberg 4 years ago
parent
commit
81f9fe6fed
  1. 1
      keystone/assignment/backends/sql.py
  2. 8
      keystone/assignment/controllers.py
  3. 16
      keystone/assignment/core.py
  4. 3
      keystone/auth/controllers.py
  5. 8
      keystone/auth/core.py
  6. 3
      keystone/auth/plugins/base.py
  7. 5
      keystone/auth/plugins/core.py
  8. 3
      keystone/auth/plugins/external.py
  9. 3
      keystone/auth/plugins/mapped.py
  10. 2
      keystone/auth/plugins/oauth1.py
  11. 2
      keystone/auth/plugins/password.py
  12. 2
      keystone/auth/plugins/token.py
  13. 2
      keystone/auth/plugins/totp.py
  14. 3
      keystone/catalog/backends/base.py
  15. 2
      keystone/catalog/backends/sql.py
  16. 7
      keystone/catalog/controllers.py
  17. 4
      keystone/catalog/core.py
  18. 8
      keystone/common/controller.py
  19. 170
      keystone/common/dependency.py
  20. 26
      keystone/common/manager.py
  21. 87
      keystone/common/provider_api.py
  22. 6
      keystone/common/tokenless_auth.py
  23. 9
      keystone/contrib/ec2/controllers.py
  24. 2
      keystone/credential/controllers.py
  25. 4
      keystone/credential/core.py
  26. 3
      keystone/credential/provider.py
  27. 2
      keystone/endpoint_policy/controllers.py
  28. 4
      keystone/endpoint_policy/core.py
  29. 8
      keystone/federation/controllers.py
  30. 4
      keystone/federation/core.py
  31. 3
      keystone/identity/controllers.py
  32. 13
      keystone/identity/core.py
  33. 3
      keystone/identity/generator.py
  34. 3
      keystone/identity/mapping_backends/base.py
  35. 2
      keystone/identity/mapping_backends/sql.py
  36. 6
      keystone/middleware/auth.py
  37. 5
      keystone/notifications.py
  38. 6
      keystone/oauth1/controllers.py
  39. 3
      keystone/oauth1/core.py
  40. 5
      keystone/oauth1/validator.py
  41. 2
      keystone/policy/controllers.py
  42. 3
      keystone/policy/core.py
  43. 6
      keystone/resource/controllers.py
  44. 9
      keystone/resource/core.py
  45. 2
      keystone/revoke/controllers.py
  46. 3
      keystone/revoke/core.py
  47. 40
      keystone/server/backends.py
  48. 2
      keystone/server/common.py
  49. 238
      keystone/tests/unit/common/test_injection.py
  50. 64
      keystone/tests/unit/common/test_provider_api.py
  51. 8
      keystone/tests/unit/core.py
  52. 6
      keystone/tests/unit/ksfixtures/backendloader.py
  53. 25
      keystone/tests/unit/test_cli.py
  54. 18
      keystone/tests/unit/test_token_provider.py
  55. 3
      keystone/tests/unit/test_v3_auth.py
  56. 10
      keystone/tests/unit/test_v3_oauth1.py
  57. 2
      keystone/token/_simple_cert.py
  58. 10
      keystone/token/controllers.py
  59. 2
      keystone/token/persistence/__init__.py
  60. 35
      keystone/token/persistence/core.py
  61. 7
      keystone/token/provider.py
  62. 11
      keystone/token/providers/common.py
  63. 2
      keystone/token/providers/fernet/core.py
  64. 3
      keystone/trust/controllers.py
  65. 4
      keystone/trust/core.py

1
keystone/assignment/backends/sql.py

@ -42,6 +42,7 @@ class AssignmentType(object):
class Assignment(base.AssignmentDriverBase):
@classmethod
def default_role_driver(self):
return 'sql'

8
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()."""

16
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)

3
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

8
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

3
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):

5
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):

3
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.

3
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):

2
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."""

2
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):

2
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):

2
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):

3
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):

2
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):

7
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'

4
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'

8
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

170
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."""

26
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)

87
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()

6
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.

9
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'

2
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'

4
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)

3
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)

2
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'

4
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)

8
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."""

4
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)

3
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'

13
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

3
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)

3
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

2
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):

6
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

5
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."""

6
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