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>
}
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.
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 notifications.notify_on_state_change, notifications.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.
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].
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:
There is a Nova configuration parameter notifications.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.
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 versioned_notifications_topic config option.
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_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.notifications.base.NotificationPayloadBase class and shall define the fields of the payload as versionedobject fields. The base classes are described in the following section.
NotificationBase
(**kwargs)Bases: nova.notifications.objects.base.NotificationObject
Base class for versioned notifications.
Every subclass shall define a 'payload' field.
emit
(context)Send the notification.
NotificationObject
(**kwargs)Bases: nova.objects.base.NovaObject
Base class for every notification related versioned object.
NotificationPayloadBase
Bases: nova.notifications.objects.base.NotificationObject
Base class for the payload of versioned notifications.
populate_schema
(**kwargs)Populate the object based on the SCHEMA and the source objects
Parameters: | kwargs -- A dict contains the source object at the key defined in the SCHEMA |
---|
notification_sample
(sample)Class decorator to attach the notification sample information to the notification object for documentation generation purposes.
Parameters: | sample -- the path of the sample json file relative to the doc/notification_samples/ directory in the nova repository root. |
---|
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 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.
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. See [3] 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. See [4] as an example.
This is just a guideline. You should always consider the actual use case that requires the notification.
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.
[1] | http://docs.openstack.org/developer/oslo.messaging/notifier.html |
[2] | http://docs.openstack.org/developer/oslo.versionedobjects |
[3] | https://review.openstack.org/#/c/463001/ |
[4] | https://review.openstack.org/#/c/453077/ |
Except where otherwise noted, this document is licensed under Creative Commons Attribution 3.0 License. See all OpenStack Legal Documents.