版本化通知转换¶
https://blueprints.launchpad.net/nova/+spec/versioned-notification-transformation-newton
在 Mitaka 中,版本化通知的基础设施已经合并 [1]。现在是时候开始将现有的遗留通知转换为新格式了。本规范提出了最初的几次转换。
问题描述¶
nova 的遗留通知接口定义不明确,并且遗留通知定义了一个非常不一致的接口。通知消费者无法轻松地查看 nova 将发送的格式和内容。
用例¶
作为工具开发者,我想消费 nova 通知以实现我的需求。我想知道通知的格式,并且希望有一种方法来检测和跟踪通知格式的变化。
提议的变更¶
让我们首先转换以下通知,因为它们是 nova 代码库中最常见的通知
instance.update 是 nova 中 payload 大小最大的通知,这将为我们提供有关新版本化通知基础设施实用性的良好反馈
instance.delete.* 是 nova 中常见通知模式的一个实例。存在具有 instance.* event_type 的类似通知。它们都通过相同的代码路径,并带有不同的额外 payload 部分。因此,将定义一个通用的 instance action payload,如果 event_type 没有额外的 payload 字段,可以直接使用,或者可以轻松地子类化以添加额外的 payload 字段。此外,有一种通用的模式,即对于给定的 instance action,具有 action.start action.end 和 action.error 通知。新的通知将在这些 event_type 之间尽可能多地共享 payload 类。
instance.delete.end – 与 identity.user.deleted – 类似,由操作员用于在系统释放资源时触发清理和计费活动。因此,它很重要。此外,nova 想要添加具有类似类型的通知 [3],[4],因此创建一个示例也将有助于这些工作。
nova.exceptions.wrap_exception 装饰器会发出具有可变 payload 的遗留通知。通知的 ‘args’ 字段填充了被装饰函数的调用参数,这些参数由 nova.safe_utils.getcallargs 收集。因此,我们无法为每个被装饰函数制定一个完全版本化的通知,因为这是不可行的,所以我们将只发出信息的静态部分,例如模块名称、函数名称、异常类、异常消息。
在转换过程中,我们将为这些通知定义一个对象模型,有关详细信息,请参阅数据模型影响部分。新的通知对象将支持同时发出遗留格式和新的版本化格式,因此所提出的更改是向后兼容的。
备选方案¶
我们可以以不同的顺序开始转换遗留通知。
数据模型影响¶
数据库模式不受影响。
通知对象的分离命名空间¶
目前,我们拥有的每个对象都是 Nova 的私有/内部对象。为通知 payload 定义的对象模型是 Nova 公共接口的一部分。因此,需要将通知模型与现有的对象模型分离,以便开发者在定义在 nova 外部使用的内容时能够清楚地了解,并保证我们不会意外地将内部对象作为公共通知的一部分暴露出来。
为了实现必要的隔离,我们将
我们将已经创建的与通知相关的对象移动到 nova/notifications/objects/ 下的单独目录中,并将新提出的对象也添加到那里。
NotificationBase 和 NotificationPayloadBase 将 OBJ_PROJECT_NAMESPACE 设置为 ‘nova-notification’,因此所有与通知相关的对象都将属于一个单独的 ovo 命名空间。
继续使用 NovaObject 作为通知对象的基类,以保持线格式,但不要将通知对象注册到 NovaObjectRegistry,以避免将 nova 内部对象与通知对象混合。
分离单元测试,以便我们可以测试未注册的对象哈希以维护版本控制。
instance.update 和 instance.delete¶
instance.delete 和 instance.update 通知具有部分公共 payload,因此我们可以创建一些基类,然后根据需要将它们混合在一起。
以下 InstancePayload 类持有公共部分
@base.NovaObjectRegistry.register_if(False)
class InstancePayload(notification.NotificationPayloadBase):
SCHEMA = {
'uuid': ('instance', 'uuid'),
'user_id': ('instance', 'user_id'),
'tenant_id': ('instance', 'project_id'),
'reservation_id': ('instance', 'reservation_id'),
'display_name': ('instance', 'display_name'),
'host_name': ('instance', 'hostname'),
'host': ('instance', 'host'),
'node': ('instance', 'node'),
'os_type': ('instance', 'os_type'),
'architecture': ('instance', 'architecture'),
'cell_name': ('instance', 'cell_name'),
'availability_zone': ('instance', 'availability_zone'),
'instance_type_id': ('instance', 'instance_type_id'),
'memory_mb': ('instance', 'memory_mb'),
'vcpus': ('instance', 'vcpus'),
'root_gb': ('instance', 'root_gb'),
'ephemeral_gb': ('instance', 'ephemeral_gb'),
'kernel_id': ('instance', 'kernel_id'),
'ramdisk_id': ('instance', 'ramdisk_id'),
'created_at': ('instance', 'created_at'),
'launched_at': ('instance', 'launched_at'),
'terminated_at': ('instance', 'terminated_at'),
'deleted_at': ('instance', 'deleted_at'),
'state': ('instance', 'terminated_at'),
'state_description': ('instance', 'task_state'),
'progress': ('instance', 'progress'),
'metadata': ('instance', 'metadata'),
}
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'uuid': fields.UUIDField(),
'user_id': fields.StringField(nullable=True),
'tenant_id': fields.StringField(nullable=True),
'reservation_id': fields.StringField(nullable=True),
'display_name': fields.StringField(nullable=True),
'host_name': fields.StringField(nullable=True),
'host': fields.StringField(nullable=True),
'node': fields.StringField(nullable=True),
'os_type': fields.StringField(nullable=True),
'architecture': fields.StringField(nullable=True),
'cell_name': fields.StringField(nullable=True),
'availability_zone': fields.StringField(nullable=True),
'instance_flavor_id': fields.StringField(nullable=True),
'instance_type_id': fields.IntegerField(nullable=True),
'instance_type': fields.StringField(nullable=True),
'memory_mb': fields.IntegerField(nullable=True),
'vcpus': fields.IntegerField(nullable=True),
'root_gb': fields.IntegerField(nullable=True),
'disk_gb': fields.IntegerField(nullable=True),
'ephemeral_gb': fields.IntegerField(nullable=True),
'image_ref_url': fields.StringField(nullable=True),
'kernel_id': fields.StringField(nullable=True),
'ramdisk_id': fields.StringField(nullable=True),
'image_meta': fields.DictOfStringsField(nullable=True),
'created_at': fields.DateTimeField(nullable=True),
'launched_at': fields.DateTimeField(nullable=True),
'terminated_at': fields.DateTimeField(nullable=True),
'deleted_at': fields.DateTimeField(nullable=True),
'state': fields.StringField(nullable=True),
'state_description': fields.StringField(nullable=True),
'progress': fields.IntegerField(nullable=True),
'ip_addresses': fields.ListOfObjectsField('IpPayload'),
'metadata': fields.DictOfStringsField(),
}
def __init__(self, instance):
super(InstancePayload, self).__init__()
self.populate_schema(instance=instance)
然后这里是 InstanceUpdatePayload,它添加了 instance.update 通知独有的额外字段
@base.NovaObjectRegistry.register_if(False)
class InstanceUpdatePayload(InstancePayload):
# No SCHEMA as all the additional fields are calculated
VERSION = '1.0'
fields = {
'state_update': fields.ObjectField('InstanceStateUpdatePayload'),
'audit_period': fields.ObjectField('AuditPeriodPayload'),
'bandwidth': fields.ListOfObjectsField('BandwidthPayload'),
'old_display_name': fields.StringField(nullable=True)
}
def __init__(self, instance):
super(InstanceUpdatePayload, self).__init__(instance)
然后这里是 InstanceActionPayload,它添加了所有 instance.<action> 通知通用的额外 fault 字段
@base.NovaObjectRegistry.register_if(False)
class InstanceActionPayload(InstancePayload):
# No SCHEMA as all the additional fields are calculated
VERSION = '1.0'
fields = {
'fault': fields.ObjectField('ExceptionPayload', nullable=True),
}
def __init__(self, instance):
super(InstanceActionPayload, self).__init__(instance)
此外,我们的 payload 引用了几个额外的类
@base.NovaObjectRegistry.register_if(False)
class BandwidthPayload(base.NovaObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'network_name': fields.StringField(),
'in_bytes': fields.IntegerField(),
'out_bytes': fields.IntegerField(),
}
@base.NovaObjectRegistry.register_if(False)
class IpPayload(base.NovaObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'label': fields.StringField(),
'vif_mac': fields.StringField(),
'meta': fields.DictOfStringsField(),
'port_uuid': fields.UUIDField(nullable=True),
'version': fields.IntegerField(),
'address': fields.IPAddressField(),
}
@base.NovaObjectRegistry.register_if(False)
class AuditPeriodPayload(base.NovaObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'audit_period_beginning': fields.DateTimeField(nullable=True),
'audit_period_ending': fields.DateTimeField(nullable=True),
}
@base.NovaObjectRegistry.register_if(False)
class InstanceStateUpdatePayload(base.NovaObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'old_state': fields.StringField(nullable=True),
'state': fields.StringField(nullable=True),
'old_task_state': fields.StringField(nullable=True),
'new_task_state': fields.StringField(nullable=True),
}
现在我们可以定义 instance.update 通知类
@notification.notification_sample('instance-update.json')
@base.NovaObjectRegistry.register_if(False)
class InstanceUpdateNotification(notification.NotificationBase):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'payload': fields.ObjectField('InstanceUpdatePayload')
}
然后我们可以定义三个 instance.delete.* 通知
@notification.notification_sample('instance-action.json')
@base.NovaObjectRegistry.register_if(False)
class InstanceActionNotification(notification.NotificationBase):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'payload': fields.ObjectField('InstanceActionPayload')
}
请注意,instance.delete.start 和 instance.delete.end 和 instance.delete.error 的 payload 具有相同的结构,因此可以在模型中使用相同的通用 InstanceActionPayload。这允许从相同的 InstanceActionNotification 类创建这两个通知。
该模型旨在保存与现有遗留通知相同的信息,但是需要进行一些更改
遗留通知中的 ‘progress’ 字段在通知中要么是一个整数,要么是一个空字符串。无法在模型中保留此行为,因此在版本化通知中,‘progress’ 是一个可为空的整数。
在现有的通知中,‘bandwidth’ 字段是一个字典,其中键是网络标签,值是具有两个键值对的字典,用于输入和输出带宽。新模型将其简化为字典列表,其中每个字典具有三个键值对,一个用于标签,两个用于带宽。
审核周期字段位于遗留 instance.update 通知 payload 的根级别,现在它被移动到子对象。
所提出的 IpPayload、InstanceStateUpdatePayload 和 AuditPeriodPayload 类与现有的 nova.object 类是单独的定义。现有的类用于 nova 内部使用,而新的类用于通知 payload 使用。我们不能使用相同的名称来命名这些对象,因为 ovos 仅使用类的不合格名称来验证字段的内容。
nova.exception.wrap_exception¶
nova.exceptions.wrap_exception 装饰器用于在被装饰函数发生异常时发送通知。今天,此通知具有以下结构
{
event_type: <the named of the decorated function>,
publisher_id: <needs to be provided to the decorator via the notifier>,
payload: {
exception: <the exception object>
args: <dict of the call args of the decorated function as gathered
by nova.safe_utils.getcallargs except the ones that has
'_pass' in their names>
}
timestamp: ...
message_id: ...
}
可变的 event_type 使消费这些通知变得非常困难,因此在版本化格式中,我们将定义一个单一的 event_type ‘compute.exception’,并将函数名称添加到 payload 中。
我们可以为它定义以下通知对象
@base.NovaObjectRegistry.register_if(False)
class ExceptionPayload(base.NovaObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'module_name': fields.StringField(),
'function_name': fields.StringField(),
'exception': fields.StringField(),
'exception_message': fields.StringField()
}
@notification.notification_sample('compute-exception.json')
@base.NovaObjectRegistry.register_if(False)
class ExceptionNotification(notification.NotificationBase):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'payload': fields.ObjectField('ExceptionPayload')
}
此外,NotificationPayloadBase 类将扩展两个新的可为空字段 instance_uuid 和 request_id,因为这些是几乎所有 nova 通知(包括 instance action 通知)的通用信息。
REST API 影响¶
无
安全影响¶
无
通知影响¶
转换后的通知将在 ‘notification_format’ 配置选项设置为 ‘both’ 或 ‘versioned’ 时发出新的版本化通知格式。如果配置设置为 ‘unversioned’ 或 ‘both’,则遗留通知将保持不变地发出。
如 versioned-notification-api bp 中实现的那样,版本化通知始终发送到名为 ‘versioned_notifications’ 的不同的 amqp 主题,因此消费者可以通过主题来区分遗留格式和新格式。
其他最终用户影响¶
无
性能影响¶
如果 ‘notification_format’ 设置为 ‘both’,则将以不同的格式发出相同通知的两个实例。
其他部署者影响¶
无
开发人员影响¶
添加新通知发出代码的开发者需要调用新对象模型提供的新的接口。
实现¶
负责人¶
- 主要负责人
balazs-gibizer
工作项¶
对于每个转换后的通知
将现有的通知相关对象移动到单独的命名空间
添加新的对象模型
添加使用新的 Notification 类发出遗留格式的可能性
更改 nova 代码库以调用新的 Notification 类
为新的版本化格式添加通知示例
依赖项¶
无
测试¶
将提供功能测试来执行发出新的版本化通知,并且测试将断言存储的通知示例的有效性。
文档影响¶
将提供通知示例文件。notification.rst [2] 中的版本化通知表会自动更新。
参考资料¶
[1] https://blueprints.launchpad.net/nova/+spec/versioned-notification-api
[2] https://docs.openstack.org/developer/nova/notifications.html
[3] https://blueprints.launchpad.net/openstack/?searchtext=expose-quiesce-unquiesce-api
[4] https://blueprints.launchpad.net/openstack/?searchtext=add-swap-volume-notifications
[5] https://blueprints.launchpad.net/oslo.versionedobjects/+spec/json-schema-for-versioned-object
历史¶
发布名称 |
描述 |
|---|---|
Newton |
引入 |