使用 UUID 在服务和 os-hypervisors API 中

https://blueprints.launchpad.net/nova/+spec/service-hyper-uuid-in-api

目前,我们在计算 REST API 中使用和接收主键 ID 来处理服务和超visor(计算节点)。在多单元部署中,这些 ID 不是唯一的。此规范建议在 REST API 中暴露服务和超visor的 UUID,以唯一标识资源,无论其位于哪个单元中。

问题描述

我们目前从计算 REST API 中泄露数据库 ID 字段(主键)用于服务和 compute_nodes,它们都位于一个单元数据库中(在 cells v2 部署中为“nova”数据库)。它们分别位于 os-servicesos-hypervisors API 中。

例如,要删除服务记录,您必须向 /os-services/{service_id} 发送 DELETE 请求,以删除具有该 ID 的服务记录。

os-hypervisors API 在 GET(索引)请求中暴露 ID,并在“show”和“uptime”方法中使用它通过该 ID查找 ComputeNode 对象。

这在单单元部署中虽然丑陋但功能正常。但是,在多单元部署中,我们没有上下文来查询应该从哪个单元获取服务/节点详细信息,因为您可能在每个单元中都有一个 nova-compute 服务和 ID 为 1 的计算节点,那么您选择哪个单元来删除服务或显示超visor的详细信息?

用例

作为云管理员,我希望能够唯一标识云中的资源,无论它们位于哪个单元中,并能够获取其详细信息并删除它们。

提议的变更

此蓝图建议向计算 REST API 添加一个微版本,该版本将 ID 字段的使用替换为 UUID 字段。UUID 将在 GET 响应中返回,并作为 CRUD API 中 ID 的输入。

然后,当发出删除服务的请求时,如果提供了 UUID,我们可以简单地迭代单元,直到找到该服务,或者返回 404 错误。

在微版本之前,如果传递了一个 ID 并且只有一个单元,或者在多个单元中没有重复项,我们将继续响应该请求。但是,如果在请求中传递了一个 ID(在微版本之前),并且我们无法从多个单元中唯一标识记录,我们将返回 400 错误。这与创建服务器时,如果没有提供网络或端口并且有多个网络可供项目使用时,我们返回 400“NetworkAmbiguous”错误的类似行为。

compute_nodes 表已经有一个 uuid 字段。但是,services 表没有,因此作为此蓝图的一部分,我们需要向该表和相应的版本对象添加一个 uuid 列。

备选方案

替代方案是仅暴露基本的 UUID 并使用它来迭代潜在的多个单元,直到找到匹配项,或者将单元 UUID 编码到资源 UUID 中。例如,如果我们可以简单地返回 {cell_uuid}-{resource_uuid}

然后,我们不必迭代所有单元来查找资源,而是可以解码输入 UUID 以获取所需的单元。

这不是一个推荐的替代方案,因为它将单元编码到 REST API 中,而我们过去说过我们不想这样做,并且类似于 cells v1 在单元上进行命名空间的方式。它还意味着计算 API 的某些部分正在编码单元 UUID,而其他部分(如 servers API)则没有。这可能会导致实际代码中的维护问题,因为我们将对不同的资源执行不同的查找操作。

另一种替代方案是在 Nova API 数据库中创建映射表,例如 host_mappingsinstance_mappings 表。至少目前,不推荐这种替代方案,因为处理服务记录的需求应该相对较小。

数据模型影响

单元(nova)数据库中的 services 表将添加一个可为空的 uuid 列。由于现有记录没有 uuid 字段,因此该列将可为空。

我们可以在通过版本对象访问时迁移数据,和/或提供在线数据迁移,以便在升级期间将 UUID 添加到现有记录。

REST API 影响

os-hypervisors

此 API 中只有 GET 方法。它们都将被更改为返回 id 字段的 uuid 值,并接受 {hypervisor_id} 的 uuid 值作为输入。我们不能使用 Ocata 中添加的 查询参数验证 来验证传入的 ID 是否为 UUID,因为它不是查询参数。因此,我们需要在代码中验证输入的 id 值是否为 UUID。

以下 API 也会被更改

