Decouple notifications from DI
This appears to have been a marriage of convenience. There is no real reason to combine these two concepts. bp object-dependency-lifecycle Change-Id: I2dead4f66675f73a9ebc1551f257218a085211f0
This commit is contained in:
parent
2bf413ecda
commit
893a10144a
@ -30,7 +30,6 @@ import traceback
|
||||
import six
|
||||
|
||||
from keystone.i18n import _
|
||||
from keystone import notifications
|
||||
|
||||
|
||||
_REGISTRY = {}
|
||||
@ -94,44 +93,10 @@ def provider(name):
|
||||
"""
|
||||
def wrapper(cls):
|
||||
def wrapped(init):
|
||||
def register_event_callbacks(self):
|
||||
# NOTE(morganfainberg): A provider who has an implicit
|
||||
# dependency on other providers may utilize the event callback
|
||||
# mechanism to react to any changes in those providers. This is
|
||||
# performed at the .provider() mechanism so that we can ensure
|
||||
# that the callback is only ever called once and guaranteed
|
||||
# to be on the properly configured and instantiated backend.
|
||||
if not hasattr(self, 'event_callbacks'):
|
||||
return
|
||||
|
||||
if not isinstance(self.event_callbacks, dict):
|
||||
msg = _('event_callbacks must be a dict')
|
||||
raise ValueError(msg)
|
||||
|
||||
for event in self.event_callbacks:
|
||||
if not isinstance(self.event_callbacks[event], dict):
|
||||
msg = _('event_callbacks[%s] must be a dict') % event
|
||||
raise ValueError(msg)
|
||||
for resource_type in self.event_callbacks[event]:
|
||||
# Make sure we register the provider for each event it
|
||||
# cares to call back.
|
||||
callbacks = self.event_callbacks[event][resource_type]
|
||||
if not callbacks:
|
||||
continue
|
||||
if not hasattr(callbacks, '__iter__'):
|
||||
# ensure the callback information is a list
|
||||
# allowing multiple callbacks to exist
|
||||
callbacks = [callbacks]
|
||||
notifications.register_event_callback(event,
|
||||
resource_type,
|
||||
callbacks)
|
||||
|
||||
def __wrapped_init__(self, *args, **kwargs):
|
||||
"""Initialize the wrapped object and add it to the registry."""
|
||||
init(self, *args, **kwargs)
|
||||
_set_provider(name, self)
|
||||
register_event_callbacks(self)
|
||||
|
||||
resolve_future_dependencies(__provider_name=name)
|
||||
|
||||
return __wrapped_init__
|
||||
|
@ -24,6 +24,7 @@ from keystone import notifications
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
@notifications.listener # NOTE(dstanek): only needed if using event_callbacks
|
||||
@dependency.provider('example_api')
|
||||
class ExampleManager(manager.Manager):
|
||||
"""Example Manager.
|
||||
@ -47,8 +48,8 @@ class ExampleManager(manager.Manager):
|
||||
# project_created_callback will be invoked whenever a new project is
|
||||
# created.
|
||||
|
||||
# This information is used when the @dependency.provider decorator acts
|
||||
# on the class.
|
||||
# This information is used when the @notifications.listener decorator
|
||||
# acts on the class.
|
||||
self.event_callbacks = {
|
||||
notifications.ACTIONS.deleted: {
|
||||
'project': [self.project_deleted_callback],
|
||||
|
@ -364,6 +364,7 @@ def exception_translated(exception_type):
|
||||
return _exception_translated
|
||||
|
||||
|
||||
@notifications.listener
|
||||
@dependency.provider('identity_api')
|
||||
@dependency.requires('assignment_api', 'credential_api', 'id_mapping_api',
|
||||
'resource_api', 'revoke_api')
|
||||
|
@ -15,6 +15,7 @@
|
||||
"""Notifications module for OpenStack Identity Service resources"""
|
||||
|
||||
import collections
|
||||
import functools
|
||||
import inspect
|
||||
import logging
|
||||
import socket
|
||||
@ -292,6 +293,72 @@ def register_event_callback(event, resource_type, callbacks):
|
||||
LOG.debug(msg, {'callback': callback_str, 'event': event_str})
|
||||
|
||||
|
||||
def listener(cls):
|
||||
"""A class decorator to declare a class to be a notification listener.
|
||||
|
||||
A notification listener must specify the event(s) it is interested in by
|
||||
defining a ``event_callbacks`` attribute or property. ``event_callbacks``
|
||||
is a dictionary where the key is the type of event and the value is a
|
||||
dictionary containing a mapping of resource types to callback(s).
|
||||
|
||||
``keystone.notifications.ACTIONS`` contains constants for the currently
|
||||
supported events. There is currently no single place to find constants for
|
||||
the resource types.
|
||||
|
||||
Example::
|
||||
|
||||
@listener
|
||||
class Something(object):
|
||||
|
||||
event_callbacks = {
|
||||
notifications.ACTIONS.created: {
|
||||
'user': self._user_created_callback,
|
||||
},
|
||||
notifications.ACTIONS.deleted: {
|
||||
'project': [
|
||||
self._project_deleted_callback,
|
||||
self._do_cleanup,
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
def init_wrapper(init):
|
||||
@functools.wraps(init)
|
||||
def __new_init__(self, *args, **kwargs):
|
||||
init(self, *args, **kwargs)
|
||||
if not hasattr(self, 'event_callbacks'):
|
||||
msg = _("%r object has no attribute 'event_callbacks'")
|
||||
raise AttributeError(msg % self.__class__.__name)
|
||||
_register_event_callbacks(self)
|
||||
return __new_init__
|
||||
|
||||
def _register_event_callbacks(self):
|
||||
if not isinstance(self.event_callbacks, dict):
|
||||
msg = _('event_callbacks must be a dict')
|
||||
raise ValueError(msg)
|
||||
|
||||
for event in self.event_callbacks:
|
||||
if not isinstance(self.event_callbacks[event], dict):
|
||||
msg = _('event_callbacks[%s] must be a dict') % event
|
||||
raise ValueError(msg)
|
||||
for resource_type in self.event_callbacks[event]:
|
||||
# Make sure we register the provider for each event it
|
||||
# cares to call back.
|
||||
callbacks = self.event_callbacks[event][resource_type]
|
||||
if not callbacks:
|
||||
continue
|
||||
if not hasattr(callbacks, '__iter__'):
|
||||
# ensure the callback information is a list
|
||||
# allowing multiple callbacks to exist
|
||||
callbacks = [callbacks]
|
||||
register_event_callback(event, resource_type, callbacks)
|
||||
|
||||
cls.__init__ = init_wrapper(cls.__init__)
|
||||
return cls
|
||||
|
||||
|
||||
def notify_event_callbacks(service, resource_type, operation, payload):
|
||||
"""Sends a notification to registered extensions."""
|
||||
if operation in _SUBSCRIBERS:
|
||||
|
@ -24,7 +24,6 @@ from pycadf import cadftype
|
||||
from pycadf import eventfactory
|
||||
from pycadf import resource as cadfresource
|
||||
|
||||
from keystone.common import dependency
|
||||
from keystone import notifications
|
||||
from keystone.tests import unit
|
||||
from keystone.tests.unit import test_v3
|
||||
@ -695,7 +694,7 @@ class TestEventCallbacks(test_v3.RestfulTestCase):
|
||||
def test_provider_event_callbacks_subscription(self):
|
||||
callback_called = []
|
||||
|
||||
@dependency.provider('foo_api')
|
||||
@notifications.listener
|
||||
class Foo(object):
|
||||
def __init__(self):
|
||||
self.event_callbacks = {
|
||||
@ -712,7 +711,7 @@ class TestEventCallbacks(test_v3.RestfulTestCase):
|
||||
self.assertEqual([True], callback_called)
|
||||
|
||||
def test_invalid_event_callbacks(self):
|
||||
@dependency.provider('foo_api')
|
||||
@notifications.listener
|
||||
class Foo(object):
|
||||
def __init__(self):
|
||||
self.event_callbacks = 'bogus'
|
||||
@ -720,7 +719,7 @@ class TestEventCallbacks(test_v3.RestfulTestCase):
|
||||
self.assertRaises(ValueError, Foo)
|
||||
|
||||
def test_invalid_event_callbacks_event(self):
|
||||
@dependency.provider('foo_api')
|
||||
@notifications.listener
|
||||
class Foo(object):
|
||||
def __init__(self):
|
||||
self.event_callbacks = {CREATED_OPERATION: 'bogus'}
|
||||
|
Loading…
Reference in New Issue
Block a user