统一限制 API

详细说明将资源限制与项目关联所需的工作。

bp unified-limits

问题描述

目前,资源配额在 Keystone 之外的每个项目中维护,例如 Nova、Cinder 或其他。这导致一个问题,即项目与其资源之间没有强关联。例如,当用户在 Nova 中设置一个配额,如“项目 A 可以创建 10 台虚拟机”时,如果项目 A 不存在于 Keystone 中,Nova 仍然会创建该配额。如果项目 A 在 Keystone 中被删除,Nova 不会知道,并且仍然会保留该配额。当处理项目层次结构时,这个问题只会更加严重。

有关此问题和总体方法的更多信息,请参阅单独的高级 规范

提议的变更

用于创建、读取、更新、删除限制定义的接口。它包括两种类型的限制:注册 限制项目 限制注册 限制 是默认限制。您不能为未注册的限制设置项目限制。项目 限制 是覆盖每个项目的注册限制的限制。

注意

本规范不讨论限制的使用检查。由使用服务来强制使用。

注册限制

以下 API 调用专门用于管理注册限制。

创建注册限制

限制与注册限制端点一起注册。由于限制定义通常会批量创建,因此支持一次发送多个限制定义。这是一个仅限管理员的操作。

请求: POST /registered-limits

请求参数

  • service_id - 负责资源的的服务,应与服务目录中的服务匹配。

  • region_id (可选) - 服务托管的区域。如果指定了 region_id,则应与使用服务的区域相同。否则,Keystone 将像端点一样将其保留为空。

  • resource_name - 资源的名称,应与同一服务和区域的其他资源名称唯一。

  • default_limit - 所有项目应为此资源承担的默认限制。

请求体

{
    "limits": [
        {
            "service_id": "77232e5107074dfe801657000348e8c9",
            "region_id": "regionOne",
            "resource_name": "cores",
            "default_limit": 10
        },
        {
            "service_id": "77232e5107074dfe801657000348e8c9",
            "resource_name": "ram_mb",
            "default_limit": 20480
        },
    ]
}

响应

所有注册限制的完整列表。

响应参数

  • id - 每个注册限制的唯一 uuid。

  • service_id - 负责资源的的服务,应与服务目录中的服务匹配。

  • region_id (可选) - 服务托管的区域。

  • resource_name - 资源的名称,应与同一服务和区域的其他资源名称唯一。

  • default_limit - 所有项目应为此资源承担的默认限制。

响应代码

  • 200 - 确定

  • 400 - 请求错误 - 如果依赖资源不存在

  • 403 - 禁止 - 如果用户未被授权创建注册限制

  • 409 - 限制已存在

响应体

{
    "limits": [
        {
            "id": "0681e10c01c044d78ef8e5cb592c6446",
            "service_id": "77232e5107074dfe801657000348e8c9",
            "region_id": "regionOne",
            "resource_name": "cores",
            "default_limit": 10
        },
        {
            "id": "1dc633fe5acd4182b63f68c9cc8e768a",
            "service_id": "77232e5107074dfe801657000348e8c9",
            "resource_name": "ram_mb",
            "default_limit": 20480
        },
        {
            "id": "5c4182eb45304cb3ac89030b19ab5a81",
            "service_id": "ae22fb0dfbd34464bf67e758977f4839",
            "region_id": "regionOne",
            "resource_name": "storage_gb",
            "default_limit": 20
        },
    ]
}

更新注册限制

更新类似于 POST 完成,但是仅包含您希望覆盖的限制。如果 service_idregion_idresource_name 尚未存在,则会抛出错误。

请求: PUT /registered-limits

请求参数

  • service_id - 负责资源的的服务,应与服务目录中的服务匹配。

  • region_id (可选) - 服务托管的区域。

  • resource_name - 资源的名称,应与同一服务和区域的其他资源名称唯一。

  • default_limit - 所有项目应为此资源承担的默认限制。

请求体

{
    "limits":[
        {
            "id": "1dc633fe5acd4182b63f68c9cc8e768a",
            "service_id": "77232e5107074dfe801657000348e8c9",
            "resource_name": "ram_mb",
            "default_limit": 10240
        }
    ]
}

响应

所有限制的完整列表。这允许您双重检查您的工作。

