NOVA中的notification機制介紹與定製開發

個人blog:http://www.sskywatcher.com/blog

由sskywatcher翻譯,轉載請註明出處

目錄

概述

不支持多版本的經典通知

支持多版本的新型通知

如何增加一個新的notification(規程與示例)

模塊nova.notifications.objects.base

通知的負載中應該包含哪些內容?

當前已存在的通知



概述

與 OpenStack 的其他服務模塊類似,Nova 使用 oslo.messaging 模塊提供的 Notifier 類 項消息總線發出通知消息。 從一個通知消息的消費者角度來看,一個通知消息(notification) 包含兩個部分:一個由 oslo.messaging 定義好的有固定結構格式的消息封皮(envelope )部分、 一個由通知消息的發出者服務定義的負載(payload)。消息封皮的格式如下所示:

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

在本地環境中配置 nova 配置文件的一下部分,可以完全關閉 Notification 功能 :

[oslo_messaging_notifications]
driver = noop

Nova 中有兩種不同類型的通知 : 負載消息沒有版本之分的經典的通知消息、支持多版本負載消息的通知消息。


不支持多版本的經典通知

Nova 的代碼使用 nova.rpc.get_notifier 來獲取已經配置好的 oslo.messaging Notifier 對象, 使用olso組件提供的Notifier 的成員方法來發送通知。 get_notifier 調用的參數以及 oslo.messaging 模塊配置的選項 "driver" 和 "topics"決定了獲取到的Notifier 對象的配置。Nova 中有多個配置選項,這些選項用於指定 notification 的類型,如下例:

notifications.notify_on_state_change, notifications.default_level,

等等。

發送通知的代碼中定義了無多版本支持的通知的負載的結構,這種格式沒有文檔記載說明,也不考慮任何的後向兼容性。


支持多版本的新型通知

支持多版本的新型通知資格概念之所以被提出來,就是爲了彌補老式通知的缺點。 oslo.messaging 給這種新型通知提供的消息封皮部分的結構與老式通知消息的完全相同。 但是負載部分不再是一個自由定義的字典格式,相應的部分被 olso 的 versionedobjects 模塊的對象所取代

舉一個例子:service.update 通知消息的格式如下所示:

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

序列化後的 oslo versionedobject 作爲負載向消費者提供了一個版本號,這樣,消費者就可以檢測到消息中的格式是否發生了變化。對於支持多版本的通知負載,Nova 作出了以下幾個約定:

  • 當且僅當負載中的 nova_object.data 字段的語法或者語義發生變化時,負載中 nova_object.version 字段定義的版本號將會增加。
  • 使用次級版本號主要提示後向兼容性的中負載發生了較小的改變,即僅僅向負載中增加了新的字段,這樣對於一個正確編寫的消費者來說,不需要做出任何改變依然可以正常地消息這個通知消息。
  • 主版本號主要用於提示後向兼容性中的負載某些大的變動。比如負載可能移除了某些字段、字段的類型發生了變更等等。
  • 除了 ‘nova_object.data’ 和 ‘nova_object.version’ 字段外,每個負載中都有一個附加的字段 ‘nova_object.name’,這個字段包含了負責類型在 nova 模塊內部表達式的名稱。客戶端的代碼不能基於這個名稱來編寫。

Nova 的配置選項 notifications.notification_format 可以指定用哪一種格式來發送通知。 可選的值包括: "unversioned", "versioned“, "both",默認值是 "both"

相比於老式的通知,新的通知可以被髮送到不同的 topic 。默認情況下,他們將被髮送到 ‘versioned_notifications’ ,你可以在Nova 的配置文件 nova.conf 中使用 notifications.versioned_notifications_topics 這個選項來配置它。


如何增加一個新的notification(規程與示例)

爲了支持以上提及的約定,nova 代碼中每個支持多版本的通知都是以 oslo versionedobjects 作爲模板。 每個支持多版本的通知應該繼承自 nova.notifications.objects.base.NotificationBase ,該基類已經定義了通知類的3個強制性的字段:event_type, publisherpriority。新的通知類應該增加一個新字段  payload 並制定一個合適的負載類型。 通知中的負載對象(payload) 應該繼承自 nova.notifications.objects.base.NotificationPayloadBase, 並且需要將負載字段定義爲對應的 versionedobject 字段。 以下將列出各基類:

模塊nova.notifications.objects.base

class EventType(object, action, phase=None)

        基類: nova.notifications.objects.base.NotificationObject

   to_notification_event_type_field()

             將對象序列化的方法。

class NotificationBase(**kwargs)

    基類: nova.notifications.objects.base.NotificationObject

    這是支持多版本的通知類的基類。

    每個子類都需要定義 ‘payload’ 字段。

 emit(context)

        發送通知。

