Allocation API

https://storyboard.openstack.org/#!/story/2004341

此规范提出创建用于部署节点分配的 API。

问题描述

独立 ironic 用户没有现成的手段来找到适合部署的节点。 metalsmith 项目是为了短期内填补这一空白而创建的,但它不适合不是用 Python 编写的消费者代码。一个潜在的消费者是用于独立 ironic 的 K8S 提供者。

API 用户故事如下

给定一个资源类,以及可选的所需特性列表,返回一个可用的裸机节点,并设置其 instance_uuid 以将其标记为已保留。

提议的变更

概述

此 RFE 提出一个新的 ReST API 端点 /v1/allocations,该端点最初将允许创建和删除 Allocation 资源。

计划了两种分配流程的实现方式

  1. 通过数据库,类似于 metalsmith 目前的操作方式。

  2. 通过 Placement 服务,类似于 nova 目前的操作方式。

此规范侧重于 API 设计和第一个(独立)用例。

分配流程

分配过程如下

  1. API 客户端发送一个 POST /v1/allocations 请求,指定一个资源类,以及可选的特性和节点 UUID。

  2. 分配请求路由到一个随机可用的 conductor。

  3. conductor 在数据库中创建一个 Allocation 对象,其 state=allocatingconductor_affinity=<host name>

  4. 为剩余的分配过程生成一个线程,并将分配对象返回给调用者。

分配:数据库后端

以下操作由处理分配的 conductor 执行,当使用数据库作为后端时(此规范中的唯一选项)

  1. 从数据库中获取节点列表,条件为

    • provision_state=available

    • maintenance=False

    • power_state!=None

      注意

      这与旧的 API 版本兼容,这些版本允许直接在 available 状态下创建节点。

    • instance_uuid=None

    • resource_class=<请求的 资源 类>

    • uuid 在候选节点列表中(如果提供)。

    • 请求的特性是节点特性的超集。

  2. 如果列表为空,则将分配的 state 设置为 error,并将 last_error 设置为解释说明。

  3. 打乱列表,以便多个进程不会尝试以相同的顺序保留节点。

  4. 获取第一个节点的锁。如果锁定成功,则检查关于此节点的假设是否仍然有效,并通过设置其 instance_uuid 为分配的 uuid 来保留它。在同一个数据库事务中

    • 将分配的 node_id 设置为节点的 ID,

    • 将分配的 state 设置为 active

    • 将节点的 allocation_id 设置为分配的 ID,

    • 将匹配的特性添加到节点的 instance_info

    注意

    由于 conductor 可能没有所选节点的硬件类型,我们将更新 TaskManager 以避免构造 driver 对象。

  5. 如果在上一步中发生故障,则继续到下一个节点。如果没有剩余节点,则将分配的 state 设置为 error,并将 last_error 设置为解释说明。

反分配:数据库后端

反分配过程将在一个事务中

  • 取消设置节点的 instance_uuid

  • 取消设置节点的 allocation_id

  • 删除分配。

反分配是通过 API 显式触发的,或者当节点被拆除时(同时清除节点的 instance_uuidinstance_info 时)。

注意

将来我们可能会考虑支持持久分配,这些分配在节点拆除后仍然存在。这超出此规范的范围。

使用 instance_uuid 和使用分配 API 之间存在一个重要区别:instance_uuid 可以为 active 节点设置和取消设置,而对于分配则禁止这样做。原因是,使用未来的 Placement 后端,删除分配将在 Placement 中将节点标记为可用。

HA 和接管

  • 当 conductor 重新启动时,它会获取具有以下条件的分配

    • conductor_affinity=<host name>

    • state=allocating

    并为每个分配启动分配过程。

  • 如果处理分配的 conductor 在没有替换的情况下停止,则保留将变为孤立。所有 conductor 定期获取属于死 conductor 的分配列表,并尝试恢复它们。

    首先,它会尝试通过执行类似以下操作来更新 conductor_affinity

    UPDATE allocations SET conductor_affinity=<new host name>
       WHERE id=<allocation ID> AND conductor_affinity=<dead host name>
    

    如果查询更新了 1 行,我们知道新的 conductor 现在管理分配。否则,我们知道另一个 conductor 已经接管了它。

  • 为了避免这种接管过程中的罕见竞争,正常的更新也将如下所示

    UPDATE allocations SET <new values>
        WHERE id=<allocation ID> AND conductor_affinity=<current host name>
    

