从 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)吗?
逐字复制来自 Inspector 的检查规则,不删除,不添加。 我认为没有理由对长期维护进行小的改进。 其中一些添加与安全性相关。
将检查规则变成与 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 补丁的意义上)设置为该值。
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 补丁的意义上的路径。
示例¶
部分摘自 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"]
状态机影响¶
无(规则正在 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。
如果输入无效,则返回 HTTP 400,例如尝试修改
built_in,将sensitive更改为false或将优先级设置在允许的范围之外(0 到 9999)。DELETE /v1/inspection_rules/<uuid>删除一条规则。
如果未找到规则,则返回 HTTP 404。
如果规则是内置的,则返回 HTTP 400。
DELETE /v1/inspection_rules删除所有规则,除了内置规则。
客户端 (CLI) 影响¶
“openstack baremetal” CLI¶
检查规则 CRUD,改编自 内省规则 CLI,只需将 内省 替换为 检查。
$ 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一个正则表达式,用于匹配运行检查规则的 检查接口。默认值为
^(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 迁移,并包含一些实际的 示例。