
This commit adds a new sphinx extension that inspects the nova code and adds information about the existing versioned notifications to the nofitications devref. This way the devref is automatically kept up to date with relevant information. Partially-Implements: bp versioned-notification-api Change-Id: If65d5d81e26cb2b4a9f57a8c7d37d3de310ebe00
278 lines
12 KiB
ReStructuredText
278 lines
12 KiB
ReStructuredText
..
|
|
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.
|
|
|
|
There is a Nova configuration parameter `notification_format` that can be used
|
|
to specify which notifications are emitted by Nova. The possible values are
|
|
`unversioned`, `versioned`, `both` and the default value is `both`.
|
|
|
|
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`::
|
|
|
|
@notification.notification_sample('myobject-update.json')
|
|
@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::
|
|
|
|
@notification.notification_sample('service-update.json')
|
|
@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 and the notification object shall be
|
|
decorated with the `notification_sample` decorator. For example the
|
|
`service.update` notification has a sample file stored in
|
|
`doc/sample_notifications/service-update.json` and the
|
|
ServiceUpdateNotification class is decorated accordingly.
|
|
|
|
Existing versioned notifications
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
.. versioned_notifications::
|
|
|
|
|
|
|
|
.. [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
|