Notice from Nova

Similar to other OpenStack services, Nova sends notifications to the message bus through the Notifier class provided by oslo.messaging. From the notification consumer's point of view, a notification consists of two parts: a fixed-structured envelope defined by oslo.messaging and a payload defined by the service issuing the notification. The format of the envelope is as follows:

{
    "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 with non-versioned payloads and new notifications with versioned payloads.

Unversioned notifications

The Nova code uses the notification call of nova.rpc.get to get the configured oslo.messaging notification object and then it uses the functions provided by oslo on the Notifier object to send the notification. The configuration of the returned Notifier object depends on the parameters of the get notifier call and the values ​​of the oslo.messaging configuration options driver and topics. There are notification configuration options in Nova, which specify specific notification types, such as: notifications.notify_on_state_change, notifications.default_level, etc.

The structure of the payload of unversioned notifications is defined in the code that issues the notification, and there is no documentation or contract for enforcing backward compatibility in this format.

Version Control Notifications

The version notification concept was created to fix the shortcomings of non-version notifications. The envelope structure for issuing notifications is the same as in the case of unversioned notifications provided by oslo.messaging. However, the payload is not an arbitrary form dictionary, but a serialized oslo versioned object.

For example, the wire format for service.update notification looks like this:

{
    "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 that the consumer can detect if the structure of the payload has changed. Nova provides the following contracts on the payload of versioned notifications:

  • The payload version defined by the payload's nova object.version field is incremented if and only if the syntax or semantics of the payload's nova object.data field is changed.
  • A minor version of bump represents a backwards compatible change, meaning only new fields are added to the payload, so a well-written consumer can still use the new payload without any changes .
  • A major version bump indicates backwards-incompatible changes to the payload, which could mean removing fields in the payload, type changes, etc.
  • In addition to 'nova_object.data' and 'nova_object.version', there is an additional nova_object.name field on each payload.

There is a Nova configuration parameter, notifications.notification_format, which specifies what kind of notifications can be sent by NOVA. Possible values ​​are unversioned, versioned, or both. Both are fine by default.

Versioned notifications are sent to a different topic than legacy notifications. By default they are sent to versioned_notifications, but it can be changed in the versioned_notifications_topic configuration option in the nova.conf file.

How to add versioned notifications

To support the above contract from Nova code, each versioned notification is modeled with oslo versionedobjects. Every version of the notification class should inherit nova.notifications.objects.base.NotificationBase, which already defines three mandatory fields for notifications, event_type, publisher_id and priority. New notification classes should add a new field payload with an appropriate payload type. The notification payload object should inherit the nova.objects.notifications.base.NotificationPayloadBase class and should define the payload field as the versionedobject field. The next section describes the base class.

nova.notifications.objects.base module

Note that notification objects should not be registered with NovaObjectRegistry to avoid mixing nova internal objects with notification objects. Use the register notification decorator on each concrete notification object instead.

The following code example defines the necessary model classes for the 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 following code can be used to populate and send notifications:

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:

{
    "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>"
}

It is possible to reuse an existing VersionedObject by adding a SCHEMA field to the payload class, which defines the mapping between the existing object fields and the new payload object fields. For example, the service.status notification reuses an existing nova.objects.service.Service object when defining the notification 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, the payload object needs to be populated by calling populate_schema before starting to send notifications:

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 generate a JSON format data online, the format is similar to the above event_type format of myobject.update.

The data format of the SCHEMA field follows the following method:

<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>)

Mappings defined in SCHEMA fields have the following semantics. When the populate_schema function is called, the contents of the SCHEMA field are enumerated, and the field value pointing to the parameter object is copied into the request's payload field. So, in the above example, the host field of the payload object is populated with the value of the host field of the service object, which is passed as a parameter to the populate_schema call.

A notification's payload object can reuse fields from multiple existing objects. Likewise, notifications can have both new and reused fields in their payload.

Note that publisher instances of notifications are created in two different ways. Created by methods of instance NotificationPublisher objects with host and binary string parameters, or by methods of NotificationPublisher.from_service_obj to generate Service objects.

Versioned notifications have a sample file stored in the doc/sample_notifications directory and the notification object should 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.

  The Notification payload class can use inheritance to avoid duplicating common payload fragments in nova code. However, the leaf class used directly for the notification should be created in the notification to avoid the need to add an extra level of inheritance in the future, thus changing the name of the leaf class that appears in the payload class. If this cannot be avoided and the only change is the rename, the new payload should be the same version as the old payload before the rename.

Translated from: https://docs.openstack.org/nova/latest/reference/notifications.html

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325384877&siteId=291194637