From 9d0d351226de2b063cf963503b3c3fe44b76a066 Mon Sep 17 00:00:00 2001 From: Joey Date: Fri, 9 Oct 2020 14:05:14 +0800 Subject: [PATCH] Add lables for Route/Service/Consumer/SSL (#2345) Signed-off-by: imjoey --- apisix/schema_def.lua | 33 ++++++++++ doc/admin-api.md | 5 ++ doc/zh-cn/admin-api.md | 5 ++ t/admin/consumers.t | 75 ++++++++++++++++++++++ t/admin/routes.t | 141 +++++++++++++++++++++++++++++++++++++++++ t/admin/services.t | 138 ++++++++++++++++++++++++++++++++++++++++ t/admin/ssl.t | 87 +++++++++++++++++++++++++ t/admin/upstream.t | 10 +-- 8 files changed, 489 insertions(+), 5 deletions(-) diff --git a/apisix/schema_def.lua b/apisix/schema_def.lua index 2653069b..0ce1fb6a 100644 --- a/apisix/schema_def.lua +++ b/apisix/schema_def.lua @@ -452,6 +452,15 @@ _M.route = { plugins = plugins_schema, upstream = upstream_schema, + labels = { + description = "key/value pairs to specify attributes", + type = "object", + patternProperties = { + [".*"] = label_value_def + }, + maxProperties = 16 + }, + service_id = id_schema, upstream_id = id_schema, service_protocol = { @@ -490,6 +499,14 @@ _M.service = { name = {type = "string", maxLength = 50}, desc = {type = "string", maxLength = 256}, script = {type = "string", minLength = 10, maxLength = 102400}, + labels = { + description = "key/value pairs to specify attributes", + type = "object", + patternProperties = { + [".*"] = label_value_def + }, + maxProperties = 16 + } }, additionalProperties = false, } @@ -504,6 +521,14 @@ _M.consumer = { pattern = [[^[a-zA-Z0-9_]+$]] }, plugins = plugins_schema, + labels = { + description = "key/value pairs to specify attributes", + type = "object", + patternProperties = { + [".*"] = label_value_def + }, + maxProperties = 16 + }, desc = {type = "string", maxLength = 256} }, required = {"username"}, @@ -555,6 +580,14 @@ _M.ssl = { type = "integer", minimum = 1588262400, -- 2020/5/1 0:0:0 }, + labels = { + description = "key/value pairs to specify attributes", + type = "object", + patternProperties = { + [".*"] = label_value_def + }, + maxProperties = 16 + }, status = { description = "ssl status, 1 to enable, 0 to disable", type = "integer", diff --git a/doc/admin-api.md b/doc/admin-api.md index 29041c45..2e95ff71 100644 --- a/doc/admin-api.md +++ b/doc/admin-api.md @@ -70,6 +70,7 @@ |upstream_id|False |Upstream|Enabled upstream id, see [Upstream](architecture-design.md#upstream) for more || |service_id|False |Service|Binded Service configuration, see [Service](architecture-design.md#service) for more || |service_protocol|False|Upstream protocol type|only `grpc` and `http` are supported|`http` is the default value; Must set `grpc` if using `gRPC proxy` or `gRPC transcode`| +|labels |False |Match Rules|Key/value pairs to specify attributes|{"version":"v2","build":"16","env":"production"}| For the same type of parameters, such as `host` and `hosts`, `remote_addr` and `remote_addrs` cannot exist at the same time, only one of them can be selected. If enabled at the same time, the API will response an error. @@ -288,6 +289,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 |upstream_id|False |Upstream|Enabled upstream id, see [Upstream](architecture-design.md#upstream) for more || |name |False |Auxiliary |Identifies service names.|customer-xxxx| |desc |False |Auxiliary |service usage scenarios, and more.|customer xxxx| +|labels |False |Match Rules|Key/value pairs to specify attributes|{"version":"v2","build":"16","env":"production"}| Config Example: @@ -426,6 +428,7 @@ Return response from etcd currently. |username|True|Name|Consumer name|| |plugins |False |Plugin|See [Plugin](architecture-design.md#plugin) for more || |desc |False |Auxiliary |Identifies route names, usage scenarios, and more.|customer xxxx| +|labels |False |Match Rules|Key/value pairs to specify attributes|{"version":"v2","build":"16","env":"production"}| Config Example: @@ -507,6 +510,7 @@ In addition to the basic complex equalization algorithm selection, APISIX's Upst |desc |optional|upstream usage scenarios, and more.| |pass_host |optional|`pass` pass the client request host, `node` not pass the client request host, using the upstream node host, `rewrite` rewrite host by the configured `upstream_host`.| |upstream_host |optional|This option is only valid if the `pass_host` is `rewrite`.| +|labels|False |Match Rules|Key/value pairs to specify attributes|{"version":"v2","build":"16","env":"production"}| Config Example: @@ -649,6 +653,7 @@ Return response from etcd currently. |cert|True|Public key|https Public key|| |key|True|Private key|https Private key|| |sni|True|Match Rules|https SNI|| +|labels|False |Match Rules|Key/value pairs to specify attributes|{"version":"v2","build":"16","env":"production"}| Config Example: diff --git a/doc/zh-cn/admin-api.md b/doc/zh-cn/admin-api.md index 39631420..8e958e70 100644 --- a/doc/zh-cn/admin-api.md +++ b/doc/zh-cn/admin-api.md @@ -74,6 +74,7 @@ |priority |可选 |匹配规则|如果不同路由包含相同 `uri`,根据属性 `priority` 确定哪个 `route` 被优先匹配,值越大优先级越高,默认值为 0。|priority = 10| |vars |可选 |匹配规则|由一个或多个`{var, operator, val}`元素组成的列表,类似这样:`{{var, operator, val}, {var, operator, val}, ...}}`。例如:`{"arg_name", "==", "json"}`,表示当前请求参数 `name` 是 `json`。这里的 `var` 与 Nginx 内部自身变量命名是保持一致,所以也可以使用 `request_uri`、`host` 等;对于 `operator` 部分,目前已支持的运算符有 `==`、`~=`、`>`、`<` 和 `~~`。对于`>`和`<`两个运算符,会把结果先转换成 number 然后再做比较。查看支持的[运算符列表](#运算符列表)|{{"arg_name", "==", "json"}, {"arg_age", ">", 18}}| |filter_func|可选|匹配规则|用户自定义的过滤函数。可以使用它来实现特殊场景的匹配要求实现。该函数默认接受一个名为 vars 的输入参数,可以用它来获取 Nginx 变量。|function(vars) return vars["arg_name"] == "json" end| +|labels |可选 |匹配规则|标识附加属性的键值对|{"version":"v2","build":"16","env":"production"}| 有两点需要特别注意: @@ -298,6 +299,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f13 |upstream_id| upstream 或 upstream_id 两个选一个 |Upstream|启用的 upstream id,详见 [Upstream](architecture-design.md#upstream)|| |name |可选 |辅助 |标识服务名称。|| |desc |可选 |辅助 |服务描述、使用场景等。|| +|labels |可选 |匹配规则|标识附加属性的键值对|{"version":"v2","build":"16","env":"production"}| serivce 对象 json 配置内容: @@ -439,6 +441,7 @@ HTTP/1.1 200 OK |username|必需|辅助|Consumer 名称。|| |plugins|可选|Plugin|该 Consumer 对应的插件配置,它的优先级是最高的:Consumer > Route > Service。对于具体插件配置,可以参考 [Plugins](#plugin) 章节。|| |desc |可选 |辅助|consumer描述|| +|labels |可选 |匹配规则|标识附加属性的键值对|{"version":"v2","build":"16","env":"production"}| consumer 对象 json 配置内容: @@ -521,6 +524,7 @@ APISIX 的 Upstream 除了基本的复杂均衡算法选择外,还支持对上 |desc |可选 |辅助|上游服务描述、使用场景等。|| |pass_host |可选|枚举|`pass` 透传客户端请求的 host, `node` 不透传客户端请求的 host, 使用 upstream node 配置的 host, `rewrite` 使用 `upstream_host` 配置的值重写 host 。|| |upstream_host |可选|辅助|只在 `pass_host` 配置为 `rewrite` 时有效。|| +|labels |可选 |匹配规则|标识附加属性的键值对|{"version":"v2","build":"16","env":"production"}| upstream 对象 json 配置内容: @@ -661,6 +665,7 @@ HTTP/1.1 200 OK |cert|必需|公钥|https 证书公钥|| |key|必需|私钥|https 证书私钥|| |sni|必需|匹配规则|https 证书SNI|| +|labels|可选|匹配规则|标识附加属性的键值对|{"version":"v2","build":"16","env":"production"}| ssl 对象 json 配置内容: diff --git a/t/admin/consumers.t b/t/admin/consumers.t index 458e396b..2dfdab3d 100644 --- a/t/admin/consumers.t +++ b/t/admin/consumers.t @@ -223,3 +223,78 @@ GET /t {"error_msg":"missing consumer name"} --- no_error_log [error] + + + +=== TEST 7: add consumer with labels +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/consumers', + ngx.HTTP_PUT, + [[{ + "username":"jack", + "desc": "new consumer", + "labels": { + "build":"16", + "env":"production", + "version":"v2" + } + }]], + [[{ + "node": { + "value": { + "username": "jack", + "desc": "new consumer", + "labels": { + "build":"16", + "env":"production", + "version":"v2" + } + } + }, + "action": "set" + }]] + ) + + ngx.status = code + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 8: invalid format of label value: set consumer +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/consumers', + ngx.HTTP_PUT, + [[{ + "username":"jack", + "desc": "new consumer", + "labels": { + "env": ["production", "release"] + } + }]] + ) + + ngx.status = code + ngx.print(body) + } + } +--- request +GET /t +--- error_code: 400 +--- response_body +{"error_msg":"invalid configuration: property \"labels\" validation failed: failed to validate env (matching \".*\"): wrong type: expected string, got table"} +--- no_error_log +[error] diff --git a/t/admin/routes.t b/t/admin/routes.t index 20c42e18..10580050 100644 --- a/t/admin/routes.t +++ b/t/admin/routes.t @@ -2185,3 +2185,144 @@ GET /t --- error_code: 400 --- no_error_log [error] + + + +=== TEST 60: set route(with labels) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "methods": ["GET"], + "upstream": { + "nodes": { + "127.0.0.1:8080": 1 + }, + "type": "roundrobin" + }, + "labels": { + "build": "16", + "env": "production", + "version": "v2" + }, + + "uri": "/index.html" + }]], + [[{ + "node": { + "value": { + "methods": [ + "GET" + ], + "uri": "/index.html", + "upstream": { + "nodes": { + "127.0.0.1:8080": 1 + }, + "type": "roundrobin" + }, + "labels": { + "build": "16", + "env": "production", + "version": "v2" + } + }, + "key": "/apisix/routes/1" + }, + "action": "set" + }]] + ) + + ngx.status = code + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 61: patch route(change labels) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PATCH, + [[{ + "labels": { + "build": "17" + } + }]], + [[{ + "node": { + "value": { + "methods": [ + "GET" + ], + "uri": "/index.html", + "upstream": { + "nodes": { + "127.0.0.1:8080": 1 + }, + "type": "roundrobin" + }, + "labels": { + "env": "production", + "version": "v2", + "build": "17" + } + }, + "key": "/apisix/routes/1" + }, + "action": "compareAndSwap" + }]] + ) + + ngx.status = code + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 62: invalid format of label value: set route +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "methods": ["GET"], + "uri": "/index.html", + "labels": { + "env": ["production", "release"] + } + }]] + ) + + ngx.status = code + ngx.print(body) + } + } +--- request +GET /t +--- error_code: 400 +--- response_body +{"error_msg":"invalid configuration: property \"labels\" validation failed: failed to validate env (matching \".*\"): wrong type: expected string, got table"} +--- no_error_log +[error] diff --git a/t/admin/services.t b/t/admin/services.t index 5dcb8ad2..00b07442 100644 --- a/t/admin/services.t +++ b/t/admin/services.t @@ -1179,3 +1179,141 @@ GET /t passed --- no_error_log [error] + + + +=== TEST 33: set service(with labels) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/services/1', + ngx.HTTP_PUT, + [[{ + "upstream": { + "nodes": { + "127.0.0.1:8080": 1 + }, + "type": "roundrobin" + }, + "labels": { + "build":"16", + "env":"production", + "version":"v2" + }, + "desc": "new service" + }]], + [[{ + "node": { + "value": { + "upstream": { + "nodes": { + "127.0.0.1:8080": 1 + }, + "type": "roundrobin" + }, + "labels": { + "build": "16", + "env": "production", + "version": "v2" + }, + "desc": "new service" + }, + "key": "/apisix/services/1" + }, + "action": "set" + }]] + ) + + ngx.status = code + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 34: patch service(change labels) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/services/1', + ngx.HTTP_PATCH, + [[{ + "labels": { + "build": "17" + } + }]], + [[{ + "node": { + "value": { + "upstream": { + "nodes": { + "127.0.0.1:8080": 1 + }, + "type": "roundrobin" + }, + "labels": { + "build": "17", + "env": "production", + "version": "v2" + }, + "desc": "new service" + }, + "key": "/apisix/services/1" + }, + "action": "compareAndSwap" + }]] + ) + + ngx.status = code + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 35: invalid format of label value: set service +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/services/1', + ngx.HTTP_PUT, + [[{ + "upstream": { + "nodes": { + "127.0.0.1:8080": 1 + }, + "type": "roundrobin" + }, + "labels": { + "env": ["production", "release"] + }, + "desc": "new service" + }]] + ) + + ngx.status = code + ngx.print(body) + } + } +--- request +GET /t +--- error_code: 400 +--- response_body +{"error_msg":"invalid configuration: property \"labels\" validation failed: failed to validate env (matching \".*\"): wrong type: expected string, got table"} +--- no_error_log +[error] diff --git a/t/admin/ssl.t b/t/admin/ssl.t index 647c9628..3cb7f18d 100644 --- a/t/admin/ssl.t +++ b/t/admin/ssl.t @@ -530,3 +530,90 @@ GET /t {"error_msg":"invalid configuration: value should match only one schema, but matches none"} --- no_error_log [error] + + + +=== TEST 15: set ssl(with labels) +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + local t = require("lib.test_admin") + + local ssl_cert = t.read_file("conf/cert/apisix.crt") + local ssl_key = t.read_file("conf/cert/apisix.key") + local data = {cert = ssl_cert, key = ssl_key, sni = "test.com", labels = { version = "v2", build = "16", env = "production"}} + + local code, body = t.test('/apisix/admin/ssl/1', + ngx.HTTP_PUT, + core.json.encode(data), + [[{ + "node": { + "value": { + "sni": "test.com", + "labels": { + "version": "v2", + "build": "16", + "env": "production" + } + }, + + "key": "/apisix/ssl/1" + }, + "action": "set" + }]] + ) + + ngx.status = code + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 16: invalid format of label value: set ssl +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + local t = require("lib.test_admin") + + local ssl_cert = t.read_file("conf/cert/apisix.crt") + local ssl_key = t.read_file("conf/cert/apisix.key") + local data = {cert = ssl_cert, key = ssl_key, sni = "test.com", labels = { env = {"production", "release"}}} + + local code, body = t.test('/apisix/admin/ssl/1', + ngx.HTTP_PUT, + core.json.encode(data), + [[{ + "node": { + "value": { + "sni": "test.com", + "labels": { + "env": ["production", "release"] + } + }, + + "key": "/apisix/ssl/1" + }, + "action": "set" + }]] + ) + + ngx.status = code + ngx.print(body) + } + } +--- request +GET /t +--- error_code: 400 +--- response_body +{"error_msg":"invalid configuration: property \"labels\" validation failed: failed to validate env (matching \".*\"): wrong type: expected string, got table"} +--- no_error_log +[error] diff --git a/t/admin/upstream.t b/t/admin/upstream.t index 92c1bb80..695c81a2 100644 --- a/t/admin/upstream.t +++ b/t/admin/upstream.t @@ -1654,7 +1654,7 @@ GET /t "type": "roundrobin", "labels": { "build":"16", - "env":"prodution", + "env":"production", "version":"v2" } }]], @@ -1667,7 +1667,7 @@ GET /t "type": "roundrobin", "labels": { "build":"16", - "env":"prodution", + "env":"production", "version":"v2" } }, @@ -1708,7 +1708,7 @@ passed "labels": { "version":"v2", "build":"16", - "env":"prodution" + "env":"production" } }, "key": "/apisix/upstreams/1" @@ -1752,7 +1752,7 @@ passed "labels": { "version":"v2", "build":"17", - "env":"prodution" + "env":"production" } }, "key": "/apisix/upstreams/1" @@ -1787,7 +1787,7 @@ passed }, "type": "roundrobin", "labels": { - "env": ["prodution", "release"] + "env": ["production", "release"] } }]] )