Endpoint Discovery

来自 Catalog 的 Endpoint

在从 keystone 认证返回的 token 中可以找到 {service-catalog}

如果使用 v3 认证,catalog 将位于顶级 token 对象的 catalog 属性中。例如

{
  "token": {
    "catalog": {}
  }
}

如果使用 v2 认证,它将位于顶级 access 对象的 serviceCatalog 属性中。例如

{
  "access": {
    "serviceCatalog": {}
  }
}

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

type

匹配 {service-type}

endpoints

该服务对应的 endpoint 对象列表

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

name

匹配 {service-name}

id

匹配 {service-id}

endpoint 列表的格式取决于是否使用了 v2 或 v3 认证。对于两个版本,每个 endpoint 对象都有一个 region 键,如果提供了 {region-name},则该键应与其匹配。

在 v2 认证中,endpoint 对象有三个键 publicURLinternalURLadminURL。用户请求的 {interface} 的 endpoint 位于名称与 {interface} 加上字符串 URL 匹配的键中。

在 v3 认证中,endpoint 对象有一个 url,如果 interface 的值与 {interface} 匹配,则该 url 就是正在请求的 endpoint。

带有 Catalog 的 Token 示例

V3 Catalog 对象

{
  "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"
        }
    ],
}

V2 Catalog 对象

{
  "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"
      },
    ]
  }
}

Endpoint Discovery 算法

  1. 如果提供了 {endpoint-version} 并且 {service-type}v[0-9]+$ 后缀结尾,并且 {endpoint-version} 不匹配该后缀(请参阅 比较主版本),则停止。返回一个错误,说明用户请求了版本化的 {service-type} 别名和一个不兼容的 {endpoint-version}

  2. {service-catalog} 中找到与请求的 {service-type} 匹配的对象(请参阅 匹配候选条目)。

  3. 如果提供了 {service-name} 并且剩余的对象具有 name 字段,则仅保留 name{service-name} 匹配的对象。

    注意

    Keystone v3 之前的 catalog 不具有 name 字段。如果未请求 {be-strict} 并且 catalog 不具有 name 字段,则应忽略 {service-name}

  4. 如果提供了 {service-id} 并且剩余的对象具有 id 字段,则仅保留 id{service-id} 匹配的对象。

    注意

    Keystone v2 的 catalog 不具有 id 字段。如果未请求 {be-strict} 并且 catalog 不具有 id 字段,则应忽略 {service-id}

剩余的对象列表是 {candidate-catalog-objects}。如果此列表为空,则返回一个错误,说明没有匹配 {service-type}{service-name} 的 endpoint。

  1. 使用 {candidate-catalog-objects} 生成 {candidate-endpoints} 列表。对于 {candidate-catalog-objects} 中的每个 endpoint 对象

    1. 如果 v2,如果对于给定的任何 {interface} 值,都没有形式为 {interface}URL 的键,则丢弃该 endpoint。

    2. 如果 v3,如果 interface 不匹配任何给定的 {interface} 值,则丢弃该 endpoint。

  2. 如果没有剩余的 endpoint,则返回一个错误,说明没有匹配任何 {interface} 值的 endpoint,最好包括找到的接口列表。

  3. 对于剩余的 {candidate-endpoints} 中的每个 endpoint,如果提供了 {region_name} 并且它不匹配 regionregion_id 中的任何一个,则丢弃该 endpoint。

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

  4. 从剩余的候选 endpoint 集合中,找到与请求的 {service-type} 最佳匹配的 endpoint(请参阅 查找最佳服务类型匹配的 Endpoint)。

  5. 从剩余的候选 endpoint 集合中,找到与最佳可用的请求 {interface} 最佳匹配的 endpoint:按照 {interface} 列表的偏好顺序,返回与第一个具有任何匹配 endpoint 的 {interface} 匹配的所有 endpoint。

剩余的 {candidate-endpoints} 匹配请求。如果有多个 endpoint,则使用第一个,但向用户发出警告,说明剩余的 endpoint 超过一个。如果请求了 {be-strict},则返回一个错误,其中包含列表中每个 endpoint 的信息。

注意

