一致的查询参数验证

https://blueprints.launchpad.net/nova/+spec/consistent-query-parameters-validation

目前查询参数在每个 API 方法中以不一致的方式进行验证。这使得 API 代码难以维护且容易出错。本规范旨在提出一种一致的机制来验证查询参数。

问题描述

API 层支持使用 json-schema 验证请求的输入体。存在支持模式微版本的机制。但是,没有集中支持来验证查询参数,导致处理不一致。

  • API 方法中不一致的查询参数验证。 相似的查询参数具有不同的验证方式。或者某些查询参数根本没有验证。例如,服务器列表 API 和 simple_tenant_usage API 之间接受不同的日期时间格式。服务器列表中的 changes-since 接受 ISO 8601 格式 的日期时间 [1]。simple_tenant_usage 中的 start/end 接受一些自定义格式 [2]。

  • 在不深入研究代码的情况下,开发人员和用户无法知道 API 支持哪些查询参数。并且有些查询参数直接传递到 SQL 查询中。例如,服务器列表 API 中 sort_key 的值直接传递到 DB 层。[3]

  • DB 模式直接暴露给 REST API。当 DB 模式发生变化时,API 也会发生变化。与上述示例相同。 sort_key 的值直接传递到 DB 层,导致 DB 对象的内部属性 __wrapper__ 暴露给 REST API。

用例

这是一项关于 API 层代码重构的工作。旨在减轻维护 API 代码的负担。使用场景是 Nova 的开发人员

  • 开发人员需要对查询参数进行一致的验证。

  • 开发人员不希望将验证代码与其他 API 代码混合在一起。

  • 开发人员需要一个中心位置来声明支持的查询参数。

最终,最终用户将获得以下好处

  • 一致的查询参数验证。

  • 稳定的 API,API 不再会因底层 DB 模式的变化而改变。

提议的变更

本规范建议使用 JSON-schema 将查询参数的验证从 API 代码中提取出来。

JSON-schema 也是开发人员熟悉的工具,因此基于此构建新机制是好的。

为了使用 JSON-schema 验证查询参数,需要将查询参数转换为扁平化的 JSON 数据。例如,有三个查询参数

  • name 接受一个正则表达式字符串。它只能指定一次。

  • sort_key 可以接受一个字符串,有效的字符串是 created_atupdated_at。它可以被多次指定。

  • deleted 是布尔值,只能指定一次。

请求如下

The request:
 GET http://openstack.example.com/v2.1/servers?name=abc&sort_key=created_at&sort_key=updated_at&deleted=True

查询参数转换为扁平化的 JSON 数据

{
    'name': ['^abc'],
    'sort_key': ['created_at', 'updated_at']
    'deleted': ['True']
}

此扁平化的 JSON 数据可以直接通过 JSON-schema 进行验证,相应的 JSON-schema 如下

