nova/doc/source/contributor/notifications.rst
Stephen Finucane 9a31212a44 doc: Split up notifications document
This was actually three documents in one:

- An admin doc detailing how to configure and use notifications
- A contributor doc describing how to extend the versioned notifications
- A reference doc listing available versioned notifications

Split the doc up to reflect this

Change-Id: I880f1c77387efcc3c1e147323b224e10156e0a52
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
2022-02-09 18:02:04 +00:00

11 KiB

Notifications

As discussed in /admin/notifications, nova emits notifications to the message bus. There are two types of notifications provided in nova: legacy (unversioned) notifications and versioned notifications. As a developer, you may choose to add additional notifications or extend existing notifications.

Note

This section provides information on adding your own notifications in nova. For background information on notifications including usage information, refer to /admin/notifications. For a list of available versioned notifications, refer to /reference/notifications.

How to add a new versioned notification

To provide the versioning for versioned notifications, each 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

Note that the notification objects must not be registered to the NovaObjectRegistry to avoid mixing nova-internal objects with the notification objects. Instead, 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).

  1. https://review.opendev.org/#/c/463001/↩︎

  2. https://review.opendev.org/#/c/453077/↩︎