消耗服务目录

本文档描述了从服务目录中正确查找服务端点的过程。

注意

本文档中描述的过程与所有已知的 OpenStack 公有云兼容,并且与 keystoneauth Python 库的行为相匹配,keystoneauth 是使用 keystone 进行身份验证和从目录中获取信息的参考实现。在某些情况下,可以论证采用不同的过程,但考虑到 keystoneauth 的广泛使用和参考性质,我们选择保持与 keystoneauth 行为的向后兼容性,而不是设计一个全新的完美过程。keystoneauth 本身也在内部记录了它为了保持与早期库的兼容性而做出的地方。已经留下了关于库或框架可以选择实施的更严格行为的注释。

注意

本文档中“对象”一词指的是 JSON 对象,而不是任何特定编程语言中的对象。

用户请求

此过程的最终目标是让用户找到给定一些输入的服务端点信息。用户将从知道这些参数中的一些开始该过程。用户期望的每个额外输入,如果没有“他们从哪里学习这些信息”的答案,都会增加用户消耗服务的难度,因此客户端库和实用程序强烈建议尽一切努力帮助用户提出正确的问题。

注意

对你接受的内容宽容一些,对你输出的内容严格一些。

有且只有一条信息是用户绝对必须知道的

服务类型
服务的官方名称,例如计算, 镜像或者块存储OpenStack 服务类型权威机构 中列出。必需。在不知道他们想要发现的服务的情况下,用户不可能消耗服务发现。

用户可能还希望表达对通用算法的修改

严格模式
放弃宽松的向后兼容性让步,并在输入和输出验证方面更加严格。

有几个可选的信息片段是用户可能知道的,或者用户可能希望表达的附加约束。

区域名称
用户希望使用的服务的区域。根据云是否有多个区域,这可能是可选的。所有服务都存在于区域内,但有些云只有一个区域。如果{严格模式}已给出,{区域名称}是必需的。

注意

强烈建议{区域名称}始终需要它,以防止单区域云在未来添加区域。但是,keystoneauth 今天允许省略区域名称,并且存在大量具有单个名为RegionOne的区域的云。对于全新的库或主要版本,其中可以接受破坏性行为,默认情况下需要区域名称是首选的。

interface
要使用的 API 接口,例如public, 内部,或者admin用户想要使用的接口。用户还可以按其偏好顺序请求他们认为可接受的接口列表,例如['internal', 'public'](可选,默认为public.)
服务名称
部署者赋予服务的任意名称。可选。

注意

在绝大多数情况下,这永远是不需要的,并且强烈建议部署者不要将其用作有意义的标识符。但是,如果部署者为其提供了一个目录,其中服务名称必须使用,那么服务名称必须作为输入接受。如果{严格模式}已请求,提供{服务名称}应该是一个错误。

服务 ID
目录中端点的唯一标识符。可选。

注意

在具有良好格式目录的云上服务 ID这永远是不需要的。如果{严格模式}已请求,提供{服务 ID}应该是一个错误。

端点覆盖
用户从其他来源获得的服务的端点。 (可选,默认为省略。)

在发现过程结束时,用户应该知道{服务端点},这是用作服务根的端点,以及{接口}找到的端点。

在下面的描述中,上述所有输入和输出都将像{端点覆盖}这样引用,以便清楚地了解用户是否向该过程提供了输入,或者讨论的是预期的输出之一。在过程的某个点被获取并在稍后点被引用的其他值类似地引用为{服务目录}。名称不会在过程中被重用以在不同时间保存不同的内容。

还假设用户拥有{认证 URL}和身份验证信息。身份验证过程本身不在本文档的范围内。

发现算法

服务应在其{服务目录}中使用{服务类型}来自 OpenStack 服务类型权威机构。但是,由于历史原因,有一些服务具有在野外发现的旧服务类型。为了促进使用正确的{服务类型}名称,但也支持现有用户和安装,OpenStack 服务类型权威机构 包含此类服务的历史别名列表。有关数据本身的信息,请参阅 消耗服务类型权威机构

