336 lines
15 KiB
Python
336 lines
15 KiB
Python
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
|
|
#
|
|
# 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.
|
|
from oslo_utils import uuidutils
|
|
|
|
import mock
|
|
import oslo_messaging
|
|
from oslo_service import service
|
|
|
|
from barbican.common import config
|
|
from barbican import queue
|
|
from barbican.queue import keystone_listener
|
|
from barbican.tasks import keystone_consumer as consumer
|
|
from barbican.tests import utils
|
|
|
|
|
|
class UtilMixin(object):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(UtilMixin, self).__init__(*args, **kwargs)
|
|
self.conf = config.CONF
|
|
# dict which has item as {property: (value, group_name)}
|
|
self.overrides = {}
|
|
|
|
def revert_overrides(self):
|
|
'''Reverts configuration override values after test end.'''
|
|
for k, v in self.overrides.items():
|
|
value, group = v
|
|
self.conf.set_override(k, value, group)
|
|
|
|
def setUp(self):
|
|
super(UtilMixin, self).setUp()
|
|
self.addCleanup(self.revert_overrides)
|
|
|
|
def opt_in_group(self, group, **kw):
|
|
for k, v in kw.items():
|
|
# add to local overrides if its not already set
|
|
# we want to keep the original value from first override
|
|
dict_value = self.overrides.get(k)
|
|
if not dict_value:
|
|
if group:
|
|
orig_value = getattr(getattr(self.conf, group), k)
|
|
else:
|
|
orig_value = getattr(self.conf, k)
|
|
self.overrides[k] = orig_value, group
|
|
self.conf.set_override(k, v, group)
|
|
|
|
|
|
class WhenUsingNotificationTask(UtilMixin, utils.BaseTestCase):
|
|
"""Test for 'Notification' task functionality."""
|
|
|
|
def setUp(self):
|
|
super(WhenUsingNotificationTask, self).setUp()
|
|
|
|
self.task = keystone_listener.NotificationTask(self.conf)
|
|
self.payload = {'resource_info': uuidutils.generate_uuid(
|
|
dashed=False)}
|
|
|
|
self.type_index = 2
|
|
self.payload_index = 3
|
|
self.task_args = ['my_context', 'publisher_id', 'event_type',
|
|
self.payload, {'metadata': 'value'}]
|
|
|
|
@mock.patch.object(keystone_listener.NotificationTask, 'process_event')
|
|
def test_info_level_notification(self, mock_process):
|
|
self.task.info(*self.task_args)
|
|
mock_process.assert_called_once_with(*self.task_args)
|
|
|
|
@mock.patch.object(consumer.KeystoneEventConsumer, 'process',
|
|
return_value=None)
|
|
def test_create_project_event_notification(self, mock_process):
|
|
|
|
self.task_args[self.type_index] = 'identity.project.created'
|
|
result = self.task.info(*self.task_args)
|
|
self.assertFalse(mock_process.called, 'Should not call event consumer '
|
|
'for project create event')
|
|
self.assertIsNone(result)
|
|
|
|
@mock.patch.object(consumer.KeystoneEventConsumer, 'process',
|
|
return_value=None)
|
|
def test_update_project_event_notification(self, mock_process):
|
|
|
|
self.task_args[self.type_index] = 'identity.project.updated'
|
|
result = self.task.info(*self.task_args)
|
|
self.assertFalse(mock_process.called, 'Should not call event consumer '
|
|
'for project update event')
|
|
self.assertIsNone(result)
|
|
|
|
@mock.patch.object(consumer.KeystoneEventConsumer, 'process',
|
|
return_value=None)
|
|
def test_delete_project_event_notification_with_required_data(
|
|
self, mock_process):
|
|
|
|
project_id = uuidutils.generate_uuid(dashed=False)
|
|
self.task_args[self.type_index] = 'identity.project.deleted'
|
|
self.task_args[self.payload_index] = {'resource_info': project_id}
|
|
result = self.task.info(*self.task_args)
|
|
mock_process.assert_called_once_with(project_id=project_id,
|
|
operation_type='deleted',
|
|
resource_type='project')
|
|
self.assertEqual(oslo_messaging.NotificationResult.HANDLED, result)
|
|
|
|
@mock.patch.object(consumer.KeystoneEventConsumer, 'process',
|
|
return_value=None)
|
|
def test_delete_project_event_with_different_service_name_in_event_type(
|
|
self, mock_process):
|
|
|
|
project_id = uuidutils.generate_uuid(dashed=False)
|
|
self.task_args[self.type_index] = 'aaa.project.deleted'
|
|
self.task_args[self.payload_index] = {'resource_info': project_id}
|
|
result = self.task.info(*self.task_args)
|
|
|
|
mock_process.assert_called_once_with(project_id=project_id,
|
|
operation_type='deleted',
|
|
resource_type='project')
|
|
self.assertEqual(oslo_messaging.NotificationResult.HANDLED, result)
|
|
|
|
@mock.patch.object(consumer.KeystoneEventConsumer, 'process',
|
|
return_value=None)
|
|
def test_delete_project_event_with_event_type_in_different_case(
|
|
self, mock_process):
|
|
|
|
project_id = uuidutils.generate_uuid(dashed=False)
|
|
self.task_args[self.type_index] = 'Identity.PROJECT.DeleteD'
|
|
self.task_args[self.payload_index] = {'resource_info': project_id}
|
|
result = self.task.info(*self.task_args)
|
|
|
|
mock_process.assert_called_once_with(project_id=project_id,
|
|
operation_type='deleted',
|
|
resource_type='project')
|
|
self.assertEqual(oslo_messaging.NotificationResult.HANDLED, result)
|
|
|
|
@mock.patch.object(consumer.KeystoneEventConsumer, 'process',
|
|
return_value=None)
|
|
def test_delete_project_event_with_incomplete_event_type_format(
|
|
self, mock_process):
|
|
|
|
project_id = uuidutils.generate_uuid(dashed=False)
|
|
self.task_args[self.type_index] = 'project.deleted'
|
|
self.task_args[self.payload_index] = {'resource_info': project_id}
|
|
result = self.task.info(*self.task_args)
|
|
|
|
self.assertFalse(mock_process.called, 'Should not call event consumer '
|
|
'for project delete event as service name is missing '
|
|
'in event_type data. Expected format is '
|
|
' <service_name>.<resource_name>.<operation_type>')
|
|
self.assertIsNone(result)
|
|
|
|
@mock.patch.object(consumer.KeystoneEventConsumer, 'process',
|
|
return_value=None)
|
|
def test_delete_project_event_notification_with_missing_resource_info(
|
|
self, mock_process):
|
|
|
|
self.task_args[self.type_index] = 'identity.project.deleted'
|
|
self.task_args[self.payload_index] = {'resource_info': None}
|
|
result = self.task.info(*self.task_args)
|
|
|
|
self.assertFalse(mock_process.called, 'Should not call event consumer '
|
|
'for project delete event when project_id is missing '
|
|
'in payload')
|
|
self.assertIsNone(result)
|
|
|
|
@mock.patch.object(consumer.KeystoneEventConsumer, 'process',
|
|
return_value=None)
|
|
def test_delete_project_event_notification_with_missing_payload(
|
|
self, mock_process):
|
|
|
|
self.task_args[self.type_index] = 'identity.project.deleted'
|
|
self.task_args[self.payload_index] = None
|
|
result = self.task.info(*self.task_args)
|
|
|
|
self.assertFalse(mock_process.called, 'Should not call event consumer '
|
|
'for project delete event when payload is missing')
|
|
self.assertIsNone(result)
|
|
|
|
@mock.patch.object(consumer.KeystoneEventConsumer, 'process',
|
|
return_value=None)
|
|
def test_delete_project_event_notification_with_blank_payload(
|
|
self, mock_process):
|
|
|
|
self.task_args[self.type_index] = 'identity.project.deleted'
|
|
self.task_args[self.payload_index] = ''
|
|
result = self.task.info(*self.task_args)
|
|
|
|
self.assertFalse(mock_process.called, 'Should not call event consumer '
|
|
'for project delete event when payload is missing')
|
|
self.assertIsNone(result)
|
|
|
|
@mock.patch.object(consumer.KeystoneEventConsumer, 'process',
|
|
return_value=None)
|
|
def test_event_notification_with_missing_event_type(self, mock_process):
|
|
|
|
project_id = uuidutils.generate_uuid(dashed=False)
|
|
self.task_args[self.type_index] = None
|
|
self.task_args[self.payload_index] = {'resource_info': project_id}
|
|
result = self.task.info(*self.task_args)
|
|
|
|
self.assertFalse(mock_process.called, 'Should not call event consumer '
|
|
'for keystone event when event_type is missing in '
|
|
'notification')
|
|
self.assertIsNone(result)
|
|
|
|
@mock.patch.object(consumer.KeystoneEventConsumer, 'process',
|
|
return_value=None)
|
|
def test_event_notification_with_blank_event_type(self, mock_process):
|
|
|
|
project_id = uuidutils.generate_uuid(dashed=False)
|
|
self.task_args[self.type_index] = ''
|
|
self.task_args[self.payload_index] = {'resource_info': project_id}
|
|
result = self.task.info(*self.task_args)
|
|
|
|
self.assertFalse(mock_process.called, 'Should not call event consumer '
|
|
'keystone event when event_type is blank in '
|
|
'notification')
|
|
self.assertIsNone(result)
|
|
|
|
@mock.patch.object(consumer.KeystoneEventConsumer, 'process')
|
|
def test_event_notification_with_processing_error_requeue_disabled(
|
|
self, mock_process):
|
|
|
|
self.opt_in_group(queue.KS_NOTIFICATIONS_GRP_NAME, allow_requeue=False)
|
|
local_task = keystone_listener.NotificationTask(self.conf)
|
|
mock_process.side_effect = Exception('Dummy Error')
|
|
|
|
project_id = uuidutils.generate_uuid(dashed=False)
|
|
self.task_args[self.type_index] = 'identity.project.deleted'
|
|
self.task_args[self.payload_index] = {'resource_info': project_id}
|
|
result = local_task.info(*self.task_args)
|
|
|
|
self.assertTrue(mock_process.called, 'Should call event consumer for'
|
|
' project delete event')
|
|
self.assertEqual(oslo_messaging.NotificationResult.HANDLED, result)
|
|
|
|
@mock.patch.object(consumer.KeystoneEventConsumer, 'process')
|
|
def test_event_notification_with_processing_error_requeue_enabled(
|
|
self, mock_process):
|
|
|
|
self.opt_in_group(queue.KS_NOTIFICATIONS_GRP_NAME, allow_requeue=True)
|
|
local_task = keystone_listener.NotificationTask(self.conf)
|
|
mock_process.side_effect = Exception('Dummy Error')
|
|
|
|
project_id = uuidutils.generate_uuid(dashed=False)
|
|
self.task_args[self.type_index] = 'identity.project.deleted'
|
|
self.task_args[self.payload_index] = {'resource_info': project_id}
|
|
result = local_task.info(*self.task_args)
|
|
|
|
self.assertTrue(mock_process.called, 'Should call event consumer for'
|
|
' project delete event')
|
|
self.assertEqual(oslo_messaging.NotificationResult.REQUEUE, result)
|
|
|
|
|
|
class WhenUsingMessageServer(UtilMixin, utils.BaseTestCase):
|
|
"""Test using the asynchronous task client."""
|
|
|
|
def setUp(self):
|
|
super(WhenUsingMessageServer, self).setUp()
|
|
queue.init(self.conf)
|
|
|
|
patcher = mock.patch('oslo_messaging.notify.listener.'
|
|
'NotificationServer')
|
|
mock_server_class = patcher.start()
|
|
self.addCleanup(patcher.stop)
|
|
|
|
self.msg_server_mock = mock_server_class()
|
|
self.msg_server_mock.start.return_value = None
|
|
self.msg_server_mock.stop.return_value = None
|
|
self.msg_server_mock.wait.return_value = None
|
|
|
|
@mock.patch.object(queue, 'get_notification_server')
|
|
@mock.patch.object(queue, 'get_notification_target')
|
|
def test_target_and_notification_server_invocations(self, mock_target,
|
|
mock_server):
|
|
target = 'a target value here'
|
|
mock_target.return_value = target
|
|
msg_server = keystone_listener.MessageServer(self.conf)
|
|
|
|
mock_target.assert_called_once_with()
|
|
mock_server.assert_called_once_with(
|
|
targets=[target], endpoints=[msg_server])
|
|
|
|
def test_keystone_notification_config_used(self):
|
|
topic = 'my test topic'
|
|
exchange = 'my test exchange'
|
|
version = ' my test version'
|
|
self.opt_in_group(queue.KS_NOTIFICATIONS_GRP_NAME, topic=topic)
|
|
self.opt_in_group(queue.KS_NOTIFICATIONS_GRP_NAME,
|
|
control_exchange=exchange)
|
|
self.opt_in_group(queue.KS_NOTIFICATIONS_GRP_NAME, version=version)
|
|
self.opt_in_group(queue.KS_NOTIFICATIONS_GRP_NAME, version=version)
|
|
target = queue.get_notification_target()
|
|
self.assertEqual(topic, target.topic)
|
|
self.assertEqual(exchange, target.exchange)
|
|
self.assertEqual(version, target.version)
|
|
|
|
@mock.patch.object(service.Service, '__init__')
|
|
def test_keystone_notification_pool_size_used(self, mock_service_init):
|
|
thread_pool_size = 5
|
|
self.opt_in_group(queue.KS_NOTIFICATIONS_GRP_NAME,
|
|
thread_pool_size=thread_pool_size)
|
|
msg_server = keystone_listener.MessageServer(self.conf)
|
|
mock_service_init.assert_called_once_with(msg_server,
|
|
threads=thread_pool_size)
|
|
|
|
@mock.patch.object(service.Service, 'start')
|
|
def test_should_start(self, mock_service):
|
|
msg_server = keystone_listener.MessageServer(self.conf)
|
|
msg_server.start()
|
|
self.msg_server_mock.start.assert_called_with()
|
|
|
|
@mock.patch.object(service.Service, 'stop', autospec=True)
|
|
def test_should_stop(self, mock_service_stop):
|
|
msg_server = keystone_listener.MessageServer(self.conf)
|
|
msg_server.stop()
|
|
self.msg_server_mock.stop.assert_called_with()
|
|
|
|
@mock.patch.object(service.Service, 'wait')
|
|
def test_should_wait(self, mock_service_wait):
|
|
msg_server = keystone_listener.MessageServer(self.conf)
|
|
msg_server.wait()
|
|
self.assertFalse(self.msg_server_mock.stop.called, 'No need to call'
|
|
'message server wait() as Service itself creates the '
|
|
' wait event')
|
|
self.assertTrue(mock_service_wait.called, 'Expected to only call '
|
|
'service.Service.wait() method')
|