Oslo/blueprints/notification-structured
Oslo 通知的现状
目前,Oslo 提供了通过 openstack.common.notifier 模块发送通知的代码。它的子模块 openstack.common.notifier.api 允许应用程序使用 Python API 发送通知。通知通过 openstack.common.notifier 中包含的一组驱动程序发送,例如 log 或 rpc。
主要的通知函数原型是 openstack.common.notifier.api.notify(context, publisher_id, event_type, priority, payload)。
问题
目前 notify() 参数包含的结构化信息非常少。payload 参数可以是任何内容,通常是一个 Python 字典,包含各种各样的数据,其字段无法提前知道。
其中一些字段在 一个 wiki 页面 上有记录,但显然有很多缺点
- 列表很容易过时;
- 列表甚至不完整;
- 列表无法帮助任何形式的代码生成、内省和围绕事件内容的自动测试。
在 Ceilometer 中,OpenStack 中事件的主要消费者,我们长期以来一直处理所有这些问题。我们的单元测试试图通过包含我们曾经见过的 payload 副本来模拟其他 OpenStack 组件发送的内容。一旦 OpenStack 组件中的 payload 格式发生更改,我们的测试仍然会通过,但代码将无法处理在实际部署中发送的事件。
拥有非结构化事件也意味着在存储方面存在很多问题。存储非结构化事件依赖于存储基于 EAV 的数据。虽然这在某些数据库存储系统中(NoSQL)不是问题,但对于其他系统(SQL)来说是一个可怕的问题,并且在性能方面(例如,索引、查询)在两种情况下都可能是一个敏感问题。
建议的解决方案
建议的解决方案是构建一个新的 Python API,使用不同的原型,该原型依赖于 Python 对象而不是字典。这将解决所有上述问题。它可以与当前 API 并存,以允许轻松过渡到新的通知机制。
第一个类对象将是类似于 openstack.common.notifier.Notifier 的对象,并且能够发送事件。event 将是一个已知的结构化 Python 对象。稍后它可以转换为简单的字典,然后发送到驱动程序,然后驱动程序将在网络上传播它。
# This is peudo code written in the wiki, I didn't run it
class Event(object):
user_id = str
project_id = str
def __init__(self, **kwargs):
fields = set(filter(lambda x: not x.startswith('_'), self.__dict__.keys()))
if set(kwargs.keys()) != fields:
raise Exception("Too much or missing fields given in this notification") # should also indicate what
for attr in fields:
setattr(self, getattr(self.__class__, attr)(kwargs[attr]))
def as_dict(self):
return self.__dict__ # + filter out private fields, or something like that
@property
def event(self):
"""Return the event type by converting the class name from CamelCase to dotted notation."""
return re.sub('([A-Z]+)', r'.\1',self.__class__.__name__).lower().strip('.')
class ResourceCreate(Event):
resource_type = str
class InstanceCreate(ResourceCreate):
instance_id = str
instance_type_id = str
display_name = str
created_at = datetime.datetime
launched_at = datetime.datetime
image_ref_url = URL # I guess we could build a URL class :)
state = str
memory_mb = int
def __init__(self, **kwargs):
kwargs['resource_type'] = 'instance'
super(self, InstanceCreate).__init__(**kwargs)
class Notifier(object):
def __init__(self, application, host=socket.gethostname(), driver=get_the_default_driver()):
self.application = application
self.host = host
self.driver = driver
def __call__(self, context, priority, event):
self.driver.notify(context, priority, application, host, event.type, event.as_dict())
有了这样的设计,应用程序可以通过内省模块本身轻松地内省事件列表及其字段。这可以被用来自动构建 SQL 模式,例如,或验证代码(单元测试)。驱动程序将负责通知的序列化和反序列化。(请注意,目前没有反序列化 API,因为没有用于消费通知的高级 API;这也可以在此蓝图中解决,即使它与此蓝图没有直接关系)。
使用 Python 类还可以确保,如果 Oslo 中的任何部分(例如,事件定义)更新,在 Nova 中更新 Oslo 将会破坏 Nova 中的代码,并且单元测试会很容易地显示出来,因此可以轻松修复。同样的情况也适用于 Ceilometer 等消费者,他们将不得不适应处理新的事件。
为属性指定类型对于
- 验证给定的数据是否正确
- 通过可能的内省构建正确的存储模式
这受到了 WSME 自动生成 REST API 的方法启发。像 voluptuous 这样的库可能在模式验证和定义方面得到很大的利用,因此无需重新发明轮子。
Notifier 对象获得更多的第一类参数,例如发出事件的应用程序。这对于拥有良好的过滤可能性来说非常重要。
事件的定义位置仍有待讨论。这可以在 Oslo 中完成,以避免从 Nova 和所有项目中导入噩梦。
- 我喜欢将事件变成对象这个想法。
- 我真正喜欢这个提议的是,它不需要破坏现有的通知结构。相反,它只是更好地捕获它的一种方式。
- 一些问题
- 我们需要通知的版本控制。CreateInstance 对象如何区分随时间的变化?CreateInstance1、CreateInstance2 等?
- 最好让这些通知对象具有上下文处理程序,以便它也可以处理 .start/.end 的生成。应该很容易。
- 通知当前发布到 RPC --control_exchange(绑定到名为路由键的队列,"notifications.info"、"notifications.error" 等)。通知 API 可能需要扩展以允许单独的路由键和交换名称。(我知道这有点超出此页面的范围)
- 最好标记属性为用户可见或操作员可见(如 to_dict() 中所暗示)。关于支持它的建议?
- 小瑕疵:覆盖 __call__ 会使代码变得混乱,恕我直言 :)
- -Sandy
- 这看起来很棒,特别是如果我们能让它序列化和反序列化以完成循环。
- 当我们构建 Event 对象时,我们是否期望它包含特定的属性?仅从审计的角度来看,最小的数据要求是回答 7W。
- 是否有可能有一种选项/可插拔的方式来以特定的模型格式序列化/反序列化 Event 模型?(例如,如果有人想使用开放标准并将事件表示为某种模型而不是半结构化字典,他们可以选择这样做,并且可以使用相同的模型由消费者反序列化。)
- 这里有一个 pdf 文件,其中包含开放标准 DMTF CADF 规范,该规范强调了一些典型的审计要求以及我们当前如何使用它。特别关注幻灯片 28,OpenStack api 请求如何映射到 CADF。 File:Introduction to Cloud Auditing using CADF Event Model and Taxonomy 2013-10-22.pdf
- gordc