备选方案

  • 让每个消费者发明自己的分配过程或使用 metalsmith

  • 编写一个新的服务来管理保留(可能基于 metalsmith 代码库)。

  • 使 API 阻塞并避免为分配设置状态。这种方法将导致更简单的 API 和实现,但当使用外部系统(例如 Placement)作为后端时,可能会出现问题,因为对它的请求会阻塞 RPC 线程。

    此外,异步设计将使将来引入批量分配 API 更加容易,如果我们决定这样做的话。

数据模型影响

引入一个新的数据库/RPC 对象 Allocation,具有以下字段

  • id 内部整数 ID,未暴露给用户。

  • uuid 分配的唯一 UUID。

  • name 分配的唯一名称,遵循与节点名称相同的格式。

    注意

    此字段对于使用主机名的系统(例如 metalsmith)很有用。

  • node_id 保留的节点 ID(可以为 null)- 外键到 nodes 表。

  • updated_at/created_at 标准更新/创建日期时间字段。

  • resource_class 必需的请求资源类。

  • candidate_nodes 可供选择的节点 UUID 列表(可以为 null)。

    注意

    这允许调用者通过任意标准预先过滤节点。

  • state 分配状态,请参阅 状态机影响

  • last_error 上次错误消息。

  • conductor_affinity 内部字段,指定当前处理此分配的 conductor。

引入一个辅助表 allocation_traits,用于映射分配到其请求的特性(与 node_traits 类似)。

使用新的外键 allocation_id 更新 nodes 表。它将被设置为相应的分配的 ID。与 instance_uuid 不同,它仅在创建分配时设置。如果 allocation_id 不为空,instance_uuid 将包含相应分配的 UUID(反之不一定为真)。

状态机影响

对节点状态机没有影响。

此 RFE 为 Allocation 对象引入了一个简单的状态机,包含三个状态

  • allocating 分配正在进行中(初始状态)。

  • active 分配处于活动状态。

  • error 分配失败。

在初始版本中,只有以下路径是可能的

  • allocatingactive 成功时。

  • allocatingerror 失败时。

将来我们可能会允许从 error 返回到 allocating 以重试分配。

REST API 影响

  • POST /v1/allocations 创建分配。

    API 接受一个 JSON 对象。以下字段是必需的

    • resource_class 请求的节点资源类。

    以下字段是可以接受的

    • uuid 创建具有特定 UUID 的分配。

    • candidate_nodes 将查询限制为这些节点之一。

      注意

      此值在内部从名称转换为 UUID。

    • traits 请求的特性列表。

    • name 分配名称。

    正常响应是 HTTP CREATED,响应体是创建的分配表示形式。分配在 allocating 状态下创建。

    错误代码

    • 400 Bad Request 如果

      • 任何来自 candidate_nodes 的节点都无法找到(按名称或 UUID)。

      • resource_class 值无效。

      • traits 已提供且不是有效的特性字符串列表。

      • name 不是可接受的名称。

    • 406 Conflict 如果

      • 提供的 uuid 不唯一或与任何节点的 instance_uuid 匹配。

      • 提供的 name 不唯一。

  • GET /v1/allocations 列出分配。

    参数

    • fields 为每个分配检索的字段列表。

    • state 按状态过滤分配。

    • resource_class 按资源类过滤分配。

    • node 按节点 UUID 或名称过滤分配。

    错误代码

    • 400 Bad Request 如果

      • state 无效。

      • resource_class 无效。

      • node 不存在。

      • 请求的任何字段无效。

  • GET /v1/allocations/<uuid name> 获取分配。

    参数

    • fields 要检索的字段列表。

    错误代码

    • 400 Bad Request 如果请求的任何字段无效。

    • 404 Not Found 如果未找到分配。

  • DELETE /v1/allocations/<uuid name> 删除分配并释放节点。

    没有请求或响应体。响应代码是 HTTP 204 No Content。

    错误代码

    • 404 Not Found 如果未找到分配。

    • 409 Conflict 如果相应的节点是 active 或处于不允许更新的状态。

    注意

    此请求仅对真实的分配有效。将无法使用此 API 删除通过直接 PATCH 到节点创建的 instance_uuid

  • GET /v1/nodes/<node UUID name>/allocation 获取与节点关联的分配。

    参数

    • fields 要检索的字段列表。

    响应体是 Allocation 对象表示形式。

    错误代码

    • 404 Not Found 如果

      • 找不到节点。

      • 节点没有分配。

    • 400 Bad Request 如果

      • 节点具有与任何分配不对应的 instance_uuid

      • 请求的任何字段无效。

  • 更新 GET /v1/nodesGET /v1/nodes/detailGET /v1/nodes/<node UUID name

    暴露新的 allocation_uuid 字段(从节点的 allocation_id 转换而来)。

  • 更新 PATCH /v1/nodes/<node UUID name>

    如果 instance_uuid 未设置,并且当前值对应于一个分配

    • 如果节点是 active 状态或处于不允许更新的状态,并且 maintenance 模式关闭,则返回 HTTP 409 Conflict,

    • 否则删除分配。

    如果 instance_uuid 已设置,则不要创建分配,保持之前的行为。

    注意

    这是为了避免影响 nova virt 驱动程序。此决定可能在未来的 API 版本中重新评估。

    allocation_uuid 字段是只读的,尝试直接更改它将导致 HTTP 400(错误请求)。

  • 更新 DELETE /v1/nodes/<node UUID name>

    如果节点在维护模式下被删除并具有分配,则分配也会被删除。

