The 'binary' parameter has been changed to the 'source' since I95b5b0826190d396efe7bfc017f6081a6356da65. But the notification document has not been updated yet. Replace the 'binary' parameter with the 'source' parameter. Change-Id: I141c90ac27d16f2e9c033bcd2f95ac08904a2f52 Closes-Bug: #1836005
16 KiB
Notifications in Nova
Similarly to other OpenStack services Nova emits notifications to the message bus with the Notifier class provided by :oslo.messaging-doc:oslo.messaging <reference/notifier.html>. 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>
}
Notifications can be completely disabled by setting the following in your nova configuration file:
[oslo_messaging_notifications]
driver = noop
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
driver
and topics
. There are notification
configuration options in Nova which are specific for certain
notification types like :oslo.confignotifications.notify_on_state_change
,
:oslo.confignotifications.default_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.versionedobjects-doc:oslo versionedobjects object <>.
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
nova_object.version
field of the payload will be increased if and only if the syntax or the semantics of thenova_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 an additional field 'nova_object.name' for every payload besides 'nova_object.data' and 'nova_object.version'. This field contains the name of the nova internal representation of the payload type. Client code should not depend on this name.
There is a Nova configuration parameter :oslo.confignotifications.notification_format
that can be used
to specify which notifications are emitted by Nova.
The versioned notifications are emitted to a different topic than the
legacy notifications. By default they are emitted to
'versioned_notifications' but it is configurable in the nova.conf with
the :oslo.confignotifications.versioned_notifications_topics
config
option.
A presentation from the Train summit goes over the background and usage of versioned notifications, and provides a demo.
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.notifications.objects.base.NotificationBase
which
already defines three mandatory fields of the notification
event_type
, publisher
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.notifications.objects.base.NotificationPayloadBase
class and shall define the fields of the payload as versionedobject
fields. The base classes are described in the following section.
The nova.notifications.objects.base module
nova.notifications.objects.base
Please note that the notification objects shall not be registered to the NovaObjectRegistry to avoid mixing nova internal objects with the notification objects. Instead of that use the register_notification decorator on every concrete notification object.
The following code example defines the necessary model classes for a
new notification myobject.update
:
@notification.notification_sample('myobject-update.json')
@object_base.NovaObjectRegistry.register.register_notification
class MyObjectNotification(notification.NotificationBase):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'payload': fields.ObjectField('MyObjectUpdatePayload')
}
@object_base.NovaObjectRegistry.register.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(
<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')
@object_base.NovaObjectRegistry.register.register_notification
class ServiceStatusNotification(notification.NotificationBase):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'payload': fields.ObjectField('ServiceStatusPayload')
}
@object_base.NovaObjectRegistry.register.register_notification
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 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
source
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.
Notification payload classes can use inheritance to avoid duplicating common payload fragments in nova code. However the leaf classes used directly in a notification should be created with care to avoid future needs of adding extra level of inheritance that changes the name of the leaf class as that name is present in the payload class. If this cannot be avoided and the only change is the renaming then the version of the new payload shall be the same as the old payload was before the rename. See1 as an example. If the renaming involves any other changes on the payload (e.g. adding new fields) then the version of the new payload shall be higher than the old payload was. See2 as an example.
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 what part of the entity is changed. Either by filling the nova_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 a nova internal object in the payload. Create a new object and use the SCHEMA field to map the internal object to the notification payload. This way the evolution of the internal object model can be decoupled from the evolution of the notification payload.
Important
This does not mean that every field from internal objects should be mirrored in the notification payload objects. Think about what is actually needed by a consumer before adding it to a payload. When in doubt, if no one is requesting specific information in notifications, then leave it out until someone asks for it.
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).
What should NOT be in the notification payload
- Generally anything that contains sensitive information about the internals of the nova deployment, for example fields that contain access credentials to a cell database or message queue (see bug 1823104).
Existing versioned notifications
Note
Versioned notifications are added in each release, so the samples represented below may not necessarily be in an older version of nova. Ensure you are looking at the correct version of the documentation for the release you are using.