如果存在多个剩余的 endpoint,提出错误会更正确,但 keystoneauth 库返回第一个,更改它会破坏大量现有用户。如果编写一个全新的库或一个新的主要版本,其中行为更改是可以接受的,那么如果剩余的 endpoint 超过一个,最好在此处提出错误。

  1. 如果 v2,{catalog-endpoint}{interface}URL 的值。

  2. 如果 v3,{catalog-endpoint}url 的值。

匹配候选条目

对于 catalog 中的每个条目

  1. 如果条目的类型与请求的 {service-type} 匹配,则它是候选条目。

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

  3. 如果请求的类型是来自 OpenStack 服务类型权威机构 的官方类型的别名,并且条目的类型与官方类型匹配,则它是候选条目。

  4. 如果请求的类型是来自 OpenStack 服务类型权威机构 的官方类型的别名,该类型具有别名,并且条目的类型与其中一个别名匹配,并且提供了 {endpoint-version},并且找到的别名以 v[0-9]+$ 后缀结尾,并且 {endpoint-version} 与后缀中的版本匹配(请参阅 比较主版本),则它是候选条目。

查找最佳服务类型匹配的 Endpoint

给定一个已匹配其他标准的候选 endpoint 列表

  1. 检查候选 endpoint 列表,查看其中一个是否与请求的 {service-type} 匹配。如果有任何是精确匹配,则返回它们。

  2. 如果请求的 {service-type}

    查找以 v[0-9]+$ 形式结尾的别名。如果有任何别名具有与 {endpoint-version} 匹配的后缀(请参阅 比较主版本),则在候选 endpoint 列表中查找这些别名。如果有任何匹配,则返回它们。

  3. 如果请求的 {service-type}

    按列出的顺序检查每个别名,查看它是否有来自候选 endpoint 的匹配 endpoint。返回与具有匹配 endpoint 的第一个别名匹配的 endpoint。

  4. 如果请求的 {service-type}

    查找以 v[0-9]+$ 形式结尾的别名。如果有任何别名具有与 {endpoint-version} 匹配的后缀(请参阅 比较主版本),则在候选 endpoint 列表中查找这些别名。

    返回与最高匹配版本的别名匹配的 endpoint。

  5. 如果没有匹配的 endpoint,则返回一个错误。

注意

的情况是

  • 请求了一个别名

  • 没有提供 {endpoint-version}

  • catalog 中有一个不同的别名

是不安全的,因此有意将其视为缺少匹配的 endpoint。许多别名都带有隐含的版本,因此在没有用户请求的 {endpoint-version} 的情况下,返回与明确请求的不同 endpoint 很有可能不是用户期望的 endpoint。

比较主版本

在比较主版本时,有一个 required 和一个 candidate

  • required 是用户请求的。

  • candidate 是正在测试的可能版本。

为了适合,候选版本 必须与 所需版本 具有相同的 major 版本号,并且 minor 版本号至少相等:候选版本 3.3所需版本 3.1 匹配,但 4.1 不匹配。

在所有情况下,应丢弃前导的 ‘v’ 字符串。

  1. 仅包含单个数字的版本号应归一化为 .0。也就是说,版本号 2 应被视为 2.0

  2. 如果 所需版本 是字符串 latest 或不包含任何值,则 候选版本 匹配。

  3. 如果 所需版本 是一个范围,则任何大于或等于第一个值且小于或等于第二个值的 候选版本 都匹配。相等性按照上述规则进行判断。大于和小于的判断按预期进行:首先比较第一个数字,如果第一个数字匹配,则比较第二个数字。因此,{所需版本}2,4 时,匹配 22.3344.7{所需版本}2.1,4.0 时,匹配 2.3344.7,但不匹配 2

  4. 如果 所需版本 是一个没有最大值的范围,则最大值应被视为 latest

发现示例

例如,给定以下目录

{
  "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.

service_type = 'volume'
api_version = 2
# volume not in authority or catalog
# volume is an alias of block-storage
# block-storage is not found.
# volumev2 is an alias of block-storage and ends with v2 which matches
#   api_version of 2
# return volumev2

给定以下目录

{
  "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

service_type = 'volumev2'
api_version = '3'
# volumev2 ends with a version suffix of v2 which does not match 3
# return an error before even fetching the catalog

给定以下目录

{
  "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