客户端需要一份在 OpenStack 服务类型权威机构 中发布的数据的副本才能完成完整的发现算法。客户端库可以保留本地副本或从 https://service-types.openstack.org/service-types.json 获取数据并可能对其进行缓存。建议客户端库处理其用户的历史数据,但也允许用户提供最新版本的数据(如果需要)。有关如何获取数据的更多信息,请参阅 消耗服务类型权威机构

基本过程是

  1. 如果用户提供了{端点覆盖},停止。这是{服务端点}.
  2. {认证 URL}处对 keystone 进行身份验证,检索令牌,其中包含{服务目录}.
  3. 检索{目录端点}{服务目录}给定{服务类型}, {接口}, {服务名称}, {区域名称}{服务 ID}的某种组合。(参见 从目录获取端点。)

从目录获取端点

The{服务目录}可以在从 keystone 身份验证返回的令牌中找到。

如果使用 v3 身份验证,目录将在顶层对象的catalog令牌属性中。例如

{
  'token': {
    'catalog': {}
  }
}

如果使用 v2 身份验证,它将在serviceCatalogcatalogaccess属性中。例如

{
  'access': {
    'serviceCatalog': {}
  }
}

中。在两种情况下,目录内容本身都是对象的列表。每个对象都有两个主要键与发现相关

type
匹配项{服务类型}
端点
该服务的端点对象列表

此外,为了保持向后兼容性,可能需要检查以下键。

name
匹配项{服务名称}
id
匹配项{服务 ID}

端点列表的格式取决于是否使用了 v2 或 v3 身份验证。对于两个版本,每个端点对象都有一个区域键,该键应与{区域名称}(如果提供了)匹配。

在 v2 身份验证中,端点对象有三个键publicURL, internalURL, adminURL。用于{接口}请求的端点位于与{接口}匹配的键中URL.

加上字符串在 v3 身份验证中,端点对象有一个urlinterface如果{接口}.

匹配,则该端点是请求的端点。

带有目录的令牌的具体示例

