keystone/keystone/tests/test_notifications.py
Henry Nash 799758b5b7 Migrate ID generation for users/groups from controller to manager
The implementation of multi-backend unique IDs requires that the
manager layer is in charge of ID generation.  This patch lays that
ground work.  No functional change should be observed from this patch.

Partially Implements: blueprint multi-backend-uuids

Change-Id: I2253aa9872d1c9950ef35851bd3f11d7ab5e433b
2014-06-20 12:19:03 -07:00

592 lines
23 KiB
Python

# 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.
import uuid
import mock
from oslo.config import cfg
from oslotest import mockpatch
import testtools
from keystone.common import dependency
from keystone import notifications
from keystone.tests import test_v3
CONF = cfg.CONF
EXP_RESOURCE_TYPE = uuid.uuid4().hex
CREATED_OPERATION = 'created'
UPDATED_OPERATION = 'updated'
DELETED_OPERATION = 'deleted'
class ArbitraryException(Exception):
pass
def register_callback(operation, resource_type=EXP_RESOURCE_TYPE):
"""Helper for creating and registering a mock callback.
"""
callback = mock.Mock(__name__='callback',
im_class=mock.Mock(__name__='class'))
notifications.register_event_callback(operation, resource_type, callback)
return callback
class NotificationsWrapperTestCase(testtools.TestCase):
def create_fake_ref(self):
resource_id = uuid.uuid4().hex
return resource_id, {
'id': resource_id,
'key': uuid.uuid4().hex
}
@notifications.created(EXP_RESOURCE_TYPE)
def create_resource(self, resource_id, data):
return data
def test_resource_created_notification(self):
exp_resource_id, data = self.create_fake_ref()
callback = register_callback(CREATED_OPERATION)
self.create_resource(exp_resource_id, data)
callback.assert_called_with('identity', EXP_RESOURCE_TYPE,
CREATED_OPERATION,
{'resource_info': exp_resource_id})
@notifications.updated(EXP_RESOURCE_TYPE)
def update_resource(self, resource_id, data):
return data
def test_resource_updated_notification(self):
exp_resource_id, data = self.create_fake_ref()
callback = register_callback(UPDATED_OPERATION)
self.update_resource(exp_resource_id, data)
callback.assert_called_with('identity', EXP_RESOURCE_TYPE,
UPDATED_OPERATION,
{'resource_info': exp_resource_id})
@notifications.deleted(EXP_RESOURCE_TYPE)
def delete_resource(self, resource_id):
pass
def test_resource_deleted_notification(self):
exp_resource_id = uuid.uuid4().hex
callback = register_callback(DELETED_OPERATION)
self.delete_resource(exp_resource_id)
callback.assert_called_with('identity', EXP_RESOURCE_TYPE,
DELETED_OPERATION,
{'resource_info': exp_resource_id})
@notifications.created(EXP_RESOURCE_TYPE)
def create_exception(self, resource_id):
raise ArbitraryException()
def test_create_exception_without_notification(self):
callback = register_callback(CREATED_OPERATION)
self.assertRaises(
ArbitraryException, self.create_exception, uuid.uuid4().hex)
self.assertFalse(callback.called)
@notifications.created(EXP_RESOURCE_TYPE)
def update_exception(self, resource_id):
raise ArbitraryException()
def test_update_exception_without_notification(self):
callback = register_callback(UPDATED_OPERATION)
self.assertRaises(
ArbitraryException, self.update_exception, uuid.uuid4().hex)
self.assertFalse(callback.called)
@notifications.deleted(EXP_RESOURCE_TYPE)
def delete_exception(self, resource_id):
raise ArbitraryException()
def test_delete_exception_without_notification(self):
callback = register_callback(DELETED_OPERATION)
self.assertRaises(
ArbitraryException, self.delete_exception, uuid.uuid4().hex)
self.assertFalse(callback.called)
class NotificationsTestCase(testtools.TestCase):
def setUp(self):
super(NotificationsTestCase, self).setUp()
# these should use self.config_fixture.config(), but they haven't
# been registered yet
CONF.rpc_backend = 'fake'
CONF.notification_driver = ['fake']
def test_send_notification(self):
"""Test the private method _send_notification to ensure event_type,
payload, and context are built and passed properly.
"""
resource = uuid.uuid4().hex
resource_type = EXP_RESOURCE_TYPE
operation = CREATED_OPERATION
# NOTE(ldbragst): Even though notifications._send_notification doesn't
# contain logic that creates cases, this is supposed to test that
# context is always empty and that we ensure the resource ID of the
# resource in the notification is contained in the payload. It was
# agreed that context should be empty in Keystone's case, which is
# also noted in the /keystone/notifications.py module. This test
# ensures and maintains these conditions.
expected_args = [
{}, # empty context
'identity.%s.created' % resource_type, # event_type
{'resource_info': resource}, # payload
'INFO', # priority is always INFO...
]
with mock.patch.object(notifications._get_notifier(),
'_notify') as mocked:
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 = []
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)
self.useFixture(mockpatch.PatchObject(
notifications, '_send_notification', fake_notify))
def _assertNotifySeen(self, resource_id, operation, resource_type):
self.assertIn(operation, self.exp_operations)
self.assertIn(resource_id, self.exp_resource_ids)
self.assertIn(resource_type, self.exp_resource_types)
self.assertTrue(self.send_notification_called)
def _assertLastNotify(self, resource_id, operation, resource_type):
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)
group_ref = self.identity_api.create_group(group_ref)
self._assertLastNotify(group_ref['id'], CREATED_OPERATION, 'group')
def test_create_project(self):
project_ref = self.new_project_ref(domain_id=self.domain_id)
self.assignment_api.create_project(project_ref['id'], project_ref)
self._assertLastNotify(
project_ref['id'], CREATED_OPERATION, 'project')
def test_create_role(self):
role_ref = self.new_role_ref()
self.assignment_api.create_role(role_ref['id'], role_ref)
self._assertLastNotify(role_ref['id'], CREATED_OPERATION, 'role')
def test_create_user(self):
user_ref = self.new_user_ref(domain_id=self.domain_id)
user_ref = self.identity_api.create_user(user_ref)
self._assertLastNotify(user_ref['id'], CREATED_OPERATION, 'user')
def test_create_trust(self):
trustor = self.new_user_ref(domain_id=self.domain_id)
trustor = self.identity_api.create_user(trustor)
trustee = self.new_user_ref(domain_id=self.domain_id)
trustee = self.identity_api.create_user(trustee)
role_ref = self.new_role_ref()
self.assignment_api.create_role(role_ref['id'], role_ref)
trust_ref = self.new_trust_ref(trustor['id'],
trustee['id'])
self.trust_api.create_trust(trust_ref['id'],
trust_ref,
[role_ref])
self._assertLastNotify(
trust_ref['id'], CREATED_OPERATION, 'OS-TRUST:trust')
def test_delete_group(self):
group_ref = self.new_group_ref(domain_id=self.domain_id)
group_ref = self.identity_api.create_group(group_ref)
self.identity_api.delete_group(group_ref['id'])
self._assertLastNotify(group_ref['id'], DELETED_OPERATION, 'group')
def test_delete_project(self):
project_ref = self.new_project_ref(domain_id=self.domain_id)
self.assignment_api.create_project(project_ref['id'], project_ref)
self.assignment_api.delete_project(project_ref['id'])
self._assertLastNotify(
project_ref['id'], DELETED_OPERATION, 'project')
def test_delete_role(self):
role_ref = self.new_role_ref()
self.assignment_api.create_role(role_ref['id'], role_ref)
self.assignment_api.delete_role(role_ref['id'])
self._assertLastNotify(role_ref['id'], DELETED_OPERATION, 'role')
def test_delete_user(self):
user_ref = self.new_user_ref(domain_id=self.domain_id)
user_ref = self.identity_api.create_user(user_ref)
self.identity_api.delete_user(user_ref['id'])
self._assertLastNotify(user_ref['id'], DELETED_OPERATION, 'user')
def test_update_domain(self):
domain_ref = self.new_domain_ref()
self.assignment_api.create_domain(domain_ref['id'], domain_ref)
domain_ref['description'] = uuid.uuid4().hex
self.assignment_api.update_domain(domain_ref['id'], domain_ref)
self._assertLastNotify(domain_ref['id'], UPDATED_OPERATION, 'domain')
def test_delete_trust(self):
trustor = self.new_user_ref(domain_id=self.domain_id)
trustor = self.identity_api.create_user(trustor)
trustee = self.new_user_ref(domain_id=self.domain_id)
trustee = self.identity_api.create_user(trustee)
role_ref = self.new_role_ref()
trust_ref = self.new_trust_ref(trustor['id'], trustee['id'])
self.trust_api.create_trust(trust_ref['id'],
trust_ref,
[role_ref])
self.trust_api.delete_trust(trust_ref['id'])
self._assertLastNotify(
trust_ref['id'], DELETED_OPERATION, 'OS-TRUST:trust')
def test_delete_domain(self):
domain_ref = self.new_domain_ref()
self.assignment_api.create_domain(domain_ref['id'], domain_ref)
domain_ref['enabled'] = False
self.assignment_api.update_domain(domain_ref['id'], domain_ref)
self.assignment_api.delete_domain(domain_ref['id'])
self._assertLastNotify(domain_ref['id'], DELETED_OPERATION, 'domain')
def test_disable_domain(self):
domain_ref = self.new_domain_ref()
self.assignment_api.create_domain(domain_ref['id'], domain_ref)
domain_ref['enabled'] = False
self.assignment_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)
group_ref = self.identity_api.create_group(group_ref)
self.identity_api.update_group(group_ref['id'], group_ref)
self._assertLastNotify(group_ref['id'], UPDATED_OPERATION, 'group')
def test_update_project(self):
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_OPERATION, '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_OPERATION, 'project')
self._assertNotifyNotSent(project_ref['id'], 'disabled', 'project')
def test_update_role(self):
role_ref = self.new_role_ref()
self.assignment_api.create_role(role_ref['id'], role_ref)
self.assignment_api.update_role(role_ref['id'], role_ref)
self._assertLastNotify(role_ref['id'], UPDATED_OPERATION, 'role')
def test_update_user(self):
user_ref = self.new_user_ref(domain_id=self.domain_id)
user_ref = self.identity_api.create_user(user_ref)
self.identity_api.update_user(user_ref['id'], user_ref)
self._assertLastNotify(user_ref['id'], UPDATED_OPERATION, 'user')
class TestEventCallbacks(test_v3.RestfulTestCase):
def setUp(self):
super(TestEventCallbacks, self).setUp()
self.has_been_called = False
def _project_deleted_callback(self, service, resource_type, operation,
payload):
self.has_been_called = True
def _project_created_callback(self, service, resource_type, operation,
payload):
self.has_been_called = True
def test_notification_received(self):
callback = register_callback(CREATED_OPERATION, 'project')
project_ref = self.new_project_ref(domain_id=self.domain_id)
self.assignment_api.create_project(project_ref['id'], project_ref)
self.assertTrue(callback.called)
def test_notification_method_not_callable(self):
fake_method = None
self.assertRaises(TypeError,
notifications.register_event_callback,
UPDATED_OPERATION,
'project',
[fake_method])
def test_notification_event_not_valid(self):
self.assertRaises(ValueError,
notifications.register_event_callback,
uuid.uuid4().hex,
'project',
self._project_deleted_callback)
def test_event_registration_for_unknown_resource_type(self):
# Registration for unknown resource types should succeed. If no event
# is issued for that resource type, the callback wont be triggered.
notifications.register_event_callback(DELETED_OPERATION,
uuid.uuid4().hex,
self._project_deleted_callback)
resource_type = uuid.uuid4().hex
notifications.register_event_callback(DELETED_OPERATION,
resource_type,
self._project_deleted_callback)
def test_provider_event_callbacks_subscription(self):
callback_called = []
@dependency.provider('foo_api')
class Foo:
def __init__(self):
self.event_callbacks = {
CREATED_OPERATION: {'project': [self.foo_callback]}}
def foo_callback(self, service, resource_type, operation,
payload):
# uses callback_called from the closure
callback_called.append(True)
Foo()
project_ref = self.new_project_ref(domain_id=self.domain_id)
self.assignment_api.create_project(project_ref['id'], project_ref)
self.assertEqual([True], callback_called)
def test_invalid_event_callbacks(self):
@dependency.provider('foo_api')
class Foo:
def __init__(self):
self.event_callbacks = 'bogus'
self.assertRaises(ValueError, Foo)
def test_invalid_event_callbacks_event(self):
@dependency.provider('foo_api')
class Foo:
def __init__(self):
self.event_callbacks = {CREATED_OPERATION: 'bogus'}
self.assertRaises(ValueError, Foo)
class CadfNotificationsWrapperTestCase(test_v3.RestfulTestCase):
LOCAL_HOST = 'localhost'
ACTION = 'authenticate'
def setUp(self):
super(CadfNotificationsWrapperTestCase, self).setUp()
self._notifications = []
def fake_notify(action, initiator, outcome):
note = {
'action': action,
'initiator': initiator,
# NOTE(stevemar): outcome has 2 stages, pending and success
# so we are ignoring it for now.
# 'outcome': outcome,
'send_notification_called': True}
self._notifications.append(note)
self.useFixture(mockpatch.PatchObject(
notifications, '_send_audit_notification', fake_notify))
def _assertLastNotify(self, action, user_id):
self.assertTrue(self._notifications)
note = self._notifications[-1]
self.assertEqual(note['action'], action)
initiator = note['initiator']
self.assertEqual(initiator.name, user_id)
self.assertEqual(initiator.host.address, self.LOCAL_HOST)
self.assertTrue(note['send_notification_called'])
def test_v3_authenticate_user_name_and_domain_id(self):
user_id = self.user_id
user_name = self.user['name']
password = self.user['password']
domain_id = self.domain_id
data = self.build_authentication_request(username=user_name,
user_domain_id=domain_id,
password=password)
self.post('/auth/tokens', body=data)
self._assertLastNotify(self.ACTION, user_id)
def test_v3_authenticate_user_id(self):
user_id = self.user_id
password = self.user['password']
data = self.build_authentication_request(user_id=user_id,
password=password)
self.post('/auth/tokens', body=data)
self._assertLastNotify(self.ACTION, user_id)
def test_v3_authenticate_user_name_and_domain_name(self):
user_id = self.user_id
user_name = self.user['name']
password = self.user['password']
domain_name = self.domain['name']
data = self.build_authentication_request(username=user_name,
user_domain_name=domain_name,
password=password)
self.post('/auth/tokens', body=data)
self._assertLastNotify(self.ACTION, user_id)
class TestCallbackRegistration(testtools.TestCase):
def setUp(self):
super(TestCallbackRegistration, self).setUp()
self.mock_log = mock.Mock()
# Force the callback logging to occur
self.mock_log.logger.getEffectiveLevel.return_value = 1
def verify_log_message(self, data):
"""Tests that use this are a little brittle because adding more
logging can break them.
TODO(dstanek): remove the need for this in a future refactoring
"""
self.assertEqual(len(data), self.mock_log.info.call_count)
for i, data in enumerate(data):
self.mock_log.info.assert_any_call(mock.ANY, data)
def test_a_function_callback(self):
def callback(*args, **kwargs):
pass
resource_type = 'thing'
with mock.patch('keystone.notifications.LOG', self.mock_log):
notifications.register_event_callback(
CREATED_OPERATION, resource_type, callback)
expected_log_data = {
'callback': 'keystone.tests.test_notifications.callback',
'event': 'identity.%s.created' % resource_type
}
self.verify_log_message([expected_log_data])
def test_a_method_callback(self):
class C(object):
def callback(self, *args, **kwargs):
pass
with mock.patch('keystone.notifications.LOG', self.mock_log):
notifications.register_event_callback(
CREATED_OPERATION, 'thing', C.callback)
expected_log_data = {
'callback': 'keystone.tests.test_notifications.C.callback',
'event': 'identity.thing.created'
}
self.verify_log_message([expected_log_data])
def test_a_list_of_callbacks(self):
def callback(*args, **kwargs):
pass
class C(object):
def callback(self, *args, **kwargs):
pass
with mock.patch('keystone.notifications.LOG', self.mock_log):
notifications.register_event_callback(
CREATED_OPERATION, 'thing', [callback, C.callback])
expected_log_data = [
{
'callback': 'keystone.tests.test_notifications.callback',
'event': 'identity.thing.created'
},
{
'callback': 'keystone.tests.test_notifications.C.callback',
'event': 'identity.thing.created'
},
]
self.verify_log_message(expected_log_data)
def test_an_invalid_callback(self):
self.assertRaises(TypeError,
notifications.register_event_callback,
(CREATED_OPERATION, 'thing', object()))
def test_an_invalid_event(self):
def callback(*args, **kwargs):
pass
self.assertRaises(ValueError,
notifications.register_event_callback,
uuid.uuid4().hex,
'thing',
callback)