Merge "doc: add devref about versioned notifications"
This commit is contained in:
commit
ce2acf335d
@ -157,6 +157,7 @@ Open Development.
|
|||||||
block_device_mapping
|
block_device_mapping
|
||||||
addmethod.openstackapi
|
addmethod.openstackapi
|
||||||
conductor
|
conductor
|
||||||
|
notifications
|
||||||
|
|
||||||
Architecture Evolution Plans
|
Architecture Evolution Plans
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
263
doc/source/notifications.rst
Normal file
263
doc/source/notifications.rst
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
..
|
||||||
|
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.
|
||||||
|
|
||||||
|
Notifications in Nova
|
||||||
|
=====================
|
||||||
|
Similarly to other OpenStack services Nova emits notifications to the message
|
||||||
|
bus with the Notifier class provided by oslo.messaging [1]_. From the
|
||||||
|
notification consumer point of view a notification consists of two parts: an
|
||||||
|
envelope with a fixed structure defined by oslo.messaging and a payload defined
|
||||||
|
by the service emitting the notification. The envelope format is 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>
|
||||||
|
}
|
||||||
|
|
||||||
|
There are two types of notifications in Nova: legacy notifications which have
|
||||||
|
an unversioned payload and newer notifications which have a versioned payload.
|
||||||
|
|
||||||
|
Unversioned notifications
|
||||||
|
-------------------------
|
||||||
|
Nova code uses the nova.rpc.get_notifier call to get a configured
|
||||||
|
oslo.messaging Notifier object and it uses the oslo provided functions on the
|
||||||
|
Notifier object to emit notifications. The configuration of the returned
|
||||||
|
Notifier object depends on the parameters of the get_notifier call and the
|
||||||
|
value of the oslo.messaging configuration options `notification_driver` and
|
||||||
|
`notification_topics`. There are notification configuration options in Nova
|
||||||
|
which are specific for certain notification types like
|
||||||
|
`notify_on_state_change`, `notify_api_faults`, `default_notification_level`,
|
||||||
|
etc.
|
||||||
|
|
||||||
|
The structure of the payload of the unversioned notifications is defined in the
|
||||||
|
code that emits the notification and no documentation or enforced backward
|
||||||
|
compatibility contract exists for that format.
|
||||||
|
|
||||||
|
|
||||||
|
Versioned notifications
|
||||||
|
-----------------------
|
||||||
|
The versioned notification concept is created to fix the shortcomings of the
|
||||||
|
unversioned notifications. The envelope structure of the emitted notification
|
||||||
|
is the same as in the unversioned notification case as it is provided by
|
||||||
|
oslo.messaging. However the payload is not a free form dictionary but a
|
||||||
|
serialized oslo versionedobject [2]_.
|
||||||
|
|
||||||
|
.. _service.update:
|
||||||
|
|
||||||
|
For example the wire format of the `service.update` notification looks like the
|
||||||
|
following::
|
||||||
|
|
||||||
|
{
|
||||||
|
"priority":"INFO",
|
||||||
|
"payload":{
|
||||||
|
"nova_object.namespace":"nova",
|
||||||
|
"nova_object.name":"ServiceStatusPayload",
|
||||||
|
"nova_object.version":"1.0",
|
||||||
|
"nova_object.data":{
|
||||||
|
"host":"host1",
|
||||||
|
"disabled":false,
|
||||||
|
"last_seen_up":null,
|
||||||
|
"binary":"nova-compute",
|
||||||
|
"topic":"compute",
|
||||||
|
"disabled_reason":null,
|
||||||
|
"report_count":1,
|
||||||
|
"forced_down":false,
|
||||||
|
"version":2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"event_type":"service.update",
|
||||||
|
"publisher_id":"nova-compute:host1"
|
||||||
|
}
|
||||||
|
|
||||||
|
The serialized oslo versionedobject as a payload provides a version number to
|
||||||
|
the consumer so the consumer can detect if the structure of the payload is
|
||||||
|
changed. Nova provides the following contract regarding the versioned
|
||||||
|
notification payload:
|
||||||
|
|
||||||
|
* the payload version defined by the `the nova_object.version` field of the
|
||||||
|
payload will be increased if and only if the syntax or the semantics of the
|
||||||
|
`nova_object.data` field of the payload is changed.
|
||||||
|
* a minor version bump indicates a backward compatible change which means that
|
||||||
|
only new fields are added to the payload so a well written consumer can still
|
||||||
|
consume the new payload without any change.
|
||||||
|
* a major version bump indicates a backward incompatible change of the payload
|
||||||
|
which can mean removed fields, type change, etc in the payload.
|
||||||
|
|
||||||
|
How to add a new versioned notification
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
To support the above contract from the Nova code every versioned notification
|
||||||
|
is modeled with oslo versionedobjects. Every versioned notification class
|
||||||
|
shall inherit from the `nova.objects.notification.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
|
||||||
|
notifications shall inherit from the
|
||||||
|
`nova.objects.notification.NotificationPayloadBase` class and shall define the
|
||||||
|
fields of the payload as versionedobject fields. The base classes are described
|
||||||
|
in [3]_.
|
||||||
|
|
||||||
|
The following code example defines the necessary model classes for a new
|
||||||
|
notification `myobject.update`::
|
||||||
|
|
||||||
|
@base.NovaObjectRegistry.register
|
||||||
|
class MyObjectNotification(notification.NotificationBase):
|
||||||
|
# Version 1.0: Initial version
|
||||||
|
VERSION = '1.0'
|
||||||
|
|
||||||
|
fields = {
|
||||||
|
'payload': fields.ObjectField('MyObjectUpdatePayload')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@base.NovaObjectRegistry.register
|
||||||
|
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(
|
||||||
|
<nova.objects.service.Service 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":{
|
||||||
|
"nova_object.namespace":"nova",
|
||||||
|
"nova_object.name":"MyObjectUpdatePayload",
|
||||||
|
"nova_object.version":"1.0",
|
||||||
|
"nova_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>"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
There is a possibility to reuse an existing versionedobject as notification
|
||||||
|
payload by adding a `SCHEMA` field for the payload class that defines a mapping
|
||||||
|
between the fields of existing objects and the fields of the new payload
|
||||||
|
object. For example the service.status notification reuses the existing
|
||||||
|
`nova.objects.service.Service` object when defines the notification's payload::
|
||||||
|
|
||||||
|
@base.NovaObjectRegistry.register
|
||||||
|
class ServiceStatusNotification(notification.NotificationBase):
|
||||||
|
# Version 1.0: Initial version
|
||||||
|
VERSION = '1.0'
|
||||||
|
|
||||||
|
fields = {
|
||||||
|
'payload': fields.ObjectField('ServiceStatusPayload')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@base.NovaObjectRegistry.register
|
||||||
|
class ServiceStatusPayload(notification.NotificationPayloadBase):
|
||||||
|
SCHEMA = {
|
||||||
|
'host': ('service', 'host'),
|
||||||
|
'binary': ('service', 'binary'),
|
||||||
|
'topic': ('service', 'topic'),
|
||||||
|
'report_count': ('service', 'report_count'),
|
||||||
|
'disabled': ('service', 'disabled'),
|
||||||
|
'disabled_reason': ('service', 'disabled_reason'),
|
||||||
|
'availability_zone': ('service', 'availability_zone'),
|
||||||
|
'last_seen_up': ('service', 'last_seen_up'),
|
||||||
|
'forced_down': ('service', 'forced_down'),
|
||||||
|
'version': ('service', 'version')
|
||||||
|
}
|
||||||
|
# Version 1.0: Initial version
|
||||||
|
VERSION = '1.0'
|
||||||
|
fields = {
|
||||||
|
'host': fields.StringField(nullable=True),
|
||||||
|
'binary': fields.StringField(nullable=True),
|
||||||
|
'topic': fields.StringField(nullable=True),
|
||||||
|
'report_count': fields.IntegerField(),
|
||||||
|
'disabled': fields.BooleanField(),
|
||||||
|
'disabled_reason': fields.StringField(nullable=True),
|
||||||
|
'availability_zone': fields.StringField(nullable=True),
|
||||||
|
'last_seen_up': fields.DateTimeField(nullable=True),
|
||||||
|
'forced_down': fields.BooleanField(),
|
||||||
|
'version': fields.IntegerField(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def populate_schema(self, service):
|
||||||
|
super(ServiceStatusPayload, self).populate_schema(service=service)
|
||||||
|
|
||||||
|
If the `SCHEMA` field is defined then the payload object needs to be populated
|
||||||
|
with the `populate_schema` call before it can be emitted::
|
||||||
|
|
||||||
|
payload = ServiceStatusPayload()
|
||||||
|
payload.populate_schema(service=<nova.object.service.Service object>)
|
||||||
|
ServiceStatusNotification(
|
||||||
|
publisher=notification.NotificationPublisher.from_service_obj(
|
||||||
|
<nova.object.service.Service object>),
|
||||||
|
event_type=notification.EventType(
|
||||||
|
object='service',
|
||||||
|
action=fields.NotificationAction.UPDATE),
|
||||||
|
priority=fields.NotificationPriority.INFO,
|
||||||
|
payload=payload).emit(context)
|
||||||
|
|
||||||
|
The above code will emit the :ref:`already shown notification<service.update>`
|
||||||
|
on the wire.
|
||||||
|
|
||||||
|
Every item in the `SCHEMA` has the syntax of::
|
||||||
|
|
||||||
|
<payload field name which needs to be filled>:
|
||||||
|
(<name of the parameter of the populate_schema call>,
|
||||||
|
<the name of a field of the parameter object>)
|
||||||
|
|
||||||
|
The mapping defined in the `SCHEMA` field has the following semantics. When
|
||||||
|
the `populate_schema` function is called the content of the `SCHEMA` field is
|
||||||
|
enumerated and the value of the field of the pointed parameter object is copied
|
||||||
|
to the requested payload field. So in the above example the `host` field of
|
||||||
|
the payload object is populated from the value of the `host` field of the
|
||||||
|
`service` object that is passed as a parameter to the `populate_schema` call.
|
||||||
|
|
||||||
|
A notification payload object can reuse fields from multiple existing
|
||||||
|
objects. Also a notification can have both new and reused fields in its
|
||||||
|
payload.
|
||||||
|
|
||||||
|
Note that the notification's publisher instance can be created two different
|
||||||
|
ways. It can be created by instantiating the `NotificationPublisher` object
|
||||||
|
with a `host` and a `binary` string parameter or it can be generated from a
|
||||||
|
`Service` object by calling `NotificationPublisher.from_service_obj` function.
|
||||||
|
|
||||||
|
Versioned notifications shall have a sample file stored under
|
||||||
|
`doc/sample_notifications` directory. For example the `service.update`
|
||||||
|
notification has a sample file stored in
|
||||||
|
`doc/sample_notifications/service-update.json`.
|
||||||
|
|
||||||
|
.. [1] http://docs.openstack.org/developer/oslo.messaging/notifier.html
|
||||||
|
.. [2] http://docs.openstack.org/developer/oslo.versionedobjects
|
||||||
|
.. [3] http://docs.openstack.org/developer/nova/devref/api/nova.objects.notification.html
|
Loading…
Reference in New Issue
Block a user