From bd401d4cc8993e5f76ff9b87cb478c4fcaad1f4b Mon Sep 17 00:00:00 2001 From: Balazs Gibizer Date: Thu, 19 Nov 2015 15:54:48 +0100 Subject: [PATCH] Make emitting versioned notifications configurable A new config option is introduced called 'notification_format' which specifies the format of the notifications emitted, possible values are 'versioned', 'unversioned', 'both'. The default value is 'both'. Also to help consumers the versioned notifications will be emited to a separate topic called 'versioned_notifications'. DocImpact: new config option notification_format Partially-Implements: bp versioned-notification-api Change-Id: Ie45c03175800bb14269f3976b03a53488e084339 --- nova/objects/notification.py | 3 +- nova/rpc.py | 40 +++++++++++++-- nova/tests/unit/fake_notifier.py | 28 ++++++++-- nova/tests/unit/objects/test_notification.py | 8 ++- nova/tests/unit/test_notifier.py | 51 +++++++++++++++++++ ...sioned-notifications-423f4d8d2a3992c6.yaml | 7 +++ 6 files changed, 123 insertions(+), 14 deletions(-) create mode 100644 nova/tests/unit/test_notifier.py create mode 100644 releasenotes/notes/versioned-notifications-423f4d8d2a3992c6.yaml diff --git a/nova/objects/notification.py b/nova/objects/notification.py index d41219ee4aba..79b90c62c227 100644 --- a/nova/objects/notification.py +++ b/nova/objects/notification.py @@ -114,8 +114,7 @@ class NotificationBase(base.NovaObject): } def _emit(self, context, event_type, publisher_id, payload): - assert rpc.NOTIFIER is not None - notifier = rpc.NOTIFIER.prepare(publisher_id=publisher_id) + notifier = rpc.get_versioned_notifier(publisher_id) notify = getattr(notifier, self.priority) notify(context, event_type=event_type, payload=payload) diff --git a/nova/rpc.py b/nova/rpc.py index 14a6962bb524..b50077efdde4 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -33,8 +33,20 @@ from oslo_serialization import jsonutils import nova.context import nova.exception + CONF = cfg.CONF +notification_opts = [ + cfg.StrOpt('notification_format', + choices=['unversioned', 'versioned', 'both'], + default='both', + help='Specifies which notification format shall be used by ' + 'nova.'), +] + +CONF.register_opts(notification_opts) + TRANSPORT = None +LEGACY_NOTIFIER = None NOTIFIER = None ALLOWED_EXMODS = [ @@ -56,21 +68,34 @@ TRANSPORT_ALIASES = { def init(conf): - global TRANSPORT, NOTIFIER + global TRANSPORT, LEGACY_NOTIFIER, NOTIFIER exmods = get_allowed_exmods() TRANSPORT = messaging.get_transport(conf, allowed_remote_exmods=exmods, aliases=TRANSPORT_ALIASES) serializer = RequestContextSerializer(JsonPayloadSerializer()) - NOTIFIER = messaging.Notifier(TRANSPORT, serializer=serializer) + if conf.notification_format == 'unversioned': + LEGACY_NOTIFIER = messaging.Notifier(TRANSPORT, serializer=serializer) + NOTIFIER = messaging.Notifier(TRANSPORT, serializer=serializer, + driver='noop') + elif conf.notification_format == 'both': + LEGACY_NOTIFIER = messaging.Notifier(TRANSPORT, serializer=serializer) + NOTIFIER = messaging.Notifier(TRANSPORT, serializer=serializer, + topic='versioned_notifications') + else: + LEGACY_NOTIFIER = messaging.Notifier(TRANSPORT, serializer=serializer, + driver='noop') + NOTIFIER = messaging.Notifier(TRANSPORT, serializer=serializer, + topic='versioned_notifications') def cleanup(): - global TRANSPORT, NOTIFIER + global TRANSPORT, LEGACY_NOTIFIER, NOTIFIER assert TRANSPORT is not None + assert LEGACY_NOTIFIER is not None assert NOTIFIER is not None TRANSPORT.cleanup() - TRANSPORT = NOTIFIER = None + TRANSPORT = LEGACY_NOTIFIER = NOTIFIER = None def set_defaults(control_exchange): @@ -141,7 +166,12 @@ def get_server(target, endpoints, serializer=None): def get_notifier(service, host=None, publisher_id=None): - assert NOTIFIER is not None + assert LEGACY_NOTIFIER is not None if not publisher_id: publisher_id = "%s.%s" % (service, host or CONF.host) + return LEGACY_NOTIFIER.prepare(publisher_id=publisher_id) + + +def get_versioned_notifier(publisher_id): + assert NOTIFIER is not None return NOTIFIER.prepare(publisher_id=publisher_id) diff --git a/nova/tests/unit/fake_notifier.py b/nova/tests/unit/fake_notifier.py index 4c599adec14e..59432872bc46 100644 --- a/nova/tests/unit/fake_notifier.py +++ b/nova/tests/unit/fake_notifier.py @@ -21,10 +21,12 @@ from oslo_serialization import jsonutils from nova import rpc NOTIFICATIONS = [] +VERSIONED_NOTIFICATIONS = [] def reset(): del NOTIFICATIONS[:] + del VERSIONED_NOTIFICATIONS[:] FakeMessage = collections.namedtuple('Message', @@ -64,11 +66,27 @@ class FakeNotifier(object): NOTIFICATIONS.append(msg) +class FakeVersionedNotifier(FakeNotifier): + def _notify(self, priority, ctxt, event_type, payload): + payload = self._serializer.serialize_entity(ctxt, payload) + VERSIONED_NOTIFICATIONS.append({'publisher_id': self.publisher_id, + 'priority': priority, + 'event_type': event_type, + 'payload': payload}) + + def stub_notifier(stubs): stubs.Set(messaging, 'Notifier', FakeNotifier) - if rpc.NOTIFIER: - stubs.Set(rpc, 'NOTIFIER', - FakeNotifier(rpc.NOTIFIER.transport, - rpc.NOTIFIER.publisher_id, - serializer=getattr(rpc.NOTIFIER, '_serializer', + if rpc.LEGACY_NOTIFIER and rpc.NOTIFIER: + stubs.Set(rpc, 'LEGACY_NOTIFIER', + FakeNotifier(rpc.LEGACY_NOTIFIER.transport, + rpc.LEGACY_NOTIFIER.publisher_id, + serializer=getattr(rpc.LEGACY_NOTIFIER, + '_serializer', None))) + stubs.Set(rpc, 'NOTIFIER', + FakeVersionedNotifier(rpc.NOTIFIER.transport, + rpc.NOTIFIER.publisher_id, + serializer=getattr(rpc.NOTIFIER, + '_serializer', + None))) diff --git a/nova/tests/unit/objects/test_notification.py b/nova/tests/unit/objects/test_notification.py index a885708f3736..ada34b80ddbc 100644 --- a/nova/tests/unit/objects/test_notification.py +++ b/nova/tests/unit/objects/test_notification.py @@ -140,8 +140,9 @@ class TestNotificationBase(test.NoDBTestCase): actual_payload = mock_notify.call_args[1]['payload'] self.assertJsonEqual(expected_payload, actual_payload) + @mock.patch('nova.rpc.LEGACY_NOTIFIER') @mock.patch('nova.rpc.NOTIFIER') - def test_emit_notification(self, mock_notifier): + def test_emit_notification(self, mock_notifier, mock_legacy): mock_context = mock.Mock() mock_context.to_dict.return_value = {} @@ -152,6 +153,7 @@ class TestNotificationBase(test.NoDBTestCase): mock_context, expected_event_type='test_object.update.start', expected_payload=self.expected_payload) + self.assertFalse(mock_legacy.called) @mock.patch('nova.rpc.NOTIFIER') def test_emit_with_host_and_binary_as_publisher(self, mock_notifier): @@ -174,8 +176,9 @@ class TestNotificationBase(test.NoDBTestCase): expected_event_type='test_object.update', expected_payload=self.expected_payload) + @mock.patch('nova.rpc.LEGACY_NOTIFIER') @mock.patch('nova.rpc.NOTIFIER') - def test_emit_event_type_without_phase(self, mock_notifier): + def test_emit_event_type_without_phase(self, mock_notifier, mock_legacy): noti = self.TestNotification( event_type=notification.EventType( object='test_object', @@ -194,6 +197,7 @@ class TestNotificationBase(test.NoDBTestCase): mock_context, expected_event_type='test_object.update', expected_payload=self.expected_payload) + self.assertFalse(mock_legacy.called) @mock.patch('nova.rpc.NOTIFIER') def test_not_possible_to_emit_if_not_populated(self, mock_notifier): diff --git a/nova/tests/unit/test_notifier.py b/nova/tests/unit/test_notifier.py new file mode 100644 index 000000000000..d8474207f377 --- /dev/null +++ b/nova/tests/unit/test_notifier.py @@ -0,0 +1,51 @@ +# Copyright 2015 OpenStack Foundation +# All Rights Reserved. +# +# 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 mock + +from nova import rpc +from nova import test + + +class TestNotifier(test.NoDBTestCase): + + @mock.patch('oslo_messaging.get_transport') + @mock.patch('oslo_messaging.Notifier') + def test_notification_format_affects_notification_driver(self, + mock_notifier, + mock_transport): + conf = mock.Mock() + + cases = { + 'unversioned': [ + mock.call(mock.ANY, serializer=mock.ANY), + mock.call(mock.ANY, serializer=mock.ANY, driver='noop')], + 'both': [ + mock.call(mock.ANY, serializer=mock.ANY), + mock.call(mock.ANY, serializer=mock.ANY, + topic='versioned_notifications')], + 'versioned': [ + mock.call(mock.ANY, serializer=mock.ANY, driver='noop'), + mock.call(mock.ANY, serializer=mock.ANY, + topic='versioned_notifications')]} + + for config in cases: + mock_notifier.reset_mock() + mock_notifier.side_effect = ['first', 'second'] + conf.notification_format = config + rpc.init(conf) + self.assertEqual(cases[config], mock_notifier.call_args_list) + self.assertEqual('first', rpc.LEGACY_NOTIFIER) + self.assertEqual('second', rpc.NOTIFIER) diff --git a/releasenotes/notes/versioned-notifications-423f4d8d2a3992c6.yaml b/releasenotes/notes/versioned-notifications-423f4d8d2a3992c6.yaml new file mode 100644 index 000000000000..d2904945982b --- /dev/null +++ b/releasenotes/notes/versioned-notifications-423f4d8d2a3992c6.yaml @@ -0,0 +1,7 @@ +--- +features: + - As part of refactoring the notification interface of Nova + a new config option 'notification_format' has been added to specifies + which notification format shall be used by nova. The possible values + are 'unversioned' (e.g. legacy), 'versioned', 'both'. The default + value is 'both'.