* GET /os-hypervisors/{hypervisor_hostname_pattern}/search
* GET /os-hypervisors/{hypervisor_hostname_pattern}/servers

这两个 API 都返回给定主机名搜索模式的匹配列表。虽然这与此规范中陈述的问题没有直接关系,但我们将利用此 API 中微版本更改的机会来改进它们。hypervisor_hostname_pattern 将更改为查询参数。

  • 旧:GET /os-hypervisors/{hypervisor_hostname_pattern}/search

  • 新:GET /os-hypervisors?hypervisor_hostname=xxx

示例请求

GET /os-hypervisors?hypervisor_hostname=london1.compute

示例响应

{
  "hypervisors": [
    {
      "hypervisor_hostname": "london1.compute.1",
      "id": "37c62dfd-105f-40c2-a749-0bd1c756e8ff",
      "state": "up",
      "status": "enabled"
    }
  ]
}
  • 旧:GET /os-hypervisors/{hypervisor_hostname_pattern}/servers

  • 新:GET /os-hypervisors?hypervisor_hostname=xxx&with_servers=true

示例请求

GET /os-hypervisors?hypervisor_hostname=london1.compute&with_servers=true

示例响应

{
  "hypervisors": [
    {
      "hypervisor_hostname": "london1.compute.1",
      "id": "37c62dfd-105f-40c2-a749-0bd1c756e8ff",
      "state": "up",
      "status": "enabled",
      "servers": [
        {
          "name": "test_server1",
          "uuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
        },
        {
          "name": "test_server2",
          "uuid": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
        }
      ]
    }
  ]
}

os-services

以下 API 方法将接受输入和/或在响应中返回整数主键 ID,将被更新为接受/返回 UUID

* GET /os-services
* DELETE /os-services/{service_id}

例如

GET /os-services

响应

{
   "services": [
      {
         "id": "8e6e4ab6-0662-4ff5-8994-dde92bedada1",
         "binary": "nova-scheduler",
         "disabled_reason": "test1",
         "host": "host1",
         "state": "up",
         "status": "disabled",
         "updated_at": "2012-10-29T13:42:02.000000",
         "forced_down": false,
         "zone": "internal"
      },
      {
         "id": "3fe90b52-1d67-4f03-9ed3-5fbf1a6fa1e1",
         "binary": "nova-compute",
         "disabled_reason": "test2",
         "host": "host1",
         "state": "up",
         "status": "disabled",
         "updated_at": "2012-10-29T13:42:05.000000",
         "forced_down": false,
         "zone": "nova"
      },
   ]
}

DELETE /os-services/3fe90b52-1d67-4f03-9ed3-5fbf1a6fa1e1

成功的删除操作没有响应。

**action** API 不接受 ID 来标识要执行操作的服务。这些包括

* PUT /os-services/disable
* PUT /os-services/disable-log-reason
* PUT /os-services/enable
* PUT /os-services/force-down

/servers/{server_id}/action API 将操作放在请求主体中不同,这些 API 不接受特定的服务 ID。请求主体包含一个 hostbinary 字段来标识服务。

作为此微版本的一部分,我们将把这些 action API 合并到一个 PUT 方法中,该方法支持所有 action 并接受一个 service_id 作为输入,以唯一标识服务,而不是包含 hostbinary 字段的主体。

以下是每个 action API 的旧格式和新格式的示例。

  • PUT /os-services/disable

    旧请求

    PUT /os-services/disable
    {
        "host": "host1",
        "binary": "nova-compute"
    }
    

    新请求

    PUT /os-services/{service_id}
    {
        "status": "disabled"
    }
    
  • PUT /os-services/disable-log-reason

    旧请求

    PUT /os-services/disable-log-reason
    {
        "host": "host1",
        "binary": "nova-compute",
        "disabled_reason": "test2"
    }
    

    新请求

    PUT /os-services/{service_id}
    {
        "status": "disabled",
        "disabled_reason": "test2"
    }
    
  • PUT /os-services/enable*

    旧请求

    PUT /os-services/enable
    {
        "host": "host1",
        "binary": "nova-compute"
    }
    

    新请求

    PUT /os-services/{service_id}
    {
        "status": "enabled"
    }
    
  • PUT /os-services/force-down

    旧请求

    PUT /os-services/force-down
    {
        "host": "host1",
        "binary": "nova-compute",
        "forced_down": true
    }
    

    新请求

    PUT /os-services/{service_id}
    {
        "forced_down": true
    }
    