class NotificationObject(**kwargs)

    基類: nova.objects.base.NovaObject

    這是每個關聯於支持多版本對象的通知類的基類。

class NotificationPayloadBase

    基類: nova.notifications.objects.base.NotificationObject

    支持多版本的通知中負載類(payload )的基類。

 populate_schema(set_none=True, **kwargs)

        根據 SCHEMA 和源對象填充出對象

  參數: kwargs –包含了在 SCHEMA 中使用 key 值定義的源對象的字典

class NotificationPublisher(host, source)

    基類: nova.notifications.objects.base.NotificationObject

 notification_sample(sample)

    這是一個給通知類加上示例信息的類裝飾器,主要爲生成文檔服務。

參數列表: sample –nova版本庫中 doc/notification_samples/ 目錄下 json文件的路徑。

請注意,通知對象不要註冊到 NovaObjectRegistry ,以避免將通知對象和 nova 內部的對象混淆。相應的解決方法是在每個具體的通知對象使用 register_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(), 
    }

接下來可以使用下面的示例代碼來講通知消息填充和發送:

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)

上面的示例代碼將會產生下面格式的通知消息:

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

通過在payload類中增加一個定義了已存在對象的字段和新payload對象的字段映射關係的 SCHEMA 字段,可以實現重用已存在的versionedobject 。舉個例子, service.status 這個通知在定義自己的負載類時重用了 nova.objects.service.Service 對象:

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

如果定義了 SCHEMA 字段, 那麼 payload 必須先使用 populate_schema 調用填充後才能發送:

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)

 SCHEMA 中的每一個項目遵循以下語法:

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

SCHEMA 中定義的字段擁有以下語義。 當調用了 populate_schema 方法時,  SCHEMA 的文本內容將被枚舉並且將指定參數對象的字段的值拷貝至請求的 payload 對象對應字段中。所以上述示例中, payload 對象的 host 字段通過調用 populate_schema  被填充爲 service 對象的 host 字段的值。

一個通知的 payload 可以重用多個已存在對象的字段值。通知也可以同時使用新定義的字段和重用字段。

注意,可以使用2種不同的方式來創建通知的發佈者實例。可以通過帶 hostbinary 字符串參數實例化 NotificationPublisher 對象,也可以通過通過調用 NotificationPublisher.from_service_obj 方法直接生成一個 Service 對象。

支持多版本的通知需要在 doc/sample_notifications 目錄下維護一個示例文件, 並且該通知類需要使用 notification_sample 裝飾器裝飾。比如, service.update 的示例文件保存在 doc/sample_notifications/service-update.json ,也相應地使用裝飾器裝飾了 ServiceUpdateNotification 類。

在 Nova 代碼中通知的負載 (payload)類可以使用繼承來避免多次定義相同片段的 payload 。但是,在通知中必須小心的創建直接使用的葉類,避免將來因爲葉類的名字已經存在而導致不得不需要增加額外的繼承等級來修改類名的問題。如果實在無法避免並且僅僅需要改變類名的話,那麼新 payload 的版本號應該和沒被改名前的版本保持一致。參考https://review.openstack.org/#/c/463001/ 。如果改變除了改名以外包含任意其他改動(比如 增加新字段) ,那麼新 payload 的版本號應該比沒被改名前的版本號要高,示例請參考 https://review.openstack.org/#/c/453077/

通知的負載中應該包含哪些內容?

以下僅僅列出指導性意見,你必須結合實際的應用場景來考慮。

  • 每個條目的唯一索引號(e.g. uuid) , 唯一索引可以被用來使用 REST API 來查詢該條目,是的消息則可以獲取更多該條目的信息。
  • 考慮包含那些促使你發送通知的事件相關的一些字段。舉個例子,比如一個自斷髮生改變觸發你發送一個更新的通知,那麼你應該在通知的負載中包含這個字段。
  • 一個更新通知應該包含那些條目中發生變化的信息。可以通過填充負載中的 nova_object.changes 部分(注意,在通知框架中暫時還不支持這麼做),也可以在負載中同時發送該條目的舊狀態和新狀態。
  • 永遠不要在 payload 中包含一個 Nova 內部使用的類。你應該新建一個類,並且使用 SCHEMA 和字段映射機制。這種改進的方式可以讓通知中負載類的演進與 Nova 中內部類的演進相解耦。
  • 一個刪除通知應該包含和創建通知或者更新通知相同的字段信息。這使得消費者可以在保持對某些字段過濾 (e.g. project_id)的情況下依然可以接收到刪除通知。

當前已存在的通知

請參考英文原文的列表,此處不一一列舉。

本文翻譯自英文原文:https://docs.openstack.org/nova/latest/reference/notifications.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章