diff --git a/doc/ext/__init__.py b/doc/ext/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/doc/ext/versioned_notifications.py b/doc/ext/versioned_notifications.py new file mode 100644 index 00000000..938af21c --- /dev/null +++ b/doc/ext/versioned_notifications.py @@ -0,0 +1,150 @@ +# 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. + +""" +This provides a sphinx extension able to list the implemented versioned +notifications into the developer documentation. + +This code file is borrowed from the same implementation: +nova/doc/ext/versioned_notifications.py, with sincere thanks to the main +author: Balazs Gibizer + +It is used via a single directive in the .rst file + + .. versioned_notifications:: + +""" + +from docutils import nodes +from docutils.parsers import rst +import importlib +import pkgutil + +import mogan.notifications.objects +from mogan.notifications.objects import base as notification +from mogan.objects import base + + +class VersionedNotificationDirective(rst.Directive): + + SAMPLE_ROOT = 'doc/notification_samples/' + TOGGLE_SCRIPT = """ + + + + +""" + + def run(self): + notifications = self._collect_notifications() + return self._build_markup(notifications) + + def _import_all_notification_packages(self): + map(lambda module: importlib.import_module(module), + ('mogan.notifications.objects.' + name for _, name, _ in + pkgutil.iter_modules(mogan.notifications.objects.__path__))) + + def _collect_notifications(self): + self._import_all_notification_packages() + base.MoganObjectRegistry.register_notification_objects() + notifications = [] + ovos = base.MoganObjectRegistry.obj_classes() + for name, cls in ovos.items(): + cls = cls[0] + if (issubclass(cls, notification.NotificationBase) and + cls != notification.NotificationBase): + + payload_name = cls.fields['payload'].objname + payload_cls = ovos[payload_name][0] + for sample in cls.samples: + notifications.append((cls.__name__, + payload_cls.__name__, + sample)) + return sorted(notifications) + + def _build_markup(self, notifications): + content = [] + cols = ['Event type', 'Notification class', 'Payload class', 'Sample'] + table = nodes.table() + content.append(table) + group = nodes.tgroup(cols=len(cols)) + table.append(group) + + head = nodes.thead() + group.append(head) + + for _ in cols: + group.append(nodes.colspec(colwidth=1)) + + body = nodes.tbody() + group.append(body) + + # fill the table header + row = nodes.row() + body.append(row) + for col_name in cols: + col = nodes.entry() + row.append(col) + text = nodes.strong(text=col_name) + col.append(text) + + # fill the table content, one notification per row + for name, payload, sample_file in notifications: + event_type = sample_file[0: -5].replace('-', '.') + + row = nodes.row() + body.append(row) + col = nodes.entry() + row.append(col) + text = nodes.literal(text=event_type) + col.append(text) + + col = nodes.entry() + row.append(col) + text = nodes.literal(text=name) + col.append(text) + + col = nodes.entry() + row.append(col) + text = nodes.literal(text=payload) + col.append(text) + + col = nodes.entry() + row.append(col) + + with open(self.SAMPLE_ROOT + sample_file, 'r') as f: + sample_content = f.read() + + event_type = sample_file[0: -5] + html_str = self.TOGGLE_SCRIPT % ((event_type, ) * 3) + html_str += ("" % event_type) + html_str += ("
%s
" + % (event_type, sample_content)) + + raw = nodes.raw('', html_str, format="html") + col.append(raw) + + return content + + +def setup(app): + app.add_directive('versioned_notifications', + VersionedNotificationDirective) diff --git a/doc/notification_samples/create-server-error.json b/doc/notification_samples/server-create-error.json similarity index 100% rename from doc/notification_samples/create-server-error.json rename to doc/notification_samples/server-create-error.json diff --git a/doc/source/conf.py b/doc/source/conf.py index 82be7947..be67404e 100755 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -15,6 +15,7 @@ import os import sys sys.path.insert(0, os.path.abspath('../..')) +sys.path.insert(0, os.path.abspath('../')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be @@ -25,6 +26,7 @@ extensions = [ 'oslo_config.sphinxconfiggen', 'oslo_policy.sphinxpolicygen', 'sphinx.ext.graphviz', + 'ext.versioned_notifications', ] # openstackdocstheme options diff --git a/doc/source/contributor/notifications.rst b/doc/source/contributor/notifications.rst new file mode 100644 index 00000000..715706d8 --- /dev/null +++ b/doc/source/contributor/notifications.rst @@ -0,0 +1,151 @@ +================================ +Versioned notifications in Mogan +================================ + +The Mogan notifications are events related with changes of resources, and can +be consumed by internal system. Notifications are sent to these services over a +message bus by oslo.messaging's Notifier class [1]_. The notifications +consumer could be a messaging bus listener service, e.g. Ceilometer +notification agent or Searchlight Listener. + +Generally, a notification message performed by a json string, which includes +two parts: a set of common info and a notification payload which is a +dictionary that including the information we want to send out. The format as +the following:: + + { + "priority": , + "event_type": , + "timestamp": , + "publisher_id": , + "message_id": , + "payload": + } + +The versioned notification is a concept that defines a base frame for general +notifications class definition. The payload is not a free form dictionary but a +serialized oslo versionedobject [2]_. + +Configure to enable notification in Mogan +========================================= + +To enable notification in Mogan, you need to configure the ``transport_url`` +option under the ``[oslo_messaging_notifications]`` section in Mogan's +configuration file. The ``transport_url`` is the message bus connection url, +for example:: + + rabbit://stackrabbit:password@127.0.0.1:5672/ + +By default, the notifications message will be sent to the message queue named +``versioned_notification.PRIORITY``, the *PRIORITY* depends on the priority +in the notification message. + +Add a new notification to Mogan +=============================== +The every versioned notification in Mogan is modeled with oslo +versionedobjects. Every versioned notification class inherits from the +`mogan.notifications.objects.base.NotificationBase` which +already defines three mandatory fields of the notification `event_type`, +`publisher_id` and `priority`. The new notification class shall add a new field +`payload` with an appropriate payload type. The payload object of the +notification shall inherit from the +`mogan.notifications.objects.base.NotificationPayloadBase` class and shall +define the fields of the payload as versionedobject fields. + +Please note that the notification objects shall not be registered to the +MoganObjectRegistry in order to avoid mixing Mogan internal objects with the +notification objects. Instead of that using the register_notification +decorator on every concrete notification object. + +The following code example defines the necessary model classes for a new +notification `myobject.update`:: + + @mogan_base.MoganObjectRegistry.register_notification + class MyObjectNotification(notification.NotificationBase): + # Version 1.0: Initial version + VERSION = '1.0' + + fields = { + 'payload': fields.ObjectField('MyObjectUpdatePayload') + } + + + @mogan_base.MoganObjectRegistry.register_notification + class MyObjectUpdatePayload(notification.NotificationPayloadBase): + # Version 1.0: Initial version + VERSION = '1.0' + fields = { + 'some_data': fields.StringField(), + 'another_data': fields.StringField(), + } + + +After that the notification can be populated and emitted with the following +code:: + + payload = MyObjectUpdatePayload(some_data="foo", another_data="bar") + MyObjectNotification( + publisher=notification.NotificationPublisher.from_service_obj( + ), + event_type=notification.EventType( + object='myobject', + action=fields.NotificationAction.UPDATE), + priority=fields.NotificationPriority.INFO, + payload=payload).emit(context) + +The above code will generate the following notification on the wire:: + + { + "priority":"INFO", + "payload":{ + "mogan_object.namespace":"mogan", + "mogan_object.name":"MyObjectUpdatePayload", + "mogan_object.version":"1.0", + "mogan_object.data":{ + "some_data":"foo", + "another_data":"bar", + } + }, + "event_type":"myobject.update", + "publisher_id":":" + } + + +What should be in the notification payload +========================================== +This is just a guideline. You should always consider the actual use case that +requires the notification. + +* Always include the identifier (e.g. uuid) of the entity that can be used to + query the whole entity over the REST API so that the consumer can get more + information about the entity. +* You should consider including those fields that are related to the event + you are sending the notification about. For example if a change of a field of + the entity triggers an update notification then you should include the field + to the payload. +* An update notification should contain information about which part of the + entity is changed. Either by filling the object changes part of the + payload (note that it is not supported by the notification framework + currently) or sending both the old state and the new state of the entity in + the payload. +* You should never include an internal object in the payload. Create a new + object and use the SCHEMA field to map the internal object to the + notification payload. In this way the evolution of the internal object model + can be decoupled from the evolution of the notification payload. +* The delete notification should contain the same information as the create or + update notifications. This makes it possible for the consumer to listen only + to the delete notifications but still filter on some fields of the entity + (e.g. project_id). + +Existing versioned notifications +================================ + +.. This is a reference anchor used in the main index page. +.. _versioned_notification_samples: + +.. versioned_notifications:: + + + +.. [1] https://docs.openstack.org/oslo.messaging/latest/reference/notifier.html +.. [2] https://docs.openstack.org/oslo.versionedobjects/latest/ diff --git a/doc/source/index.rst b/doc/source/index.rst index b328722a..8a1740ce 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -40,6 +40,7 @@ project. contributor/dev-quickstart contributor/make-changes-to-database contributor/testing + contributor/notifications Installation Guide ================== diff --git a/mogan/notifications/objects/exception.py b/mogan/notifications/objects/exception.py index 7a5f7023..5336f8f6 100644 --- a/mogan/notifications/objects/exception.py +++ b/mogan/notifications/objects/exception.py @@ -39,12 +39,3 @@ class ExceptionPayload(base.NotificationPayloadBase): module_name=module_name, exception=fault.__class__.__name__, exception_message=six.text_type(fault)) - - -@moban_base.MoganObjectRegistry.register_notification -class ExceptionNotification(base.NotificationBase): - # Version 1.0: Initial version - VERSION = '1.0' - fields = { - 'payload': fields.ObjectField('ExceptionPayload') - } diff --git a/mogan/notifications/objects/server.py b/mogan/notifications/objects/server.py index dc466e4d..2eeabbf7 100644 --- a/mogan/notifications/objects/server.py +++ b/mogan/notifications/objects/server.py @@ -125,6 +125,11 @@ class ServerActionPayload(ServerPayload): self.fault = fault +@base.notification_sample('server-create-start.json') +@base.notification_sample('server-create-end.json') +@base.notification_sample('server-create-error.json') +@base.notification_sample('server-delete-start.json') +@base.notification_sample('server-delete-end.json') @mogan_base.MoganObjectRegistry.register_notification class ServerActionNotification(base.NotificationBase): # Version 1.0: Initial version diff --git a/mogan/tests/unit/notifications/test_notification.py b/mogan/tests/unit/notifications/test_notification.py index bb75b6e4..34fe5361 100644 --- a/mogan/tests/unit/notifications/test_notification.py +++ b/mogan/tests/unit/notifications/test_notification.py @@ -231,7 +231,6 @@ notification_object_data = { 'ServerActionPayload': '1.0-a22c2f18b8dd17a3990e5b4c64989d26', 'ServerActionNotification': '1.0-20087e599436bd9db62ae1fb5e2dfef2', 'ExceptionPayload': '1.0-7c31986d8d78bed910c324965c431e18', - 'ExceptionNotification': '1.0-20087e599436bd9db62ae1fb5e2dfef2', 'EventType': '1.0-93493dd78bdfed806fca70c91d85cbb4', 'NotificationPublisher': '1.0-4b0b0d662b21eeed0b23617f3f11794b' }