158 lines
5.8 KiB
Python
158 lines
5.8 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2013 IBM Corp.
|
|
#
|
|
# 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.
|
|
|
|
"""Notifications module for OpenStack Identity Service resources"""
|
|
|
|
import logging
|
|
|
|
from keystone.openstack.common import log
|
|
from keystone.openstack.common.notifier import api as notifier_api
|
|
|
|
|
|
LOG = log.getLogger(__name__)
|
|
# NOTE(gyee): actions that can be notified. One must update this list whenever
|
|
# a new action is supported.
|
|
ACTIONS = frozenset(['created', 'deleted', 'updated'])
|
|
# resource types that can be notified
|
|
SUBSCRIBERS = {}
|
|
|
|
|
|
class ManagerNotificationWrapper(object):
|
|
"""Send event notifications for ``Manager`` methods.
|
|
|
|
Sends a notification if the wrapped Manager method does not raise an
|
|
``Exception`` (such as ``keystone.exception.NotFound``).
|
|
|
|
:param resource_type: type of resource being affected
|
|
"""
|
|
def __init__(self, operation, resource_type):
|
|
self.operation = operation
|
|
self.resource_type = resource_type
|
|
|
|
def __call__(self, f):
|
|
def wrapper(*args, **kwargs):
|
|
"""Send a notification if the wrapped callable is successful."""
|
|
try:
|
|
result = f(*args, **kwargs)
|
|
except Exception:
|
|
raise
|
|
else:
|
|
_send_notification(
|
|
self.operation,
|
|
self.resource_type,
|
|
args[1]) # f(self, resource_id, ...)
|
|
return result
|
|
|
|
return wrapper
|
|
|
|
|
|
def created(*args, **kwargs):
|
|
"""Decorator to send notifications for ``Manager.create_*`` methods."""
|
|
return ManagerNotificationWrapper('created', *args, **kwargs)
|
|
|
|
|
|
def updated(*args, **kwargs):
|
|
"""Decorator to send notifications for ``Manager.update_*`` methods."""
|
|
return ManagerNotificationWrapper('updated', *args, **kwargs)
|
|
|
|
|
|
def deleted(*args, **kwargs):
|
|
"""Decorator to send notifications for ``Manager.delete_*`` methods."""
|
|
return ManagerNotificationWrapper('deleted', *args, **kwargs)
|
|
|
|
|
|
def _get_callback_info(callback):
|
|
if getattr(callback, 'im_class', None):
|
|
return [getattr(callback, '__module__', None),
|
|
callback.im_class.__name__,
|
|
callback.__name__]
|
|
else:
|
|
return [getattr(callback, '__module__', None), callback.__name__]
|
|
|
|
|
|
def register_event_callback(event, resource_type, callbacks):
|
|
if event not in ACTIONS:
|
|
raise ValueError(_('%(event)s is not a valid notification event, must '
|
|
'be one of: %(actions)s') %
|
|
{'event': event, 'actions': ', '.join(ACTIONS)})
|
|
|
|
if not hasattr(callbacks, '__iter__'):
|
|
callbacks = [callbacks]
|
|
|
|
for callback in callbacks:
|
|
if not callable(callback):
|
|
msg = _('Method not callable: %s') % callback
|
|
LOG.error(msg)
|
|
raise TypeError(msg)
|
|
SUBSCRIBERS.setdefault(event, {}).setdefault(resource_type, set())
|
|
SUBSCRIBERS[event][resource_type].add(callback)
|
|
|
|
if LOG.logger.getEffectiveLevel() <= logging.INFO:
|
|
# Do this only if its going to appear in the logs.
|
|
msg = _('Callback: `%(callback)s` subscribed to event '
|
|
'`%(event)s`.')
|
|
callback_info = _get_callback_info(callback)
|
|
callback_str = '.'.join(
|
|
filter(lambda i: i is not None, callback_info))
|
|
event_str = '.'.join(['identity', resource_type, event])
|
|
LOG.info(msg, {'callback': callback_str, 'event': event_str})
|
|
|
|
|
|
def notify_event_callbacks(service, resource_type, operation, payload):
|
|
"""Sends a notification to registered extensions."""
|
|
if operation in SUBSCRIBERS:
|
|
if resource_type in SUBSCRIBERS[operation]:
|
|
for cb in SUBSCRIBERS[operation][resource_type]:
|
|
subst_dict = {'cb_name': cb.__name__,
|
|
'service': service,
|
|
'resource_type': resource_type,
|
|
'operation': operation,
|
|
'payload': payload}
|
|
LOG.debug(_('Invoking callback %(cb_name)s for event '
|
|
'%(service)s %(resource_type)s %(operation)s for'
|
|
'%(payload)s'),
|
|
subst_dict)
|
|
cb(service, resource_type, operation, payload)
|
|
|
|
|
|
def _send_notification(operation, resource_type, resource_id):
|
|
"""Send notification to inform observers about the affected resource.
|
|
|
|
This method doesn't raise an exception when sending the notification fails.
|
|
|
|
:param operation: operation being performed (created, updated, or deleted)
|
|
:param resource_type: type of resource being operated on
|
|
:param resource_id: ID of resource being operated on
|
|
"""
|
|
context = {}
|
|
payload = {'resource_info': resource_id}
|
|
service = 'identity'
|
|
publisher_id = notifier_api.publisher_id(service)
|
|
event_type = '%(service)s.%(resource_type)s.%(operation)s' % {
|
|
'service': service,
|
|
'resource_type': resource_type,
|
|
'operation': operation}
|
|
|
|
notify_event_callbacks(service, resource_type, operation, payload)
|
|
|
|
try:
|
|
notifier_api.notify(
|
|
context, publisher_id, event_type, notifier_api.INFO, payload)
|
|
except Exception:
|
|
LOG.exception(
|
|
_('Failed to send %(res_id)s %(event_type)s notification'),
|
|
{'res_id': resource_id, 'event_type': event_type})
|