{
    'type': 'object',
    'properties': {
        'name': {
            'type': 'array',
            'items': {'type': 'string', 'format': 'regex'},
            'maxItems': 1
        }
        'sort_key': {
            'type': 'array',
            'items': {'type': 'string',
                      'enum': ['created_at', 'updated_at']}
        },
        'deleted': {
            'type': 'array',
            'items': parameter_types.boolean,
            'maxItems': 1
    }
    'additionalProperties': False,
}

为了减少复制/粘贴,引入了两个宏函数

{
    'type': 'object'
    'properties': {
        'name': single_param({'type': 'string', 'format': 'regex'})
        'sort_key': multi_params({'type': 'string', 'enum': ['created_at', 'updated_at']}),
        'deleted': single_param(parameter_types.boolean)
    }
    'additionalProperties': False
}

模式将通过新的装饰器附加到每个 API

@validation.query_params_schema(schema, min_version, max_version)

可以在装饰器中指定给定 json-schema 支持的微版本范围。装饰器的用法与 body jsons-schema 装饰器相同。

如果存在匹配请求版本的模式,则验证失败时将返回 400 错误。

additionalProperties 的行为如下

  • additionalProperties 的值为 True 时,表示允许额外的查询参数。但这些额外的查询参数将被删除。

  • additionalProperties 的值为 False 时,表示不允许额外的查询参数。

additionalProperties 的值将为 True,直到我们决定在未来限制参数,并使用新的微版本进行更改。现在我们仍然需要在查询字符串中启用随机输入。但是,额外的参数将被删除以保护系统。此外,为了匹配当前行为,我们需要为所有参数启用多个值(使用宏函数 ‘multi_params’ 提取多个值的模式)。对于遗留 v2 API 模式,additionalProperties 的值也应为 True,这使得遗留 v2 API 模式也受到保护。

当前 API 仅在 API 用户在请求中指定了多个值时,才接受单个值参数的一个值。只有接受的值将被验证。新的验证机制支持多个值参数。区别在于,新的机制将验证所有值,即使只有一个被接受。但考虑到这种情况很少见,是可以接受的。

备选方案

如果我们保持一切不变,查询参数验证的代码将难以维护。这会导致隐藏查询参数中的错误。

数据模型影响

REST API 影响

本提案将使用 keypairs API 作为示例。对于使用 json-schema 验证其他 API 的查询参数,将在其他提案中说明。

在 keypairs API 中,新的查询参数是在 Microversion 2.10 和 2.35 中添加的。例如,将为 index 方法添加以下装饰器

schema_v2_1 = {
    'type': 'object',
    'properties': { }
    'additionalProperties': True
}

schema_v2_10 = copy.deepcopy(schema_v2_1)
schema_v2_10['properties'] = {'user_id': multi_params({'type': 'string'}}

schema_v2_35 = copy.deepcopy(schema_v2_10)
schema_v2_35['properties']['limit'] = multi_params(
    {'type': 'string', 'format': 'integer'})
schema_v2_35['properties']['marker'] = multi_params({'type': 'string'})
@validation.query_params_schema(schema_v2_35, '2.35')
@validation.query_params_schema(schema_v2_10, '2.10', '2.34')
@validation.query_params_schema(schema_v2_1, '2.0', '2.9)
def index(req):
    ....

Keypairs API 的行为如下

对于 GET /keypairs?user_id=1&user_id=2

  • 过去:接受,但我们忽略 user_id=2

  • 现在:接受,但我们忽略 user_id=2

  • 未来:在添加新的微版本后返回 400

对于 GET /keypairs?limit=abc

  • 拒绝,该值为整数

对于 GET /keypairs?limit=abc&limit=1

  • 过去:接受,忽略 limit=abc

  • 现在:拒绝,limit 的所有值都应该是整数

  • 未来:拒绝,只能指定单个值。

安全影响

通知影响

其他最终用户影响

性能影响

其他部署者影响

开发人员影响

本提案提高了 API 代码的可维护性。

实现

负责人

主要负责人

Alex Xu <hejie.xu@intel.com>

其他贡献者

ZhenYu Zheng <zhengzhenyu@huawei.com>

工作项

  • 引入新的装饰器以启用查询参数的 json-schema

  • 在 keypairs API 中使用 json-schema 进行查询参数验证。

依赖项

测试

需要单元测试和功能测试来确保新机制按预期工作。在使用新机制代替现有的查询参数处理时,现有的单元测试和功能测试仍然可以通过测试。

文档影响

开发人员参考需要描述如何使用新机制。

参考资料

[1] https://github.com/openstack/nova/blob/00bc0cb53d6113fae9a7714386953d1d75db71c1/nova/api/openstack/compute/servers.py#L244

[2] https://github.com/openstack/nova/blob/00bc0cb53d6113fae9a7714386953d1d75db71c1/nova/api/openstack/compute/simple_tenant_usage.py#L178

[3] https://github.com/openstack/nova/blob/00bc0cb53d6113fae9a7714386953d1d75db71c1/nova/api/openstack/common.py#L145

历史

修订版

发布名称

描述

Ocata

引入