客户端 (CLI) 影响

“ironic” CLI

无。

“openstack baremetal” CLI

将创建匹配的命令

openstack baremetal allocation create --resource-class <class> \
   [--trait <trait>] [--trait <trait>] [--uuid <uuid>] [--name <name>]
openstack baremetal allocation list [--state <state>] [--fields <fields>]
   [--resource-class <class>] [--node <UUID or name>]
openstack baremetal allocation get <uuid or name> [--fields <fields>]
openstack baremetal allocation delete <uuid or name>

allocation_uuid 字段将被暴露。

RPC API 影响

引入了两个新的 RPC 调用

  • 创建分配

    def create_allocation(self, context, allocation):
       """Create an allocation.
    
       Creates the provided allocation in the database, then starts a thread
       to process it.
    
       :param context: context
       :param allocation: allocation object.
       """
    
  • 删除分配

    def destroy_allocation(self, context, allocation):
       """Destroy an allocation.
    
       Removes the allocation from the database and releases the node.
    
       :param context: context
       :param allocation: allocation object.
       """
    

metalsmith 影响

metalsmith 项目在客户端实现了所提议功能的超集。引入此 API 后,metalsmith 将以以下方式切换 reserve_node 调用来使用它

  • 如果请求包含 resource_class,以及可选的 traits 和候选节点,则将使用新的 API。

  • 如果请求包含新 API 不支持的内容,metalsmith 将继续进行客户端节点过滤,并使用合适的节点列表创建分配。

驱动程序 API 影响

Nova 驱动程序影响

未来我们可能会在 nova 驱动程序中使用分配 API,但目前没有计划。当前通过分配 API 将导致在 Placement 中进行双重分配,如果 Placement 被用作分配后端。

Ramdisk 影响

安全影响

其他最终用户影响

可扩展性影响

性能影响

每个 conductor 将运行一个新的周期性任务,以定期检查属于死 conductor 的停滞分配。默认周期将为 60 秒。可以禁用它,在这种情况下,如果分配的 conductor 死亡,分配可能会永远卡住。

其他部署者影响

开发人员影响

实现

负责人

主要负责人

dtantsur

工作项

  • 添加新的表和 Allocation RPC 对象。

  • 添加用于分配/取消分配的 RPC。

  • 添加用于创建和删除分配的 API,以及 API 参考。

  • 更新 conductor 启动过程,以检查未完成的分配。

  • 添加一个周期性任务来检查孤立的未完成分配。

依赖项

测试

  • 将提供单元测试和 Tempest API。

  • 将更新独立的集成测试以使用新的 API。

  • 我们可以将对新 API 的支持添加到 bifrost(例如通过 metalsmith),并在 bifrost CI 作业中对其进行测试。

升级和向后兼容性

此更改完全向后兼容。使用 instance_uuid 进行分配的代码不受影响。

文档影响

将提供 API 参考。

参考资料