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>
This commit is contained in:
parent
6e126869f0
commit
9a31212a44
@ -163,6 +163,7 @@ log management and live migration of instances.
|
||||
security-groups
|
||||
security
|
||||
vendordata
|
||||
notifications
|
||||
|
||||
|
||||
Advanced configuration
|
||||
|
132
doc/source/admin/notifications.rst
Normal file
132
doc/source/admin/notifications.rst
Normal file
@ -0,0 +1,132 @@
|
||||
=============
|
||||
Notifications
|
||||
=============
|
||||
|
||||
Like many 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>
|
||||
}
|
||||
|
||||
There are two types of notifications in nova: legacy notifications which have
|
||||
an unversioned payload and newer notifications which have a versioned payload.
|
||||
|
||||
|
||||
Legacy (unversioned) notifications
|
||||
----------------------------------
|
||||
|
||||
The unversioned notifications exist from the early days of nova and have mostly
|
||||
grown organically. 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.
|
||||
|
||||
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
|
||||
:oslo.config:option:`oslo_messaging_notifications.driver` and
|
||||
:oslo.config:option:`oslo_messaging_notifications.topics`.
|
||||
|
||||
|
||||
Versioned notifications
|
||||
-----------------------
|
||||
|
||||
The versioned notification concept was 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 <>`.
|
||||
|
||||
.. _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 has
|
||||
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 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 an additional field, ``nova_object.name``, for every payload
|
||||
alongside the ``nova_object.data`` and ``nova_object.version`` fields. This
|
||||
field contains the name of the nova internal representation of the payload
|
||||
type. Client code should not depend on this name.
|
||||
|
||||
A `presentation from the Train summit`__ goes over the background and usage of
|
||||
versioned notifications, and provides a demo.
|
||||
|
||||
.. __: https://www.openstack.org/videos/summits/denver-2019/nova-versioned-notifications-the-result-of-a-3-year-journey
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
The :oslo.config:option:`notifications.notification_format` config option 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
|
||||
this can be configured using the
|
||||
:oslo.config:option:`notifications.versioned_notifications_topics` config
|
||||
option.
|
||||
|
||||
There are notification configuration options in nova which are specific for
|
||||
certain notification types like
|
||||
:oslo.config:option:`notifications.notify_on_state_change`,
|
||||
:oslo.config:option:`notifications.default_level`, etc.
|
||||
|
||||
Notifications can be disabled entirely by setting the
|
||||
:oslo.config:option:`oslo_messaging_notifications.driver` config option to
|
||||
``noop``.
|
||||
|
||||
|
||||
Reference
|
||||
---------
|
||||
|
||||
A list of all currently supported versioned notifications can be found in
|
||||
:doc:`/reference/notifications`.
|
@ -149,6 +149,11 @@ changes done to the API, as the impact can be very wide.
|
||||
* :doc:`/contributor/api-ref-guideline`: The guideline to write the API
|
||||
reference.
|
||||
|
||||
Nova also provides notifications over the RPC API, which you may wish to
|
||||
extend.
|
||||
|
||||
* :doc:`/contributor/notifications`: How to add your own notifications
|
||||
|
||||
.. # NOTE(amotoki): toctree needs to be placed at the end of the secion to
|
||||
# keep the document structure in the PDF doc.
|
||||
.. toctree::
|
||||
@ -158,6 +163,7 @@ changes done to the API, as the impact can be very wide.
|
||||
api-2
|
||||
microversions
|
||||
api-ref-guideline
|
||||
notifications
|
||||
|
||||
Nova Major Subsystems
|
||||
=====================
|
||||
|
272
doc/source/contributor/notifications.rst
Normal file
272
doc/source/contributor/notifications.rst
Normal file
@ -0,0 +1,272 @@
|
||||
=============
|
||||
Notifications
|
||||
=============
|
||||
|
||||
As discussed in :doc:`/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 :doc:`/admin/notifications`.
|
||||
For a list of available versioned notifications, refer to
|
||||
:doc:`/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.
|
||||
|
||||
.. rubric:: The ``nova.notifications.objects.base`` module
|
||||
|
||||
.. automodule:: nova.notifications.objects.base
|
||||
:noindex:
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
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``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@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.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
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.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"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.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@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.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
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 ``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. See [1]_ 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 [2]_
|
||||
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`_).
|
||||
|
||||
.. _bug 1823104: https://bugs.launchpad.net/nova/+bug/1823104
|
||||
|
||||
.. references:
|
||||
|
||||
.. [1] https://review.opendev.org/#/c/463001/
|
||||
.. [2] https://review.opendev.org/#/c/453077/
|
@ -96,8 +96,8 @@ resources will help you get started with consuming the API directly.
|
||||
|
||||
Nova can be configured to emit notifications over RPC.
|
||||
|
||||
* :ref:`Versioned Notifications <versioned_notification_samples>`: This
|
||||
provides the list of existing versioned notifications with sample payloads.
|
||||
* :doc:`Versioned Notifications </admin/notifications>`: This
|
||||
provides information on the notifications emitted by nova.
|
||||
|
||||
Other end-user guides can be found under :doc:`/user/index`.
|
||||
|
||||
|
@ -24,8 +24,7 @@ The following is a dive into some of the internals in nova.
|
||||
compute instances
|
||||
* :doc:`/reference/threading`: The concurrency model used in nova, which is
|
||||
based on eventlet, and may not be familiar to everyone.
|
||||
* :doc:`/reference/notifications`: How the notifications subsystem works in
|
||||
nova, and considerations when adding notifications.
|
||||
* :doc:`/reference/notifications`: The notifications available in nova.
|
||||
* :doc:`/reference/update-provider-tree`: A detailed explanation of the
|
||||
``ComputeDriver.update_provider_tree`` method.
|
||||
* :doc:`/reference/upgrade-checks`: A guide to writing automated upgrade
|
||||
|
@ -1,375 +1,15 @@
|
||||
..
|
||||
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
|
||||
=================================
|
||||
Available versioned notifications
|
||||
=================================
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
.. note::
|
||||
|
||||
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-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:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[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.config:option:`notifications.notify_on_state_change`,
|
||||
:oslo.config:option:`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.
|
||||
|
||||
|
||||
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 <>`.
|
||||
|
||||
.. _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 ``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 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.config:option:`notifications.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.config:option:`notifications.versioned_notifications_topics`
|
||||
config option.
|
||||
|
||||
A `presentation from the Train summit`_ goes over the background and usage of
|
||||
versioned notifications, and provides a demo.
|
||||
|
||||
.. _presentation from the Train summit: https://www.openstack.org/videos/summits/denver-2019/nova-versioned-notifications-the-result-of-a-3-year-journey
|
||||
|
||||
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
|
||||
..........................................
|
||||
.. automodule:: nova.notifications.objects.base
|
||||
:noindex:
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
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 :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 ``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. See [1]_ 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 [2]_
|
||||
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`_).
|
||||
|
||||
.. _bug 1823104: https://bugs.launchpad.net/nova/+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.
|
||||
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.
|
||||
|
||||
.. This is a reference anchor used in the main index page.
|
||||
.. _versioned_notification_samples:
|
||||
|
||||
.. versioned_notifications::
|
||||
|
||||
.. [1] https://review.opendev.org/#/c/463001/
|
||||
.. [2] https://review.opendev.org/#/c/453077/
|
||||
|
Loading…
Reference in New Issue
Block a user