[DOC] Add contributor guide for versioned notification

Change-Id: I79a2842bf31d6b8f6f697e4bdc980db959efa7cc
This commit is contained in:
liusheng 2017-09-17 22:02:34 +08:00
parent 7744129c83
commit 10c429e08a
9 changed files with 309 additions and 10 deletions

0
doc/ext/__init__.py Normal file
View File

View File

@ -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 <balazs.gibizer@ericsson.com>
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 = """
<!-- jQuery -->
<script type="text/javascript" src="../_static/js/jquery-3.2.1.min.js">
</script>
<script>
jQuery(document).ready(function(){
jQuery('#%s-div').toggle('show');
jQuery('#%s-hideshow').on('click', function(event) {
jQuery('#%s-div').toggle('show');
});
});
</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 += ("<input type='button' id='%s-hideshow' "
"value='hide/show sample'>" % event_type)
html_str += ("<div id='%s-div'><pre>%s</pre></div>"
% (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)

View File

@ -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

View File

@ -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": <string, selected from a predefined list by the sender>,
"event_type": <string, defined by the sender>,
"timestamp": <string, the isotime of when the notification emitted>,
"publisher_id": <string, defined by the sender>,
"message_id": <uuid, generated by oslo>,
"payload": <json serialized dict, defined by the sender>
}
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(
<movan.objects.server.Server instance that emits the notification>),
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":"<the name of the service>:<the host where the service runs>"
}
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/

View File

@ -40,6 +40,7 @@ project.
contributor/dev-quickstart
contributor/make-changes-to-database
contributor/testing
contributor/notifications
Installation Guide
==================

View File

@ -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')
}

View File

@ -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

View File

@ -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'
}