响应代码

  • 200 - 确定

  • 400 - 请求错误 - 如果依赖资源不存在

  • 403 - 禁止 - 如果用户未被授权更新注册限制

响应参数

  • id - 每个注册限制的唯一 uuid。

  • service_id - 负责资源的的服务,应与服务目录中的服务匹配。

  • region_id (可选) - 服务托管的区域。

  • resource_name - 资源的名称,应与同一服务和区域的其他资源名称唯一。

  • default_limit - 所有项目应为此资源承担的默认限制。

响应体

{
    "limits": [
        {
            "id": "0681e10c01c044d78ef8e5cb592c6446",
            "service_id": "77232e5107074dfe801657000348e8c9",
            "region_id": "regionOne",
            "resource_name": "cores",
            "default_limit": 10
        },
        {
            "id": "1dc633fe5acd4182b63f68c9cc8e768a",
            "service_id": "77232e5107074dfe801657000348e8c9",
            "resource_name": "ram_mb",
            "default_limit": 10240
        },
        {
            "id": "5c4182eb45304cb3ac89030b19ab5a81",
            "service_id": "ae22fb0dfbd34464bf67e758977f4839",
            "region_id": "regionOne",
            "resource_name": "storage_gb",
            "default_limit": 20
        },
    ]
}

列出所有注册限制

具有有效令牌的任何人都可以读取注册限制。

请求: GET /registered-limits

请求过滤器

注册限制还将支持过滤器,以便更轻松地查看子集。service_idregion_idresource_name 将都是有效的搜索参数。

响应

所有注册限制的完整列表。

响应代码

  • 200 - 确定

  • 403 - 禁止 - 如果用户未被授权列出注册限制

响应参数

  • id - 每个注册限制的唯一 uuid。

  • service_id - 负责资源的的服务,应与服务目录中的服务匹配。

  • region_id (可选) - 服务托管的区域。

  • resource_name - 资源的名称,应与同一服务和区域的其他资源名称唯一。

  • default_limit - 所有项目应为此资源承担的默认限制。

响应体

{
    "limits": [
        {
            "id": "0681e10c01c044d78ef8e5cb592c6446",
            "service_id": "77232e5107074dfe801657000348e8c9",
            "region_id": "regionOne",
            "resource_name": "cores",
            "default_limit": 10
        },
        {
            "id": "1dc633fe5acd4182b63f68c9cc8e768a",
            "service_id": "77232e5107074dfe801657000348e8c9",
            "resource_name": "ram_mb",
            "default_limit": 20480
        },
        {
            "id": "5c4182eb45304cb3ac89030b19ab5a81",
            "service_id": "ae22fb0dfbd34464bf67e758977f4839",
            "region_id": "regionOne",
            "resource_name": "storage_gb",
            "default_limit": 20
        },
    ]
}

显示注册限制

具有有效令牌的任何人都可以读取注册限制。

请求: GET /registered-limits/{registered-limits-id}

请求参数

  • registered-limits-id - 指定的注册限制的 ID。

响应

指定的注册限制。

响应代码

  • 200 - 确定

  • 403 - 禁止 - 如果用户未被授权检索注册限制

  • 404 - 未找到 - 如果请求的注册限制不存在

响应参数

  • id - 每个注册限制的唯一 uuid。

  • service_id - 负责资源的的服务,应与服务目录中的服务匹配。

  • region_id (可选) - 服务托管的区域。

  • resource_name - 资源的名称,应与同一服务和区域的其他资源名称唯一。

  • default_limit - 所有项目应为此资源承担的默认限制。

响应体

{
    "id": "0681e10c01c044d78ef8e5cb592c6446",
    "service_id": "77232e5107074dfe801657000348e8c9",
    "region_id": "regionOne",
    "resource_name": "cores",
    "default_limit": 10
}

删除注册限制

请求: DELETE /registered-limits/{registered-limits-id}

请求参数

  • registered-limits-id - 指定的注册限制的 ID。

响应

无内容。

响应代码

  • 204 - 无内容

  • 403 - 禁止 - 如果用户未被授权删除注册限制

  • 404 - 未找到 - 如果请求的注册限制不存在

项目限制

以下 API 调用专门用于管理项目限制。它们仅限项目管理员(系统管理员)。

注意

