REST API 微版本支持¶
https://blueprints.launchpad.net/nova/+spec/api-microversions
我们需要一种方法来引入对 REST API 的更改,以修复错误和添加新功能。其中一些更改与旧版本不兼容,而我们目前没有办法做到这一点。
问题描述¶
作为一个社区,我们非常擅长通过增量开发来不断发展接口和代码。我们不太擅长一次性的大规模代码更新。Nova API 已经变得足够大,并且通过新的扩展不断增长,因此它可能永远无法进行 API 的主要版本更新,因为这会对用户产生影响,并且开发者需要支持多种实现,这会增加开销。
与此同时,我们允许通过添加扩展来创新 API 的现状,已经发展到我们现在拥有扩展的扩展,假设扩展列表是版本控制的一种简陋机制。这导致了大量的技术债务。它阻止我们进行某些更改,例如弃用当前无意义或损坏的 API 部分。或者修复其他领域,增量开发导致 API 中不一致之处,这会让新用户感到困惑。
我们必须提出一种更好的方法,以满足以下需求
能够以增量方式发展 API,这是我们作为社区的优势。
为 REST API 的用户提供向后兼容性。
使代码更简洁,从而降低我们在扩展或修改 API 时出错的可能性。
一个优秀的接口会竭尽所能地使其难以错误使用。一个好的接口会努力成为一个优秀的接口,但会屈服于现实情况。
用例¶
允许开发者以向后兼容的方式修改 Nova API,并动态地向 API 用户发出更改可用信号,而无需创建新的 API 扩展。
允许开发者以不向后兼容的方式修改 Nova API,同时仍然支持旧行为。REST API 的用户能够在每次请求的基础上决定是希望 Nova API 以新方式还是旧方式运行。部署者能够在支持开发者提供支持的前提下,提供新的不向后兼容的功能,而无需删除对先前行为的支持。
REST API 的用户能够在每次请求的基础上决定他们想要使用的 API 版本(假设部署者支持他们想要的版本)。
项目优先级¶
kilo 优先级列表当前未定义。但是,根据目前拟议的优先级列表,它将属于“用户体验”,因为它显著提高了我们改进 Nova API 的能力。
提议的变更¶
设计优先级
最终用户将如何使用它,以及我们如何使其难以错误使用
代码将如何内部构建。我们如何使其
易于在代码中看到您即将破坏 API 兼容性。
易于进行向后兼容的更改
能够进行不向后兼容的更改
最大限度地减少代码重复,以最大限度地减少维护开销
我们将如何进行测试,包括单元测试和集成测试。这会施加什么限制。
版本控制¶
为了本次讨论的目的,“API”是 Nova 树中的所有核心和可选扩展。
API 的版本控制应为单个单调递增计数器。它将采用 X.Y 的形式,并遵循以下约定
如果对 API 整体产生重大的不向后兼容的更改,则仅更改 X。也就是说,很少很少增加一次。
Y 当您对 API 进行任何更改时。请注意,这包括语义更改,这些更改可能不会影响输入或输出格式,甚至不会源自 API 代码层。我们在版本控制系统中不区分向后兼容和不向后兼容的更改。但是,将在文档中明确说明哪些是向后兼容的更改,哪些是不向后兼容的更改。
请注意,API 中类似的更改组不会在单个版本更新下进行。这将最大限度地减少对用户的影响,因为他们可以控制想要公开的更改。
向后兼容的更改定义为 OpenStack API 更改指南允许的更改
https://wiki.openstack.org/wiki/APIChangeGuidelines
版本响应如下所示
GET /
{
"versions": [
{
"id": "v2.1",
"links": [
{
"href": "https://:8774/v2/",
"rel": "self"
}
],
"status": "CURRENT",
"version": "5.2"
"min_version": "2.1"
},
]
}
这指定了服务器可以理解的最小和最大版本。min_version 将从 2.1 开始,代表 v2.1 API(等效于 V2.0 API,但支持 XML)。如果存在我们认为不足以支持的支持负担,它最终可能会增加。
客户端交互¶
客户端通过以下方法指定他们想要使用的 API 版本,即一个新的标头
X-OpenStack-Nova-API-Version: 2.114
从概念上讲,这就像 accept 标头一样。这是一个全局 API 版本。
从语义上讲,这意味着
如果未提供 X-OpenStack-Nova-API-Version,则表现得好像发送了 min_version。
如果发送了 X-OpenStack-Nova-API-Version,则以该版本响应 API。如果超出支持的版本范围,则返回 406 Not Acceptable。
如果 X-OpenStack-Nova-API-Version:latest(特殊关键字),则返回 API 的 max_version。
这意味着开箱即用,使用旧客户端,OpenStack 安装将返回 v2 的纯 OpenStack 响应。用户或 SDK 必须要求不同的内容才能获得新功能。
始终在响应中返回两个额外的标头
X-OpenStack-Nova-API-Version:version_number,experimental Vary:X-OpenStack-Nova-API-Version
第一个标头指定执行的 API 的版本号。如果操作员对 API 行为进行了非标准的修改,则仅返回 Experimental。这仅用作过渡机制,直到一些云操作员使用的功能被上游化,并且将在少数几个版本中删除。
第二个标头用作缓存代理的提示,表明响应也依赖于 X-Openstack-Compute-API-Version,而不仅仅是主体和查询参数。有关详细信息,请参阅 RFC 2616 第 14.44 节。
实现设计细节¶
在每次请求中,X-OpenStack-Nova-API-Version 标头字符串将在 wsgi 代码中转换为 APIVersionRequest 对象。路由将以通常的方式进行,并将版本对象附加到请求对象(所有 API 方法都期望该对象)。API 方法然后可以使用它来确定其对传入请求的行为。
我们需要支持的更改类型
* Status code changes (success and error codes)
* Allowable body parameters (affects input validation schemas too)
* Allowable url parameters
* General semantic changes
* Data returned in response
* Removal of resources in the API
* Removal of fields in a response object or changing the layout of the response
注意:此列表并非详尽无遗
在控制器案例中,可以使用装饰器标记方法,以指示它们实现哪些 API 版本。例如
@api_version(min_version='2.1', max_version='2.9')
def show(self, req, id):
pass
@api_version(min_version='3.0')
def show(self, req, id):
pass
对 API 版本 2.2 的传入请求将执行第一种方法,而对 API 版本 3.1 的传入请求将执行第二种方法。
版本对象会传递到与请求对象关联的方法,因此也可以在方法中进行非常具体的检查。例如
def show(self, req, id):
.... stuff ....
if req.ver_obj.matches(start_version, end_version):
.... Do version specific stuff ....
.... stuff ....
请注意,end_version 是可选的,在这种情况下,它将匹配大于或等于 start_version 的任何版本。
这里有一些原型代码,解释了它是如何工作的
https://github.com/cyeoh/microversions_poc
验证模式装饰器还需要扩展以支持版本控制
@validation.schema(schema_definition, min_version, max_version)
请注意,min_version 和 max_version 都是可选参数。
可以通过指定 max_version 从 API 中删除方法、扩展或请求或响应中的字段
@api_version(min_version='2.1', max_version='2.9')
def show(self, req, id):
.... stuff ....
如果客户端发出对版本 2.11 的请求,则客户端将收到 404,就好像该方法根本不存在一样。如果 API 的最小版本提高到 2.10,则可以删除扩展本身。
API 的最小版本将仅由 Nova 开发者达成共识来提高,他们有维护向后兼容性的开销,以及希望永远保持向后兼容性的部署者和用户。
由于我们有一个在整个 API 中单调递增的版本号,而不是对单个插件进行版本控制,因此我们将面临与当前 DB 迁移更改集相同的潜在合并冲突。抱歉,我不相信有任何方法可以避免这种情况,但欢迎提出任何建议!
客户端期望¶
与支持版本协商的系统一样,使用此 API 的健壮客户端还需要支持一定范围的版本,否则该客户端将无法用于与多个云通信的软件中。
具体示例是 OpenStack Infra 中的 nodepool。假设有一个世界,它定期连接到 4 个公共云。它们的状态如下
- Cloud A:
- min_ver: 2.100
- max_ver: 2.300
- Cloud B:
- min_ver: 2.200
- max_ver: 2.450
- Cloud C:
- min_ver: 2.300
- max_ver: 2.600
- Cloud D:
- min_ver: 2.400
- max_ver: 2.800
没有一个版本的 API 可用在所有这些云中,这基于其中一些云的古老程度。但是,在客户端 SDK 中,某些基本功能(如启动)将存在,尽管根据 API 的版本可能会获得不同的附加数据。客户端应尽可能平滑这些差异。
现实地说,这今天就存在一个问题,只是没有基础设施来支持创建解决方案来解决它。
备选方案¶
另一种选择是立即进行所有不向后兼容的更改并进行主要的 API 发布。例如,将 url 前缀更改为 /v3 而不是 /v2。然后支持这两种实现很长时间。过去已经拒绝这种方法,因为担心维护开销。
数据模型影响¶
无
REST API 影响¶
如上所述,将附加版本信息添加到 GET /。这些应该是向后兼容的更改,而且我怀疑实际上没有人真正使用这些信息。
否则,除非提供请求中描述的客户端标头,否则不会进行任何更改。
安全影响¶
无
通知影响¶
无
其他最终用户影响¶
SDK 作者需要开始使用 X-OpenStack-Nova-API-Version 标头来访问新功能。只有在新版本中添加新功能这一事实才会鼓励他们这样做。
python-novaclient 处于相同的情况,需要更新以支持新的标头才能支持新的 API 功能。
性能影响¶
无
其他部署者影响¶
无
开发人员影响¶
这显然会影响 Nova 开发者修改 REST API 代码和添加新扩展的方式。
常见问题解答¶
添加新插件会更改版本号吗? 是的。
错误状态码更改时,我们是否会增加版本号? 是的,这是一个 API 更改。
实现¶
负责人¶
- 主要负责人
cyeoh-0
- 其他贡献者
<launchpad-id 或 None>
工作项¶
实现 APIVersions 类
实现处理 X-OpenStack-Nova-API-Version 标头
实现基于版本标头调用的方法路由。
找到并实现需要微版本更新的第一个 API 更改。
依赖项¶
这取决于 v2.1 v2-on-v3-api 规范的完成。
任何希望对 API 进行不向后兼容的更改的 nova 规范(例如任务 api 规范)都依赖于此更改。同样,任何希望对 v2.1 API 进行任何 API 更改而无需添加虚拟扩展的规范也依赖于此更改。
JSON-Home 与此相关,但它们提供不同的服务。微版本允许客户端控制他们暴露于哪个版本的 API,而 JSON-Home 描述了该 API,从而实现资源发现。
测试¶
Tempest 无法测试微版本支持的所有可能的 API 组合。我们将不得不选择代表已实现内容的特定版本。现有的 Nova tempest 测试将作为未来 API 版本测试的基线。
文档影响¶
长期目标是至少部分自动化地使用当前的 json schema 支持和未来的 JSON-Home 支持来生成 API 文档。这个问题与此规范相当正交。