Add ability to send notifications for actors

Previously, keystone didn't have the ability to send notifications for
actor/target relationships due to the structure of the payload. The payload was
always expected to be the ID and type of one entity. In notifications that have
relationships (like adding or removing users from groups), it is useful to know
who was added to what.

This commit introduces the ability to send information about who was added or
removed from an entity.

Change-Id: Iea4437a265f1910a1171b9bcac99d853f5015440
Partial-Bug: 1552639
This commit is contained in:
Lance Bragstad 2016-03-03 16:38:04 +00:00
parent e2ee064192
commit 06e4bb776c
2 changed files with 43 additions and 4 deletions

View File

@ -109,7 +109,8 @@ class Audit(object):
"""
@classmethod
def _emit(cls, operation, resource_type, resource_id, initiator, public):
def _emit(cls, operation, resource_type, resource_id, initiator, public,
actor_dict=None):
"""Directly send an event notification.
:param operation: one of the values from ACTIONS
@ -120,6 +121,8 @@ class Audit(object):
:param public: If True (default), the event will be sent to the
notifier API. If False, the event will only be sent via
notify_event_callbacks to in process listeners
:param actor_dict: dictionary of actor information in the event of
assignment notification
"""
# NOTE(stevemar): the _send_notification function is
# overloaded, it's used to register callbacks and to actually
@ -130,6 +133,7 @@ class Audit(object):
operation,
resource_type,
resource_id,
actor_dict,
public=public)
if CONF.notification_format == 'cadf' and public:
@ -161,6 +165,24 @@ class Audit(object):
cls._emit(ACTIONS.deleted, resource_type, resource_id, initiator,
public)
@classmethod
def added_to(cls, target_type, target_id, actor_type, actor_id,
initiator=None, public=True):
actor_dict = {'id': actor_id,
'type': actor_type,
'actor_operation': 'added'}
cls._emit(ACTIONS.updated, target_type, target_id, initiator, public,
actor_dict=actor_dict)
@classmethod
def removed_from(cls, target_type, target_id, actor_type, actor_id,
initiator=None, public=True):
actor_dict = {'id': actor_id,
'type': actor_type,
'actor_operation': 'removed'}
cls._emit(ACTIONS.updated, target_type, target_id, initiator, public,
actor_dict=actor_dict)
class ManagerNotificationWrapper(object):
"""Send event notifications for ``Manager`` methods.
@ -450,7 +472,8 @@ def _create_cadf_payload(operation, resource_type, resource_id,
target, event_type, **audit_kwargs)
def _send_notification(operation, resource_type, resource_id, public=True):
def _send_notification(operation, resource_type, resource_id, actor_dict=None,
public=True):
"""Send notification to inform observers about the affected resource.
This method doesn't raise an exception when sending the notification fails.
@ -458,6 +481,7 @@ def _send_notification(operation, resource_type, resource_id, public=True):
: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
:param actor_dict: a dictionary containing the actor's ID and type
:param public: if True (default), the event will be sent
to the notifier API.
if False, the event will only be sent via
@ -465,6 +489,11 @@ def _send_notification(operation, resource_type, resource_id, public=True):
"""
payload = {'resource_info': resource_id}
if actor_dict:
payload['actor_id'] = actor_dict['id']
payload['actor_type'] = actor_dict['type']
payload['actor_operation'] = actor_dict['actor_operation']
notify_event_callbacks(SERVICE, resource_type, operation, payload)
# Only send this notification if the 'basic' format is used, otherwise

View File

@ -290,13 +290,17 @@ class BaseNotificationTest(test_v3.RestfulTestCase):
self._audits = []
def fake_notify(operation, resource_type, resource_id,
public=True):
actor_dict=None, public=True):
note = {
'resource_id': resource_id,
'operation': operation,
'resource_type': resource_type,
'send_notification_called': True,
'public': public}
if actor_dict:
note['actor_id'] = actor_dict.get('id')
note['actor_type'] = actor_dict.get('type')
note['actor_operation'] = actor_dict.get('actor_operation')
self._notifications.append(note)
self.useFixture(mockpatch.PatchObject(
@ -326,7 +330,9 @@ class BaseNotificationTest(test_v3.RestfulTestCase):
self.useFixture(mockpatch.PatchObject(
notifications, '_send_audit_notification', fake_audit))
def _assert_last_note(self, resource_id, operation, resource_type):
def _assert_last_note(self, resource_id, operation, resource_type,
actor_id=None, actor_type=None,
actor_operation=None):
# NOTE(stevemar): If 'basic' format is not used, then simply
# return since this assertion is not valid.
if CONF.notification_format != 'basic':
@ -337,6 +343,10 @@ class BaseNotificationTest(test_v3.RestfulTestCase):
self.assertEqual(resource_id, note['resource_id'])
self.assertEqual(resource_type, note['resource_type'])
self.assertTrue(note['send_notification_called'])
if actor_id:
self.assertEqual(actor_id, note['actor_id'])
self.assertEqual(actor_type, note['actor_type'])
self.assertEqual(actor_operation, note['actor_operation'])
def _assert_last_audit(self, resource_id, operation, resource_type,
target_uri):