初始实现仅支持“扁平”层次模型。在此模型中,与项目关联的限制将作为扁平结构进行验证。这意味着限制不会根据项目的父级、子级或同级进行强制或验证。所有限制都与这些关系无关。这被称为“扁平”强制模型。未来的工作将详细说明更复杂的强制模型,这些模型了解项目层次结构。

创建项目限制

使用项目限制覆盖注册限制。

请求: POST /limits

请求参数

  • project_id (可选) - 承担限制的项目。如果省略,Keystone 将从令牌(上下文)获取 project_id。

  • service_id - 负责资源的的服务,应与服务目录中的服务匹配。

  • region_id (可选) - 服务托管的区域。它应使用将要覆盖的注册限制的相同 region_id

  • resource_name - 资源的名称,应与同一服务和区域的其他资源名称唯一。

  • resource_limit - 项目为此资源承担的覆盖限制。

请求体

{
    "limits":[
        {
            "project_id": "95541dbfaa054cab86510e0d0a87896a",
            "service_id": "77232e5107074dfe801657000348e8c9",
            "region_id": "regionOne",
            "resource_name": "ram_mb",
            "resource_limit": 10240,
        },
        {
            "project_id": "95541dbfaa054cab86510e0d0a87896a",
            "service_id": "77232e5107074dfe801657000348e8c9",
            "region_id": "regionOne",
            "resource_name": "cores",
            "resource_limit": 10,
        },
    ]
}

响应

我们返回整个限制结构,包括没有覆盖的默认值。

响应代码

  • 200 - 确定

  • 400 - 请求错误 - 如果依赖资源不存在

  • 403 - 禁止 - 如果用户未被授权更改该项目的限制

  • 409 - 限制已存在

响应参数

  • id - 指定限制的 ID。

  • project_id - 承担限制的项目。

  • service_id - 负责资源的的服务,应与服务目录中的服务匹配。

  • region_id (可选) - 服务托管的区域。

  • resource_name - 资源的名称,应与同一服务和区域的其他资源名称唯一。

  • resource_limit - 项目为此资源承担的覆盖限制。

响应体

{
    "limits":[
        {
            "id": "aaab50e9c36f4a84bab98dfc117c9836",
            "project_id": "95541dbfaa054cab86510e0d0a87896a",
            "service_id": "77232e5107074dfe801657000348e8c9",
            "region_id": "regionOne",
            "resource_name": "ram_mb",
            "resource_limit": 10240,
        },
        {
            "id": "e08fcb2756be48e387e821bd79e29538",
            "project_id": "95541dbfaa054cab86510e0d0a87896a",
            "service_id": "77232e5107074dfe801657000348e8c9",
            "region_id": "regionOne",
            "resource_name": "cores",
            "resource_limit": 10,
        },
    ]
}

更新项目限制

更新项目限制。创建项目限制后,唯一可以更改的属性是 resource_limit

请求: PUT /limits

请求参数

  • resource_limit - 项目为此资源承担的覆盖限制。

请求体

{
    "limits":[
        {
            "id": "aaab50e9c36f4a84bab98dfc117c9836",
            "resource_limit": 5120,
        },
        {
            "id": "e08fcb2756be48e387e821bd79e29538",
            "resource_limit": 5,
        },
    ]
}

响应

我们返回整个限制结构,包括没有覆盖的默认值。

响应代码

  • 200 - 确定

  • 400 - 请求错误 - 如果匹配资源名称的注册限制或具有给定 ID 的项目限制不存在

  • 403 - 禁止 - 如果用户未被授权更改该项目的限制

响应参数

  • id - 指定限制的 ID。

  • project_id - 承担限制的项目。

  • service_id - 负责资源的的服务,应与服务目录中的服务匹配。

  • region_id (可选) - 服务托管的区域。

  • resource_name - 资源的名称,应与同一服务和区域的其他资源名称唯一。

  • resource_limit - 项目为此资源承担的覆盖限制。

响应体

