从 Inspector 迁移检查规则¶
https://storyboard.openstack.org/#!/story/2010275
本规范完成了在 /approved/merge-inspector 中启动的工作,即迁移检查规则 API。
请参阅 术语表,了解理解本规范所需的概念。
问题描述¶
检查是一个相当有主见的流程。 某些领域通常需要特定站点的定制
自动发现。 例如,可以为遵循特定模式的新节点填充凭据,或者从 CMDB 中获取凭据。
验证逻辑。 一些操作员希望如果节点不满足某些条件,则使检查失败。 在自动发现的上下文中,可以使用这种验证来防止意外机器被注册。
这些请求可以通过检查钩子来覆盖。 但是,如 替代方案 中所述,编写和部署钩子可能过于不灵活。
提议的变更¶
迁移来自 Inspector 的简化版 内省规则。
在 Inspector 提供的基础上添加了这些有用的功能
- 内置规则
允许操作员从 YAML 文件加载规则。 这些规则将始终存在,不会存储在数据库中,也不会被删除。 这些规则既更容易编写钩子,又是 Inspector 笨拙的配置选项(例如
[discovery]enabled_bmc_address_version)的替代品。- 阶段
在 Inspector 中,规则始终在处理结束时运行。 我们将添加一个新的字段
phase到规则中,其值为early- 在任何其他处理之前运行,甚至在自动发现和查找之前。 这样的规则将无法访问节点对象。preprocess- 在所有检查钩子的preprocess阶段之后运行,但在主阶段之前运行。main(默认)- 在所有检查钩子之后运行。
- 更新规则
Inspector 不提供更新规则的 API。 这没有理由,我们将为它们添加
PATCH支持。- 敏感规则
规则的条件和操作可能包含敏感信息,例如 BMC 信息。 如果规则被标记为敏感,则其操作和条件将不会作为
GET请求响应的一部分返回。 不可能使敏感规则变为不敏感。运行敏感规则产生的错误消息也将比较简短,以避免意外泄露敏感信息。
- 优先级
在 Inspector 中,规则始终按照创建顺序运行。 这显然不方便,因此在 Ironic 中,我们将为它们添加优先级。 所有规则都可以使用 0 到 9999 之间的优先级,负值和高于 10000 的值保留给内置规则。 默认优先级为 0。 具有相同优先级的规则仍然按照创建顺序运行,以保持兼容性。
- 数据库存储
当前,Inspector 将每个规则拆分为三个表(规则、条件和操作)。 这可能从数据库设计角度来看更正确,但实际上使用起来不方便,因为条件和操作永远不会在规则上下文之外访问。 规则也永远不会在没有其条件和操作的情况下被访问。 本规范将它们作为 JSON 字段放在规则表中。
- 条件和操作的一致参数
条件有一个
field属性,该属性被特殊处理为节点或清单的字段。 本规范将其更改为具有一个操作和多个参数的结构,请参阅 数据模型影响。
备选方案¶
使用检查钩子机制。 钩子不太灵活,因为它们需要将 Python 代码安装在 Ironic 旁边,并且每次更改服务都需要重新启动。 后者对于基于容器的部署尤其成问题。
彻底更改规则 DSL 为不太笨拙的东西,例如类似 Ansible 的 miniscript。 虽然我最终仍然想这样做,但我认为这项工作会过度增加已经很大的工作范围。 考虑到 API 版本控制,我们始终可以在底层更改语言。
允许 API 用户上传 Python 代码。 没评论。
说真的,用 Lua 或任何其他“成熟”的嵌入式语言编写规则。 我还没有充分研究这个选项。 也许这是前进的方向? 部署者会介意一个新的 C 依赖项(在 liblua 或 LuaJIT 上)吗?
逐字复制检查规则,不删除,不添加。 我认为没有理由为了更好的长期维护进行小的改进。 其中一些添加与安全性相关。
将检查规则变成与 Ironic 分开的服务。 违背了 Inspector 合并的目的。 例如,无法访问节点数据库意味着效率较低的操作。
根本不要迁移检查规则。 它们确实带来了一些复杂性,但也证明了对操作非常有用的工具。 CERN 使用它们,这是我衡量高级操作员有用性的基准。
数据模型影响¶
改编自 Inspector,并添加了 提议的更改 中描述的补充。
class Rule(Base):
uuid = Column(String(36), primary_key=True)
created_at = Column(DateTime, nullable=False)
updated_at = Column(DateTime, nullable=True)
priority = Column(Integer, default=0)
description = Column(String(255), nullable=True)
scope = Column(String(255), nullable=True) # indexed
sensitive = Column(Boolean, default=False)
phase = Column(String(16), nullable=True) # indexed
conditions = Column(db_types.JsonEncodedList(mysql_as_long=True))
actions = Column(db_types.JsonEncodedList(mysql_as_long=True))
条件和操作¶
在本规范中,条件和操作都具有相同的基本结构
op- 操作:布尔值(条件)或操作(操作)。args- 列表(Python*args的意义)或字典(Python**kwargs的意义)的参数。
Inspector 中的操作的特殊属性获得不同的形式
代替
invert:在op前面加上一个感叹号(可选空格),例如eq-!eq。代替仅
multiple,支持类似 Ansible 的loop字段。 在操作上,将运行多个操作。 在条件上,multiple字段定义了如何连接结果。 与 Inspector 相同- any(默认)
需要任何匹配
- all
需要全部匹配
- first
有效地,在第一次迭代后短路循环
- last
有效地,仅运行循环的最后一次迭代。
变量插值¶
字符串参数由 Python 格式化处理,可用对象为 node、ports、port_groups、inventory 和 plugin_data,例如 {node.driver_info[ipmi_address]}、{inventory[interfaces][0][mac_address]}。
在早期阶段运行时,仅可用 inventory 和 plugin_data。
node 实际上是一个代理映射,它考虑了 mask_secrets 选项(如 其他部署者影响 中所述)。
如果值是单引号大括号 { 和 } 包围的字符串(没有未格式化的文本),我们将评估内部内容并避免将其转换为字符串。 这样可以将列表和字典传递给操作和 loop。 此行为可能会通过挂接到 Formatter 类来实现。
可用条件¶
与 Inspector 不同,将构建一个条件列表到 Ironic 中
is-true(value)检查值是否评估为布尔 True。 除了实际的布尔值外,非零数字和字符串“yes”、“true”(任何大小写)都评估为 True。
is-false(value)检查值是否评估为布尔 False。 除了实际的布尔值外,零
None和字符串“no”、“false”(任何大小写)都评估为 False。
注意
对于某些值,这两个条件都可能为 false(例如,随机字符串)。 这是故意的。
is-none(value)检查值是否为 None。
is-empty(value)检查值是否为 None 或空字符串、列表或字典。
eq/lt/gt(*values, *, force_strings=False)检查所有值是否相等/小于/大于。 如果
force_strings,所有值将首先转换为字符串。注意
Inspector 有
ne、le和ge,可以通过!eq、!gt和!lt来实现。in-net(address, subnet)检查给定的地址是否在提供的子网中。
contains(value, regex)检查值是否包含给定的正则表达式。
matches(value, regex)检查值是否完全匹配给定的正则表达式。
one-of(value, values)检查值是否在提供的列表中。 类似于
contains,但也适用于非字符串值。 等效于- op: eq args: [<value>, "{item}"] loop: <values>
可用操作¶
与 Inspector 类似,操作将来自入口点 ironic.inspection_rules.actions 的插件。 随 Ironic 一起提供的有
fail(msg)使用给定的消息使检查失败。
set-plugin-data(path, value)在插件数据中设置一个值。
extend-plugin-data(path, value, *, unique=False)将插件数据中的一个值视为一个列表,追加到它。 如果
unique为 True,则如果该项目存在,则不要追加。unset-plugin-data(path)取消设置插件数据中的一个值。
log(msg, level="info")将消息写入 Ironic 日志。
以下操作在 early 阶段不可用
set-attribute(path, value)将给定的路径(在 Ironic API 使用的 JSON patch 的意义上)设置为该值。
extend-attribute(path, value, *, unique=False)将给定的路径视为一个列表,追加到它。
del-attribute(path)取消设置给定的路径。 在无效的节点属性上失败,但在缺少子字典字段时不失败。
set-port-attribute(port_id, path, value)设置由 MAC 或 UUID 标识的端口上的值。
extend-port-attribute(port_id, path, value, *, unique=False)将给定的端口上的路径视为一个列表,追加到它。
del-port-attribute(port_id, path)取消设置由 MAC 或 UUID 标识的端口上的值。
注意
这里 *path* 是 Ironic API 使用的 JSON patch 的意义上的路径。
示例¶
部分摘自 Inspector 文档,使用 YAML 格式。
- description: Initialize freshly discovered nodes
sensitive: true
conditions:
- op: is-true
args: ["{node.auto_discovered}"]
- op: "!is-empty"
args: ["{plugin_data[bmc_address}"]
actions:
- op: set-attribute
args: ["/driver", "ipmi"]
- op: set-attribute
args: ["/driver_info/ipmi_address", "{plugin_data[bmc_address]}"]
- op: set-attribute
args: ["/driver_info/ipmi_username", "admin"]
- op: set-attribute
args: ["/driver_info/ipmi_password", "pa$$w0rd"]
注意
plugin_data[bmc_address] 字段是 validate_interfaces 钩子的副作用。
- description: Initialize Dell nodes using IPv6
sensitive: true
conditions:
- op: is-true
args: ["{node.auto_discovered}"]
- op: contains
args: ["{inventory[system_vendor][manufacturer]}", "(?i)dell"]
actions:
- op: set-attribute
args: ["/driver", "idrac"]
- op: set-attribute
args: ["/driver_info/redfish_address", "https://{inventory[bmc_v6address]}"]
- op: set-attribute
args: ["/driver_info/redfish_username", "root"]
- op: set-attribute
args: ["/driver_info/redfish_password", "calvin"]
状态机影响¶
None(规则正在 INSPECTING 状态下运行)
REST API 影响¶
迁移 API 大部分逐字,将前缀更改为 inspection_rules,添加 PATCH 和更多列表选项
POST /v1/inspection_rules创建检查规则。请求体是规则的表示形式。除了
built_in字段外,所有字段都可以在创建时设置。只有actions是必需的(条件为空的规则将无条件运行)。对于无效输入,返回 HTTP 400 错误。
GET /v1/inspection_rules/<uuid>返回一个检查规则。输出字段大多重复数据库字段,并添加一个布尔字段
built_in。对于敏感规则,将返回
null而不是conditions和actions。如果未找到规则,则返回 HTTP 404 错误。
GET /v1/inspection_rules[?detail=true/false&scope=...&phase=...]列出所有检查规则。如果
detail为false或未指定,则不返回条件和动作。可以按范围和阶段进行过滤。对于无效输入,返回 HTTP 400 错误。
PATCH /v1/inspection_rules/<uuid>更新一个规则并返回它。可以更新敏感规则,但结果在任何情况下都不包含条件或动作。
如果未找到规则,则返回 HTTP 404 错误。
如果输入无效,例如尝试修改
built_in,将sensitive更改为false或将优先级设置为允许范围之外(0 到 9999),则返回 HTTP 400 错误。DELETE /v1/inspection_rules/<uuid>删除一个规则。
如果未找到规则,则返回 HTTP 404 错误。
如果规则是内置规则,则返回 HTTP 400 错误。
DELETE /v1/inspection_rules删除所有规则,但保留内置规则。
客户端 (CLI) 影响¶
“openstack baremetal” CLI¶
检查规则的 CRUD 操作,改编自 Introspection Rules CLI,只需将 introspection 替换为 inspection
$ openstack baremetal inspection rule import <file>
$ openstack baremetal inspection rule list [--long]
$ openstack baremetal inspection rule get <rule ID>
$ openstack baremetal inspection rule delete <rule ID>
为了清晰起见,更改了批量删除命令。
$ # Inspector version:
$ openstack baremetal introspection rule purge
$ # New version:
$ openstack baremetal inspection rule delete --all
将支持更新操作。
$ openstack baremetal inspection rule set <rule ID> \
[--actions '<JSON>'] [--conditions '<JSON>'] \
[--sensitive] [--scope '<scope>'] [--phase 'early|preprocess|main'] \
[--uuid '<uuid>'] [--description '<description>']
$ openstack baremetal inspection rule unset <rule ID> \
[--conditions] [--scope] [--description]
还添加了一种从字段而不是单个 JSON 创建规则的方法。
$ openstack baremetal inspection rule create \
--actions '<JSON>' [--conditions '<JSON>'] \
[--sensitive] [--scope '<scope>'] [--phase 'early|preprocess|main'] \
[--uuid '<uuid>'] [--description '<description>']
“openstacksdk”¶
baremetal 模块将使用标准的 CRUD 加上批量删除进行更新。
def inspection_rules(details=False): pass
def get_inspection_rule(rule): pass
def patch_inspection_rule(rule, patch): pass
def update_inspection_rule(rule, **fields): pass
def delete_inspection_rule(rule, ignore_missing=True):
def delete_all_inspection_rules(): pass
RPC API 影响¶
无
驱动程序 API 影响¶
不会对驱动程序产生影响。操作员可以选择在具有所有检查接口(包括带外接口)的节点上运行检查规则。
Nova 驱动程序影响¶
无
Ramdisk 影响¶
无
安全影响¶
检查规则可以访问所有节点和清单数据。因此,应仅将其限制给管理员。
其他最终用户影响¶
无
可扩展性影响¶
无
性能影响¶
拥有大量的检查规则会使检查过程更长。但它不应影响系统的其余部分。
其他部署者影响¶
新的部分 [inspection_rules] 将具有以下选项
built_in一个可选的路径,指向包含内置检查规则的 YAML 文件。在服务启动时加载,因此无法通过 SIGHUP 进行修改。
default_scope对于未设置此字段的所有规则(不包括内置规则),
scope的默认值。mask_secrets是否在传递给规则的节点信息中屏蔽密钥。
always(默认)- 始终删除像 BMC 密码这样的内容。never- 从不屏蔽任何内容,将完整的节点对象传递给所有规则。sensitive- 允许对标记为sensitive的规则使用密钥。
supported_interfaces一个正则表达式,用于匹配运行检查规则的 inspect 接口。默认值为
^(agent|inspector)$,以将规则限制为仅限带内实现。可以设置为.*以也在所有节点上运行。
将在 [auto_discovery] 部分添加一个选项
inspection_scope通过自动发现注册的节点的检查范围的默认值。简化了使用检查规则定位此类节点。
开发人员影响¶
动作通过在 ironic.inspection_rules.actions 命名空间中的入口点处具有插件提供的。
class InspectionRuleActionBase(metaclass=abc.ABCMeta):
"""Abstract base class for rule action plugins."""
formatted_params = []
"""List of params to be formatted with python format."""
supports_early = False
"""Whether the action is supported in the early phase."""
def call_early(self, rule, *args, **kwargs):
"""Run action in the early phase."""
raise NotImplementedError
@abc.abstractmethod
def __call__(self, task, rule, *args, **kwargs):
"""Run action on successful rule match."""
注意
Inspector 中的接口支持几个额外的验证功能。我希望从方法签名中推导出有效的参数。
实现¶
负责人¶
- 主要负责人
Dmitry Tantsur (IRC: dtantsur, dtantsur@protonmail.com)
- 其他贡献者
待定
工作项¶
请参阅 RFE。
依赖项¶
/approved/merge-inspector
测试¶
添加功能测试,以执行检查规则的 CRUD 操作。
更新带内检查作业,使其具有一个简单的规则,我们可以验证它是否正在运行(例如,它在节点的 extra 中设置了一些内容)。
升级和向后兼容性¶
由于转换可能并不总是简单的(例如,关于变量插值或循环),因此现有的规则将不会自动从 Inspector 迁移到 Ironic。
文档影响¶
将更新 API 参考。
用户指南将从 Inspector 迁移,并包含一些实际的 示例。