OpenStack 的使命包括为 OpenStack 云定义以下目标:“部署之间可互操作”。在 HTTP API 的上下文中,这意味着为云 A 编写的客户端代码也应在云 B 和任何其他云上工作。由于云 A 和云 B 可能会在任何给定时间运行不同版本的 OpenStack 服务,并且它们可能会在不同的时间点升级其服务,因此这对 API 开发人员如何添加功能、修复错误以及从服务 API 中删除功能具有重要影响。
如果服务希望确保(并且应该确保)客户端代码始终与多个不同的 OpenStack 云互操作,那么
这些更改不得违反兼容性或稳定性,这两者将在下面详细定义。
在没有发出版本边界的情况下,不允许更改资源以及请求和响应主体和标头,除非在极少数情况下(见下文)。
注意
基于不同驱动程序可用性具有不同行为和表示形式的 API 对互操作性提出了重大挑战。在可能的情况下,应避免这些差异。如果无法避免,则应在云 A 和云 B 具有相同的驱动程序集可用时考虑互操作性。
版本发现和选择应默认为基线;使用 API 中的新功能和更改应选择加入。
当功能完全删除时,这必须导致基线提高。理想情况下,这应导致相当于主要版本更新,因为稳定性(向后兼容性)已被破坏。当服务使用微版本时,“主要版本更新”通常意味着提高可用版本的最小值。
如果将要删除功能,则其删除应遵循标准的 OpenStack 弃用程序。
希望弃用某些功能但保持稳定性的服务可以通过将该功能记录为已弃用但将其保留在 API 中来欢迎这样做。
要点是,任何具有已发布 API 并需要对其进行更改(以修复或演进 API)的服务都需要一种版本控制机制。本文档不涉及版本控制,但是目前在 OpenStack 中使用的唯一机制,并且已被证明适用于此处描述的目标是 微版本。
本文档的其余部分将描述服务如果希望确保 API 互操作性必须遵循的规则。它提出了两个理解本文档所做的权衡的重要假设
任何不支持版本控制并希望实现互操作性的服务都应执行以下两件事
注意
没有强大的版本边界管理系统但必须更改 API 的项目,从定义上来说,并没有遵守这些互操作性指南。项目本身必须决定如何演进其 API。如果这导致与 OpenStack 生态系统中的其他项目发生冲突,那么 API-SIG 的作用仅仅是帮助澄清指南并就如何最大程度地减少更改的影响提供建议。技术委员会是无法达成共识时提供裁决和调解的机构。
无论服务是否遵循指南,任何服务都应始终努力最大程度地减少 API 更改和版本升级,因为这会加剧用户“跟上”更改的需求。
一个新服务且正在开发中,显然会在其早期阶段进行大量的更改。在此期间,版本控制不是稳定性的指标或工具。但是,一旦进行了发布或与其他服务建立了依赖关系,如果需要互操作性,稳定性和兼容性就至关重要。
为了这些指南的目的,术语 兼容性 和 稳定性 具有相当狭窄的定义
这些定义假定客户端代码编写遵循 HTTP 标准。
如果对如何将这些定义应用于特定情况有疑问,则应首先考虑 API 的最终用户(即希望在多个云上使用相同代码的个人)的观点和需求,其次是云的部署者和管理员,最后是服务的开发人员。
有两种类型的更改不需要版本更改
更改是必需的,以修复一个非常严重的安全性错误,需要将其回溯到服务的所有受支持版本。
这种情况应该很少见。对于许多不太严重的安全性情况,正确的答案是将其视为错误,进行修复,创建新版本并发布,并建议人们升级。 OpenStack 漏洞管理团队具有确定安全漏洞真实严重性的专业知识。
API 服务中的一个错误导致客户端收到状态代码在500-599范围内的响应,被修复为返回信息性错误响应在400-499范围内(当请求有错误但可修复时)或响应成功(当请求正确形成但服务器处理中断时)。
以下更改需要版本更改
添加新的 URL。
这可能看起来是向后兼容的,因为旧客户端将永远不会使用新的 URL,但它破坏了呈现相同 API 版本但使用不同代码的云之间的互操作性。
将响应状态代码从一种形式的客户端错误更改为另一种形式(例如,403变为400)或一种形式的成功更改为另一种形式(例如,201变为204).
关于此主题的辩论仍在继续,尤其是在更改成功代码方面。一个强大的客户端可以有效地应对成功代码的变化,如果它将200变为299视为成功。这需要与这些指南假设的不同客户端标准。由于 OpenStack 生态系统中已经存在大量的客户端代码,因此强制执行客户端标准,例如 容忍读取器概念,是不可能的。
添加或删除请求或响应标头。
更改响应标头的值,该值会更改客户端应如何处理响应。例如,更改Content-Type标头以添加新的媒体类型。
在请求或响应中的资源表示中添加或删除属性。
更改资源表示(请求或响应)中现有属性的语义或类型。
更改资源属性中允许的值集,同时保持其类型。
例如,如果一个属性曾经接受“foo”、“bar”或“baz”,并且添加了“zoom”作为合法值,则需要一个版本。如果删除了“foo”,也需要一个版本。添加和删除在这里都很重要,因为我们希望两个不同的云在相同的 API 版本(但可能具有不同的代码版本)上表现相同。为了实现这一点,即使表面上向后兼容的更改也需要版本更改。
如果进行了版本更改,则可以进行以下更改,但应充分考虑这对现有用户的影响。在某个时候,用户将希望访问更高版本中的新功能,或者服务的最低版本将提高到发生更改的版本之后。客户端代码中的补偿更改在进行任何更改时都将非常重要,尤其是对于这些更改。
在许多情况下,更改会感觉很特殊,并且保证违反这些指南是合理的。请考虑以下场景
“更改是为了提高 API 的一致性。”
您希望提高 API 一致性的愿望值得赞赏,但所有 API 都有缺陷。需要破坏性更改的不一致之处可以在新的 API 版本中修复,但并非总是必要的。另一个选项是添加具有不同行为的新 URL。考虑所有选项,找到一种将您的精力集中于改善使用 API 的整体体验的方法。
“不太可能 API 的任何现有用户会受到影响。”
很难预测人们如何使用 API。开发人员会做奇怪的事情。随着我们的 API 越来越被采用,试图进行这样的预测将变得越来越徒劳。对此规则的一个例外是,正在更改的功能实际上是非功能的。
“现有的 API 没有很好地记录。”
如果 API 的行为没有充分记录,那么使用 API 的开发人员除了根据他们观察到的行为来判断外,别无选择。将违反这些观察的更改需要一个版本。
“更改不会影响 OpenStack 的客户端库或命令行界面用户。”
我们鼓励开发人员针对 OpenStack REST API 进行开发。会有许多工具和应用程序更喜欢 REST API 而不是我们的库或命令行界面。
如上所述,一个全新的服务不应过早地承诺稳定性(如本文档中所定义)。只有在达到某种形式的稳定性(标准英语意义上)后,才值得考虑。
一个具有现有稳定服务的项目,如果希望尝试可能选择永远不稳定的新功能,应将该实验性服务发布到服务目录中的唯一端点,与现有服务分开。