Merge "Notifications upon disable"

This commit is contained in:
Jenkins 2014-02-13 03:43:34 +00:00 committed by Gerrit Code Review
commit c7b2b11150
3 changed files with 120 additions and 35 deletions

View File

@ -80,15 +80,19 @@ class Manager(manager.Manager):
ret['domain_id'])
return ret
@notifications.disabled('project', public=False)
def _disable_project(self, tenant_id):
return self.token_api.delete_tokens_for_users(
self.list_user_ids_for_project(tenant_id),
project_id=tenant_id)
@notifications.updated('project')
def update_project(self, tenant_id, tenant):
tenant = tenant.copy()
if 'enabled' in tenant:
tenant['enabled'] = clean.project_enabled(tenant['enabled'])
if not tenant.get('enabled', True):
self.token_api.delete_tokens_for_users(
self.list_user_ids_for_project(tenant_id),
project_id=tenant_id)
self._disable_project(tenant_id)
ret = self.driver.update_project(tenant_id, tenant)
self.get_project.invalidate(self, tenant_id)
self.get_project_by_name.invalidate(self, ret['name'],
@ -300,12 +304,17 @@ class Manager(manager.Manager):
def list_domains(self, hints=None):
return self.driver.list_domains(hints or driver_hints.Hints())
@notifications.disabled('domain', public=False)
def _disable_domain(self, domain_id):
self.token_api.delete_tokens_for_domain(domain_id)
@notifications.updated('domain')
def update_domain(self, domain_id, domain):
ret = self.driver.update_domain(domain_id, domain)
# disable owned users & projects when the API user specifically set
# enabled=False
if not domain.get('enabled', True):
self.token_api.delete_tokens_for_domain(domain_id)
self._disable_domain(domain_id)
self.get_domain.invalidate(self, domain_id)
self.get_domain_by_name.invalidate(self, ret['name'])
return ret

View File

@ -23,7 +23,7 @@ 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'])
ACTIONS = frozenset(['created', 'deleted', 'disabled', 'updated'])
# resource types that can be notified
SUBSCRIBERS = {}
@ -34,11 +34,17 @@ class ManagerNotificationWrapper(object):
Sends a notification if the wrapped Manager method does not raise an
``Exception`` (such as ``keystone.exception.NotFound``).
:param operation: one of the values from ACTIONS
:param resource_type: type of resource being affected
: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
"""
def __init__(self, operation, resource_type):
def __init__(self, operation, resource_type, public=True):
self.operation = operation
self.resource_type = resource_type
self.public = public
def __call__(self, f):
def wrapper(*args, **kwargs):
@ -48,10 +54,11 @@ class ManagerNotificationWrapper(object):
except Exception:
raise
else:
_send_notification(
send_notification(
self.operation,
self.resource_type,
args[1]) # f(self, resource_id, ...)
args[1], # f(self, resource_id, ...)
public=self.public)
return result
return wrapper
@ -67,6 +74,11 @@ def updated(*args, **kwargs):
return ManagerNotificationWrapper('updated', *args, **kwargs)
def disabled(*args, **kwargs):
"""Decorator to send notifications when an object is disabled."""
return ManagerNotificationWrapper('disabled', *args, **kwargs)
def deleted(*args, **kwargs):
"""Decorator to send notifications for ``Manager.delete_*`` methods."""
return ManagerNotificationWrapper('deleted', *args, **kwargs)
@ -126,7 +138,8 @@ def notify_event_callbacks(service, resource_type, operation, payload):
cb(service, resource_type, operation, payload)
def _send_notification(operation, resource_type, resource_id):
def send_notification(operation, resource_type, resource_id,
public=True):
"""Send notification to inform observers about the affected resource.
This method doesn't raise an exception when sending the notification fails.
@ -134,6 +147,10 @@ def _send_notification(operation, resource_type, resource_id):
: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 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.
"""
context = {}
payload = {'resource_info': resource_id}
@ -146,10 +163,11 @@ def _send_notification(operation, resource_type, resource_id):
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})
if public:
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})

View File

@ -38,7 +38,8 @@ class NotificationsWrapperTestCase(tests.TestCase):
self.exp_operation = None
self.send_notification_called = False
def fake_notify(operation, resource_type, resource_id):
def fake_notify(operation, resource_type, resource_id,
public=True):
self.assertEqual(self.exp_operation, operation)
self.assertEqual(EXP_RESOURCE_TYPE, resource_type)
self.assertEqual(self.exp_resource_id, resource_id)
@ -47,7 +48,7 @@ class NotificationsWrapperTestCase(tests.TestCase):
fixture = self.useFixture(moxstubout.MoxStubout())
self.stubs = fixture.stubs
self.stubs.Set(notifications, '_send_notification', fake_notify)
self.stubs.Set(notifications, 'send_notification', fake_notify)
@notifications.created(EXP_RESOURCE_TYPE)
def create_resource(self, resource_id, data):
@ -148,36 +149,62 @@ class NotificationsTestCase(tests.TestCase):
mod_path = 'keystone.notifications.notifier_api.notify'
with mock.patch(mod_path) as mocked:
notifications._send_notification(operation, resource_type,
resource)
notifications.send_notification(operation, resource_type,
resource)
mocked.assert_called_once_with(*expected_args)
class NotificationsForEntities(test_v3.RestfulTestCase):
def setUp(self):
super(NotificationsForEntities, self).setUp()
self._notifications = []
self.exp_resource_id = None
self.exp_operation = None
self.exp_resource_type = None
self.send_notification_called = False
def fake_notify(operation, resource_type, resource_id):
self.exp_resource_id = resource_id
self.exp_operation = operation
self.exp_resource_type = resource_type
self.send_notification_called = True
def fake_notify(operation, resource_type, resource_id,
public=True):
note = {
'resource_id': resource_id,
'operation': operation,
'resource_type': resource_type,
'send_notification_called': True,
'public': public}
self._notifications.append(note)
fixture = self.useFixture(moxstubout.MoxStubout())
self.stubs = fixture.stubs
self.stubs.Set(notifications, '_send_notification', fake_notify)
self.stubs.Set(notifications, 'send_notification', fake_notify)
def _assertLastNotify(self, resource_id, operation, resource_type):
self.assertEqual(self.exp_operation, operation)
self.assertEqual(self.exp_resource_id, resource_id)
self.assertEqual(self.exp_resource_type, resource_type)
self.assertTrue(self.send_notification_called)
self.assertTrue(len(self._notifications) > 0)
note = self._notifications[-1]
self.assertEqual(note['operation'], operation)
self.assertEqual(note['resource_id'], resource_id)
self.assertEqual(note['resource_type'], resource_type)
self.assertTrue(note['send_notification_called'])
def _assertNotifyNotSent(self, resource_id, operation, resource_type,
public=True):
unexpected = {
'resource_id': resource_id,
'operation': operation,
'resource_type': resource_type,
'send_notification_called': True,
'public': public}
for note in self._notifications:
self.assertNotEqual(unexpected, note)
def _assertNotifySent(self, resource_id, operation, resource_type, public):
expected = {
'resource_id': resource_id,
'operation': operation,
'resource_type': resource_type,
'send_notification_called': True,
'public': public}
for note in self._notifications:
if expected == note:
break
else:
self.fail("Notification not sent.")
def test_create_group(self):
group_ref = self.new_group_ref(domain_id=self.domain_id)
@ -237,6 +264,12 @@ class NotificationsForEntities(test_v3.RestfulTestCase):
self.identity_api.delete_user(user_ref['id'])
self._assertLastNotify(user_ref['id'], 'deleted', 'user')
def test_update_domain(self):
domain_ref = self.new_domain_ref()
self.assignment_api.create_domain(domain_ref['id'], domain_ref)
self.assignment_api.update_domain(domain_ref['id'], domain_ref)
self._assertLastNotify(domain_ref['id'], 'updated', 'domain')
def test_delete_trust(self):
trustor = self.new_user_ref(domain_id=self.domain_id)
self.identity_api.create_user(trustor['id'], trustor)
@ -250,6 +283,14 @@ class NotificationsForEntities(test_v3.RestfulTestCase):
self.trust_api.delete_trust(trust_ref['id'])
self._assertLastNotify(trust_ref['id'], 'deleted', 'OS-TRUST:trust')
def test_disable_domain(self):
domain_ref = self.new_domain_ref()
self.identity_api.create_domain(domain_ref['id'], domain_ref)
domain_ref['enabled'] = False
self.identity_api.update_domain(domain_ref['id'], domain_ref)
self._assertNotifySent(domain_ref['id'], 'disabled', 'domain',
public=False)
def test_update_group(self):
group_ref = self.new_group_ref(domain_id=self.domain_id)
self.identity_api.create_group(group_ref['id'], group_ref)
@ -260,7 +301,24 @@ class NotificationsForEntities(test_v3.RestfulTestCase):
project_ref = self.new_project_ref(domain_id=self.domain_id)
self.assignment_api.create_project(project_ref['id'], project_ref)
self.assignment_api.update_project(project_ref['id'], project_ref)
self._assertNotifySent(project_ref['id'], 'updated', 'project',
public=True)
def test_disable_project(self):
project_ref = self.new_project_ref(domain_id=self.domain_id)
self.assignment_api.create_project(project_ref['id'], project_ref)
project_ref['enabled'] = False
self.assignment_api.update_project(project_ref['id'], project_ref)
self._assertNotifySent(project_ref['id'], 'disabled', 'project',
public=False)
def test_update_project_does_not_send_disable(self):
project_ref = self.new_project_ref(domain_id=self.domain_id)
self.assignment_api.create_project(project_ref['id'], project_ref)
project_ref['enabled'] = True
self.assignment_api.update_project(project_ref['id'], project_ref)
self._assertLastNotify(project_ref['id'], 'updated', 'project')
self._assertNotifyNotSent(project_ref['id'], 'disabled', 'project')
def test_update_role(self):
role_ref = self.new_role_ref()