diff --git a/watcher/common/exception.py b/watcher/common/exception.py index 01ee74ba9..db7958361 100644 --- a/watcher/common/exception.py +++ b/watcher/common/exception.py @@ -399,3 +399,8 @@ class NotSoftDeletedStateError(WatcherException): class NegativeLimitError(WatcherException): msg_fmt = _("Limit should be positive") + + +class NotificationPayloadError(WatcherException): + _msg_fmt = _("Payload not populated when trying to send notification " + "\"%(class_name)s\"") diff --git a/watcher/common/rpc.py b/watcher/common/rpc.py index 542e20cac..c48cc974a 100644 --- a/watcher/common/rpc.py +++ b/watcher/common/rpc.py @@ -72,8 +72,12 @@ def init(conf): aliases=TRANSPORT_ALIASES) serializer = RequestContextSerializer(JsonPayloadSerializer()) - NOTIFIER = messaging.Notifier(NOTIFICATION_TRANSPORT, - serializer=serializer) + if not conf.notification_level: + NOTIFIER = messaging.Notifier( + NOTIFICATION_TRANSPORT, serializer=serializer, driver='noop') + else: + NOTIFIER = messaging.Notifier(NOTIFICATION_TRANSPORT, + serializer=serializer) def initialized(): diff --git a/watcher/common/service.py b/watcher/common/service.py index 8b4fd0847..8d5445387 100644 --- a/watcher/common/service.py +++ b/watcher/common/service.py @@ -36,6 +36,7 @@ from watcher.common import rpc from watcher.common import scheduling from watcher import objects from watcher.objects import base +from watcher.objects import fields as wfields from watcher import opts from watcher import version @@ -64,6 +65,18 @@ service_opts = [ cfg.CONF.register_opts(service_opts) +NOTIFICATION_OPTS = [ + cfg.StrOpt('notification_level', + choices=[''] + list(wfields.NotificationPriority.ALL), + default=wfields.NotificationPriority.INFO, + help=_('Specifies the minimum level for which to send ' + 'notifications. If not set, no notifications will ' + 'be sent. The default is for this option to be at the ' + '`INFO` level.')) +] +cfg.CONF.register_opts(NOTIFICATION_OPTS) + + CONF = cfg.CONF LOG = log.getLogger(__name__) diff --git a/watcher/objects/fields.py b/watcher/objects/fields.py index 9f0f9ae00..3ff2e538a 100644 --- a/watcher/objects/fields.py +++ b/watcher/objects/fields.py @@ -103,14 +103,13 @@ class BaseWatcherEnum(Enum): class NotificationPriority(BaseWatcherEnum): - CRITICAL = 'critical' DEBUG = 'debug' INFO = 'info' + WARNING = 'warning' ERROR = 'error' - SAMPLE = 'sample' - WARNING = 'warn' + CRITICAL = 'critical' - ALL = (CRITICAL, DEBUG, INFO, ERROR, SAMPLE, WARNING) + ALL = (DEBUG, INFO, WARNING, ERROR, CRITICAL) class NotificationPhase(BaseWatcherEnum): diff --git a/watcher/objects/notifications/base.py b/watcher/objects/notifications/base.py index b8b81bc94..d2ba3cee4 100644 --- a/watcher/objects/notifications/base.py +++ b/watcher/objects/notifications/base.py @@ -12,10 +12,24 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo_config import cfg + +from watcher.common import exception from watcher.common import rpc from watcher.objects import base from watcher.objects import fields as wfields +CONF = cfg.CONF + +# Definition of notification levels in increasing order of severity +NOTIFY_LEVELS = { + wfields.NotificationPriority.DEBUG: 0, + wfields.NotificationPriority.INFO: 1, + wfields.NotificationPriority.WARNING: 2, + wfields.NotificationPriority.ERROR: 3, + wfields.NotificationPriority.CRITICAL: 4 +} + @base.WatcherObjectRegistry.register_if(False) class NotificationObject(base.WatcherObject): @@ -125,6 +139,20 @@ class NotificationBase(NotificationObject): 'publisher': wfields.ObjectField('NotificationPublisher'), } + def _should_notify(self): + """Determine whether the notification should be sent. + + A notification is sent when the level of the notification is + greater than or equal to the level specified in the + configuration, in the increasing order of DEBUG, INFO, WARNING, + ERROR, CRITICAL. + :return: True if notification should be sent, False otherwise. + """ + if not CONF.notification_level: + return False + return (NOTIFY_LEVELS[self.priority] >= + NOTIFY_LEVELS[CONF.notification_level]) + def _emit(self, context, event_type, publisher_id, payload): notifier = rpc.get_notifier(publisher_id) notify = getattr(notifier, self.priority) @@ -132,8 +160,11 @@ class NotificationBase(NotificationObject): def emit(self, context): """Send the notification.""" - assert self.payload.populated - + if not self._should_notify(): + return + if not self.payload.populated: + raise exception.NotificationPayloadError( + class_name=self.__class__.__name__) # Note(gibi): notification payload will be a newly populated object # therefore every field of it will look changed so this does not carry # any extra information so we drop this from the payload. diff --git a/watcher/tests/objects/notifications/test_notification.py b/watcher/tests/objects/notifications/test_notification.py index 1d16a46d3..dab514dba 100644 --- a/watcher/tests/objects/notifications/test_notification.py +++ b/watcher/tests/objects/notifications/test_notification.py @@ -17,6 +17,7 @@ import collections import mock from oslo_versionedobjects import fixture +from watcher.common import exception from watcher.common import rpc from watcher.objects import base from watcher.objects import fields as wfields @@ -136,6 +137,46 @@ class TestNotificationBase(testbase.TestCase): expected_event_type='test_object.update.start', expected_payload=self.expected_payload) + @mock.patch.object(rpc, 'NOTIFIER') + def test_no_emit_notifs_disabled(self, mock_notifier): + # Make sure notifications aren't emitted when notification_level + # isn't defined, indicating notifications should be disabled + self.config(notification_level=None) + notif = self.TestNotification( + event_type=notificationbase.EventType( + object='test_object', + action=wfields.NotificationAction.UPDATE, + phase=wfields.NotificationPhase.START), + publisher=notificationbase.NotificationPublisher( + host='fake-host', binary='watcher-fake'), + priority=wfields.NotificationPriority.INFO, + payload=self.payload) + + mock_context = mock.Mock() + notif.emit(mock_context) + + self.assertFalse(mock_notifier.called) + + @mock.patch.object(rpc, 'NOTIFIER') + def test_no_emit_level_too_low(self, mock_notifier): + # Make sure notification doesn't emit when set notification + # level < config level + self.config(notification_level='warning') + notif = self.TestNotification( + event_type=notificationbase.EventType( + object='test_object', + action=wfields.NotificationAction.UPDATE, + phase=wfields.NotificationPhase.START), + publisher=notificationbase.NotificationPublisher( + host='fake-host', binary='watcher-fake'), + priority=wfields.NotificationPriority.INFO, + payload=self.payload) + + mock_context = mock.Mock() + notif.emit(mock_context) + + self.assertFalse(mock_notifier.called) + @mock.patch.object(rpc, 'NOTIFIER') def test_emit_event_type_without_phase(self, mock_notifier): noti = self.TestNotification( @@ -171,7 +212,8 @@ class TestNotificationBase(testbase.TestCase): payload=non_populated_payload) mock_context = mock.Mock() - self.assertRaises(AssertionError, noti.emit, mock_context) + self.assertRaises(exception.NotificationPayloadError, + noti.emit, mock_context) self.assertFalse(mock_notifier.called) @mock.patch.object(rpc, 'NOTIFIER')