{
    "limits":[
        {
            "id": "aaab50e9c36f4a84bab98dfc117c9836",
            "project_id": "95541dbfaa054cab86510e0d0a87896a",
            "service_id": "77232e5107074dfe801657000348e8c9",
            "region_id": "regionOne",
            "resource_name": "ram_mb",
            "resource_limit": 5120,
        },
        {
            "id": "e08fcb2756be48e387e821bd79e29538",
            "project_id": "95541dbfaa054cab86510e0d0a87896a",
            "service_id": "77232e5107074dfe801657000348e8c9",
            "region_id": "regionOne",
            "resource_name": "cores",
            "resource_limit": 5,
        },
    ]
}

列出项目限制

请求: GET /limits

请求过滤器

  • project_id - 仅供云管理员用于使用指定的 project_id 过滤限制。项目管理员只能列出其自己项目的限制。

限制还将支持过滤器,以便更轻松地查看子集。service_idregion_idresource_name 将都是有效的搜索参数。

响应

项目中所有限制的列表。

响应代码

  • 200 - 确定

  • 403 - 禁止 - 如果用户未被授权列出该项目的限制

响应参数

  • id - 指定限制的 ID。

  • project_id - 承担限制的项目。

  • service_id - 负责资源的的服务,应与服务目录中的服务匹配。

  • region_id (可选) - 服务托管的区域。

  • resource_name - 资源的名称,应与同一服务和区域的其他资源名称唯一。

  • resource_limit - 项目为此资源承担的覆盖限制。

响应体

{
    "limits":[
        {
            "project_id": "95541dbfaa054cab86510e0d0a87896a",
            "service_id": "77232e5107074dfe801657000348e8c9",
            "region_id": "regionOne",
            "resource_name": "ram_mb",
            "resource_limit": 10240,
        },
        {
            "project_id": "95541dbfaa054cab86510e0d0a87896a",
            "service_id": "77232e5107074dfe801657000348e8c9",
            "region_id": "regionOne",
            "resource_name": "cores",
            "resource_limit": 10,
        },
    ]
}

显示项目限制

请求: GET /limits/{limit-id}

请求参数

  • limit-id - 指定限制的 ID。

响应

指定限制的详细信息。

响应代码

  • 200 - 确定

  • 403 - 禁止 - 如果用户未被授权检索该项目限制

  • 404 - 未找到 - 如果请求的项目限制不存在

响应参数

  • id - 指定限制的 ID。

  • project_id - 承担限制的项目。

  • service_id - 负责资源的的服务,应与服务目录中的服务匹配。

  • region_id (可选) - 服务托管的区域。

  • resource_name - 资源的名称,应与同一服务和区域的其他资源名称唯一。

  • resource_limit - 项目为此资源承担的覆盖限制。

响应体

{
    "project_id": "95541dbfaa054cab86510e0d0a87896a"
    "service_id": "77232e5107074dfe801657000348e8c9",
    "region_id": "regionOne",
    "resource_name": "ram_mb",
    "resource_limit": 10240,
}

删除项目限制

请求: DELETE /limits/{limit-id}

请求参数

  • limit-id - 指定限制的 ID。

响应

无内容

响应代码

  • 204 - 无内容

  • 403 - 禁止 - 如果用户未被授权删除该项目的限制

  • 404 - 未找到 - 如果请求的项目限制不存在

扁平层次强制

Keystone 支持分层多租户,其中项目可以分组到树结构中,并具有父级、同级和子级。可以考虑项目限制根据树中其他项目的限制以不同的方式交互的各种方式。本规范中记录的项目限制的初始实现将考虑扁平结构。这意味着限制信息和验证不考虑层次结构中的其他项目。每个项目都有自己的限制。

假设项目 P 是项目 F 的子项目,而项目 F 是项目 A 的子项目。在限制上设置默认值,所有项目都获得该有效默认值。

假设我们有一个默认限制为 10

graph {
   node [shape=box]
   A [label="A (10)"];
   F [label="F (10)"];
   P [label="P (10)"];
}

然后我们 UPDATE LIMIT on A to 20

graph {
   node [shape=box]
   A [label="A (20)", fontcolor = "#FF0000"];
   F [label="F (10)"];
   P [label="P (10)"];
}

或者我们可以 UPDATE LIMIT on P to 30

graph {
   node [shape=box]
   A [label="A (20)"];
   F [label="F (10)"];
   P [label="P (30)", fontcolor = "#FF0000"];
}