{
  "token": {
    "catalog": [
        {
            "endpoints": [
                {
                    "id": "39dc322ce86c4111b4f06c2eeae0841b",
                    "interface": "public",
                    "region": "RegionOne",
                    "url": "https://identity.example.com"
                },
                {
                    "id": "ec642f27474842e78bf059f6c48f4e99",
                    "interface": "internal",
                    "region": "RegionOne",
                    "url": "https://identity.example.com"
                },
                {
                    "id": "c609fc430175452290b62a4242e8a7e8",
                    "interface": "admin",
                    "region": "RegionOne",
                    "url": "https://identity.example.com"
                }
            ],
            "id": "4363ae44bdf34a3981fde3b823cb9aa2",
            "type": "identity",
            "name": "keystone"
        }
    ],
}

V3 目录对象

{
  "access": {
    "serviceCatalog": [
      {
        "endpoints_links": [],
        "endpoints": [
          {
            "adminURL": "https://identity.example.com/v2.0",
            "region": "RegionOne",
            "publicURL": "https://identity.example.com/v2.0",
            "internalURL": "https://identity.example.com/v2.0",
            "id": "4deb4d0504a044a395d4480741ba628c"
          }
        ],
        "type": "identity",
        "name": "keystone"
      },
    ]
  }
}

V2 目录对象

  1. 该算法是{服务目录}找到{服务类型}中与请求的
  2. 如果{服务名称}匹配的对象。(参见 匹配候选条目。)name如果给出了name如果{服务名称}.

注意

,并且剩余的对象具有{严格模式}字段,则仅保留name匹配的对象。Keystone v3 之前的目录不包含 name 字段。如果{服务名称}未请求,并且目录没有

  1. 如果{服务 ID}匹配的对象。(参见 匹配候选条目。)id如果给出了id如果{服务 ID}.

注意

字段,则应忽略{严格模式}字段,则仅保留id匹配的对象。Keystone v3 之前的目录不包含 name 字段。如果{服务 ID}未请求,并且目录没有

。Keystone v2 目录不包含 id 字段。如果剩余的对象是{候选目录对象}{服务类型}{服务名称}.

使用剩余的对象是。如果没有端点,则返回错误,说明没有匹配的端点,以生成.

{候选端点}剩余的对象是:

  1. 的列表。对于每个中的端点对象,如果 v2,如果没有形式为{接口}URL{接口}的键,则丢弃该端点。
  2. 如果 v3,如果interface不匹配任何{接口}的键,则丢弃该端点。

如果没有任何端点,则返回错误,说明没有匹配任何{接口}的端点,最好包括找到的接口列表。

对于剩余的的端点,以生成:

  1. 如果{region_name}已给出,并且不匹配区域或者region_id中的任何一个,则丢弃该端点。

如果没有剩余的端点,则返回错误,说明没有匹配{region_name}的端点,最好包括找到的区域列表。

  1. 从剩余的候选端点集中,找到与请求的{服务类型}最匹配的端点。(参见 查找与最佳服务类型匹配的端点。)

剩余的的端点,以生成匹配请求。如果有多个,则使用第一个,但向用户发出警告,说明有多个端点剩余。如果{严格模式}已请求,则返回一个错误,其中包含列表中每个端点的相关信息。

注意

如果剩余的端点多于一个,则引发错误会更正确,但 keystoneauth 库返回第一个,更改它会破坏大量现有用户。如果从头开始编写全新的库,或者新版本,其中可以接受行为更改,那么如果剩余的端点多于一个,则引发错误是首选的。

  1. 如果 v2,则{目录端点}中的端点对象,如果 v2,如果没有形式为.
  2. 的值。如果 v3,则{目录端点}在 v3 身份验证中,端点对象有一个.

匹配候选条目

对于目录中的每个条目

  1. 如果条目的类型与请求的{服务类型}匹配,则它是候选条目。
  2. 如果请求的类型是 OpenStack 服务类型权威机构 中的官方类型,并且具有别名,并且其中一个别名与条目的类型匹配,则它是候选条目。
  3. 如果请求的类型是 OpenStack 服务类型权威机构 的官方类型的别名,并且条目的类型与官方类型匹配,则它是候选条目。

注意

目前不支持请求一个别名并找到另一个别名,因为大多数别名还包含有关主要版本的隐含信息。后续规范添加了版本发现过程,此时可以安全地尝试返回列在别名下的端点。

查找与最佳服务类型匹配的端点

给定已匹配其他标准的候选端点列表

  1. 检查候选端点列表,查看其中一个是否与请求的{服务类型}匹配。如果有任何是精确匹配,则 查找与最佳接口匹配的端点
  2. 如果请求的{服务类型}OpenStack 服务类型权威机构 中的官方类型,并且具有别名,则按权威机构中列出的偏好顺序检查每个别名,查看是否有匹配的候选端点。对于与第一个别名具有匹配端点的所有端点,查找与最佳接口匹配的端点
  3. 如果请求的{服务类型}OpenStack 服务类型权威机构 中的一个官方类型的别名,并且任何端点都匹配官方类型,查找匹配最佳接口的端点

查找匹配最佳接口的端点

给定已匹配其他标准的候选端点列表

  1. 按照优先级顺序{接口}列表,返回与第一个匹配的所有端点{接口}与匹配的端点。

例如,给定以下目录

{
  "token": {
    "catalog": [
        {
            "endpoints": [
                {
                    "interface": "public",
                    "region": "RegionOne",
                    "url": "https://block-storage.example.com/v3"
                }
            ],
            "id": "4363ae44bdf34a3981fde3b823cb9aa3",
            "type": "volumev3",
            "name": "cinder"
        },
        {
            "endpoints": [
                {
                    "interface": "public",
                    "region": "RegionOne",
                    "url": "https://block-storage.example.com/v2"
                }
            ],
            "id": "4363ae44bdf34a3981fde3b823cb9aa2",
            "type": "volumev2",
            "name": "cinder"
        }
    ],
}

那么以下

service_type = 'block-storage'
# block-storage is not found, get list of aliases
# volumev3 is found, return it

service_type = 'volumev2'
# volumev2 not an official type in authority, but is in catalog
# return volumev2 entry

service_type = 'volume'
# volume not in authority or catalog
# volume is an alias of block-storage
# block-storage is not found. Return error.

给定以下目录

{
  "token": {
    "catalog": [
        {
            "endpoints": [
                {
                    "interface": "public",
                    "region": "RegionOne",
                    "url": "https://block-storage.example.com"
                }
            ],
            "id": "4363ae44bdf34a3981fde3b823cb9aa3",
            "type": "block-storage",
            "name": "cinder"
        }
    ],
}

那么以下

service_type = 'block-storage'
# block-storage is found, return it

service_type = 'volumev2'
# volumev2 not in authority, is an alias for block-storage
# block-storage is in the catalog, return it

给定以下目录

{
  "token": {
    "catalog": [
        {
            "endpoints": [
                {
                    "interface": "public",
                    "region": "RegionOne",
                    "url": "https://block-storage.example.com"
                }
            ],
            "id": "4363ae44bdf34a3981fde3b823cb9aa3",
            "type": "block-storage",
            "name": "cinder"
        },
        {
            "endpoints": [
                {
                    "interface": "public",
                    "region": "RegionOne",
                    "url": "https://block-storage.example.com/v2"
                },
                {
                    "interface": "internal",
                    "region": "RegionOne",
                    "url": "https://block-storage.example.int/v2"
                }
            ],
            "id": "4363ae44bdf34a3981fde3b823cb9aa2",
            "type": "volumev2",
            "name": "cinder"
        }
    ],
}

那么以下

service_type = 'block-storage'
interface = ['internal', 'public']
# block-storage is found
# block-storage does not have internal, but has public
# return block-storage public

service_type = 'volumev2'
interface = ['internal', 'public']
# volumev2 not an official type in authority, but is in catalog
# volumev2 has an internal interface
# return volumev2 internal entry

使用服务类型权威机构

OpenStack 服务类型权威机构 包含关于官方服务类型名称和历史服务类型名称的数据,这些名称通常在没有官方列表之前使用。它可供库和其他客户端 API 消费者使用,以便能够基于官方列表提供一致的接口,同时仍然支持现有名称。强烈建议提供此支持,但最终是可选的。匹配过程的第一步始终是返回目录和用户请求之间的直接匹配,因此在权威机构存在之前现有的消费模式应始终有效。

为了使用 OpenStack 服务类型权威机构 中的信息,重要的是要知道一些事情

  1. 数据以 YAML 格式保存在 git 中。这是列表的最终权威源代码。
  2. 数据以 JSON 格式发布在 https://service-types.openstack.org/service-types.json,并具有 JSONSchema 在 https://service-types.openstack.org/published-schema.json
  3. 发布的数据包含一个基于 ISO 日期时间格式 的日期版本,一个包含构建发布数据所使用的 git 提交 SHA 的 sha,以及官方类型和别名之间的预构建正向和反向映射。
  4. JSON 文件使用 ETag 支持提供,应被认为具有高度可缓存性。
  5. JSON 文件的当前版本应始终是首选使用的文件。
  6. JSON 文件类似于时区数据。不应将其视为版本化,因此发行版的稳定版本不应提供其冻结版本。发行版软件包应在发布文件的最新版本时,为所有活动版本进行更新。