我们还将为 PUT 方法提供完整的响应。例如

  • PUT /os-services/disable-log-reason

    旧响应

    {
        "service": {
            "binary": "nova-compute",
            "disabled_reason": "test2",
            "host": "host1",
            "status": "disabled"
        }
    }
    

    新响应

    {
        "service": {
            "id": "ade63841-f3e4-47de-840f-815322afa569",
            "binary": "nova-compute",
            "disabled_reason": "test2",
            "host": "host1",
            "state": "up",
            "status": "disabled",
            "updated_at": "2012-10-29T13:42:05.000000",
            "forced_down": false,
            "zone": "nova"
        }
    }
    

安全影响

通知影响

Services

service.update 版本通知有效负载将被更新,以包含新的 uuid 字段。

Hosts

此 API 中存在未版本化的通知,用于对计算节点执行操作,例如 HostAPI.set_enabled.start。这些尚未转换为使用版本化通知,因此在此之前,无需进行任何更改。

其他最终用户影响

由于 REST API 更改不会更改响应中的“id”键,只会更改值,因此无需对 python-novaclient 进行任何更改。

性能影响

无。由于我们在 nova_api 数据库中没有服务映射表,因此我们已经必须迭代单元来查找匹配项,如这个更改所示:https://review.openstack.org/#/c/442162/

其他部署者影响

一旦部署者拥有多个单元,他们可能需要更新工具以指定微版本,以唯一标识超visor 或服务,例如,删除服务。

开发人员影响

实现

负责人

主要负责人

Matt Riedemann (mriedem)

其他贡献者

Dan Peschman (dpeschman)

工作项

  • 编写数据库模式迁移以添加 services.uuid 列。

  • 将 uuid 字段添加到 Service 对象。
    • 如果在创建() 时未指定,则为新服务生成 UUID。

    • 在从数据库检索时生成并保存旧服务的 UUID,就像计算节点获得 UUID 一样 [1]

  • 向 ComputeNode 和 Service 对象添加 get_by_uuid 方法。

  • 添加服务 UUID 的在线数据迁移,就像我们为计算节点所做的那样 [2]

  • 更新 nova.compute.api.HostAPI 方法,这些方法接受一个 ID 并检查该 ID 是否为 UUID,如果是,则使用对象上的 get_by_uuid 方法查询资源,否则使用 get_by_id,就像今天一样。

  • 将微版本添加到 os-hypervisorsos-services API,包括验证以确保传入的 id 是 UUID。这还包括更改 os-services PUT 方法的请求格式。这可能是一个大型且相对复杂的更改进行审查,但鉴于所有这些更改都将在同一个微版本中进行,我们无法实际地将这些更改分解。

  • 更新超visor [3] 和服务 [4] 的计算 API 响应模式验证。请注意,Tempest 响应模式已经允许整数或字符串。作为此更改的一部分,我们应该更新 Tempest 中的响应模式验证,以便在新的微版本之后,超visor 和服务 ID 应该是 UUID。

依赖项

测试

  • 针对负面场景的单元测试,例如无法在多个单元中按 UUID 找到服务。我们还应该测试将非 UUID 整数值传递给带有新微版本的更改的 API,以确保查询参数验证使该请求失败并显示 400 错误。

  • API 示例的函数测试,以确保微版本后的响应中的“id”值是 UUID 而不是整数。

  • 可能会添加 Tempest API 测试,尽管我们可能可以使用树内函数测试来处理相同的测试覆盖范围。

  • 我们将使用树内函数测试来测试所有 os-services PUT 方法的更改,因为 Tempest 不会测试禁用或强制关闭计算服务,因为这会破坏并发的多租户 Tempest 运行。

文档影响

需要更新 os-servicesos-hypervisors API 参考文档,以说明新的微版本接受输入并在响应中返回“id”键的 UUID 值。

参考资料

历史

修订历史

发布名称

描述

Pike

引入