在扁平强制下允许这样做,因为层次结构在限制验证期间没有被考虑在内。将来,我们将引入一种能够根据项目层次结构验证限制的模型。重要的是要注意,在强制模型之间切换需要仔细的计划,并且可能导致根据所做的请求和新的强制模型而导致 API 更改。部署需要了解这一点,了解切换强制模型的影响以及它可能对现有限制产生的影响。

Keystone 还会公开一个 GET /limits-model 端点,该端点负责返回部署选择的强制模型。这对于允许可发现的限制模型和保持 OpenStack 部署之间的互操作性至关重要,这些部署具有不同的强制模型。

备选方案

至少有一个项目已经尝试在服务本身中实现分层配额。由于理解层次结构可能很混乱,因此不复制该逻辑是导致我们采用这种将限制与层次结构紧密关联的方法的原因。

另一种选择是在项目内部添加限制。API 将类似于 /projects/{project_id}/limits/{limit_id}。这些 API 已经具有显示项目层次结构的方法。这样,我们可以轻松地重用它。

安全影响

本工作中针对的限制的强制和验证特定于扁平层次结构。这意味着限制与项目相关联,与父级、子级或同级项目无关。例如,假设项目 alpha 是项目 bravocharlie 的父项目。扁平层次结构允许 bravo 具有 10 个实例的限制,而 charliealpha 可能只有 5 个实例的限制。从项目层次结构的视角来看,这可能感觉不直观。这是第一个强制模型实现,一旦我们建立知识并收集使用反馈,我们将努力开发更复杂的强制模型,这些模型考虑项目层次结构。

注册限制应被视为公开信息且可发现。项目限制应可供项目成员使用。具有项目 alpha 上角色的用户应该能够列出项目的限制,但不能列出 bravocharlie 的限制。当我们在未来开始开发考虑层次结构的强制模型时,这种情况将变得更加复杂。

当引入更复杂的模型时,我们需要提供足够的信息给用户,以便他们了解为什么限制更新失败或为什么资源请求使他们超出配额,而不会泄露太多关于相关项目的信息。这不需要使用这个初始的“扁平”实现来解决。

通知影响

注册限制和项目限制应受到 Keystone 中其他资源相同的通知。

其他最终用户影响

无。最终用户将能够查询 Keystone 以获取限制信息。这提高了可用性,因为他们可以看到限制是什么,并在向管理员寻求帮助时收集更多信息。

性能影响

初始扁平层次设计的内部性能影响应可以忽略不计。这可能会在开始开发分层强制模型时变得更加复杂(例如,计算相对于其父级、子级和同级项目的项目限制)。然后,Keystone 将不得不计算更复杂的限制结构。

其他服务需要对 keystone 进行额外的调用来检索限制信息,以便进行配额实施。这将给 API 调用的整体性能增加一些开销。

值得注意的是,注册限制和项目限制预计不会频繁更改。这意味着数据可以安全地缓存一段时间。缓存将在 keystone 内部实现,类似于 keystone 缓存其他资源的响应的方式。但是,也可以在客户端进行缓存,以避免为相对静态的限制信息频繁调用 keystone。

其他部署者影响

希望在 keystone 中使用注册限制和项目限制的部署需要在安装时进行设置。这为操作员创建了一个额外的步骤,类似于他们在服务目录中注册服务的方式。

开发人员影响

来自其他项目的开发人员可能会提出以下问题

  • 注册限制和项目限制有什么区别?

  • 限制中传递了哪些信息?

  • 如何根据限制信息实施使用情况?

  • 是否有库可以帮我做到这一点?

对于希望实施分层配额的开发人员,我们需要确保沟通很多事情。Keystone 只是这里的信息点。我们需要在另一端可用,以帮助他们消费这些信息。

这些问题以及其他问题可能需要在 keystone 的开发者文档中得到解答。

实现

负责人

主要负责人

其他贡献者

工作项

  1. 实现统一限制,将上述新 API 添加到 Keystone。

  2. 实现对统一限制的客户端支持。

  3. 记录限制模型,记录统一限制,添加相关的开发者和用户文档。

跟踪这项工作的 epic 可以在 keystone 的 Queens 路线图中找到。

注意

确保 API 足够通用,以便将来支持更多的配额模型。

依赖项

文档影响

应该解决对新限制 API 的使用方法。

参考资料

关于限制的高级 概述