From f4890bdfbb96f6a1747d82479f3d026ec0856f5e Mon Sep 17 00:00:00 2001 From: kingluo Date: Mon, 21 Aug 2023 16:14:36 +0800 Subject: [PATCH 1/7] feat: add schema validate admin API --- apisix/admin/init.lua | 40 ++++++++++++++++ docs/en/latest/admin-api.md | 49 +++++++++++++++++++ t/admin/schema-validate.t | 95 +++++++++++++++++++++++++++++++++++++ 3 files changed, 184 insertions(+) create mode 100644 t/admin/schema-validate.t diff --git a/apisix/admin/init.lua b/apisix/admin/init.lua index 0d4ef932362f..6440bab5eb99 100644 --- a/apisix/admin/init.lua +++ b/apisix/admin/init.lua @@ -376,6 +376,41 @@ local function reload_plugins(data, event, source, pid) end +local function schema_validate() + local uri_segs = core.utils.split_uri(ngx.var.uri) + core.log.info("uri: ", core.json.delay_encode(uri_segs)) + + local seg_res = uri_segs[6] + local resource = resources[seg_res] + if not resource then + core.response.exit(404, {error_msg = "Unsupported resource type: ".. seg_res}) + end + + local req_body, err = core.request.get_body(MAX_REQ_BODY) + if err then + core.log.error("failed to read request body: ", err) + core.response.exit(400, {error_msg = "invalid request body: " .. err}) + end + + if req_body then + local data, err = core.json.decode(req_body) + if err then + core.log.error("invalid request body: ", req_body, " err: ", err) + core.response.exit(400, {error_msg = "invalid request body: " .. err, + req_body = req_body}) + end + + req_body = data + end + + local ok, err = core.schema.check(resource.schema, req_body) + if ok then + core.response.exit(200) + end + core.response.exit(400, {err = err}) +end + + local uri_route = { { paths = [[/apisix/admin]], @@ -392,6 +427,11 @@ local uri_route = { methods = {"GET"}, handler = get_plugins_list, }, + { + paths = [[/apisix/admin/schema/validate/*]], + methods = {"POST"}, + handler = schema_validate, + }, { paths = reload_event, methods = {"PUT"}, diff --git a/docs/en/latest/admin-api.md b/docs/en/latest/admin-api.md index 787a61e98896..0bdff6981c7c 100644 --- a/docs/en/latest/admin-api.md +++ b/docs/en/latest/admin-api.md @@ -1514,3 +1514,52 @@ Proto resource request address: /apisix/admin/protos/{id} | content | True | String | content of `.proto` or `.pb` files | See [here](./plugins/grpc-transcode.md#enabling-the-plugin) | | create_time | False | Epoch timestamp (in seconds) of the created time. If missing, this field will be populated automatically. | 1602883670 | | update_time | False | Epoch timestamp (in seconds) of the updated time. If missing, this field will be populated automatically. | 1602883670 | + +## Schema validate + +Check the validity of a configuration against its entity schema. This allows you to test your input before submitting a request to the entity endpoints of the Admin API. + +Note that this only performs the schema validation checks, checking that the input configuration is well-formed. Requests to the entity endpoint using the given configuration may still fail due to other reasons, such as invalid foreign key relationships or uniqueness check failures against the contents of the data store. + +### Schema validate API + +Schema validate request address: /apisix/admin/schema/validate/{resource} + +### Request Methods + +| Method | Request URI | Request Body | Description | +| ------ | -------------------------------- | ------------ | ----------------------------------------------- | +| POST | /apisix/admin/schema/validate/{resource} | {..resource conf..} | Validate the resource configuration against corresponding schema. | + +### Request Body Parameters + +* 200: validate ok. +* 400: validate failed, with error as response body in JSON format. + +Example: + +```bash +curl http://127.0.0.1:9180/apisix/admin/schema/validate/routes \ + -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X POST -i -d '{ + "uri": 1980, + "upstream": { + "scheme": "https", + "type": "roundrobin", + "nodes": { + "nghttp2.org": 1 + } + } +}' +HTTP/1.1 400 Bad Request +Date: Mon, 21 Aug 2023 07:37:13 GMT +Content-Type: application/json +Transfer-Encoding: chunked +Connection: keep-alive +Server: APISIX/3.4.0 +Access-Control-Allow-Origin: * +Access-Control-Allow-Credentials: true +Access-Control-Expose-Headers: * +Access-Control-Max-Age: 3600 + +{"err":"property \"uri\" validation failed: wrong type: expected string, got number"} +``` diff --git a/t/admin/schema-validate.t b/t/admin/schema-validate.t new file mode 100644 index 000000000000..dcd6d8dd3856 --- /dev/null +++ b/t/admin/schema-validate.t @@ -0,0 +1,95 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +use t::APISIX 'no_plan'; + +repeat_each(1); +no_long_string(); +no_root_location(); +no_shuffle(); +log_level("warn"); + +add_block_preprocessor(sub { + my ($block) = @_; + + if (!$block->request) { + $block->set_value("request", "GET /t"); + } +}); + +run_tests; + +__DATA__ + +=== TEST 1: validate ok +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/schema/validate/routes', + ngx.HTTP_POST, + [[{ + "uri": "/httpbin/*", + "upstream": { + "scheme": "https", + "type": "roundrobin", + "nodes": { + "nghttp2.org": 1 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + ngx.say(body) + return + end + } +} +--- error_code: 200 + + + +=== TEST 2: validate failed, wrong uri type +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/schema/validate/routes', + ngx.HTTP_POST, + [[{ + "uri": 666, + "upstream": { + "scheme": "https", + "type": "roundrobin", + "nodes": { + "nghttp2.org": 1 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + ngx.say(body) + return + end + } +} +--- error_code: 400 +--- response +{"err": {"property \"uri\" validation failed: wrong type: expected string, got number"}} From 196c67bafb001fa383f482cabf1a595481491f08 Mon Sep 17 00:00:00 2001 From: kingluo Date: Mon, 21 Aug 2023 16:38:57 +0800 Subject: [PATCH 2/7] change err to error_msg --- apisix/admin/init.lua | 2 +- docs/en/latest/admin-api.md | 2 +- t/admin/schema-validate.t | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apisix/admin/init.lua b/apisix/admin/init.lua index 6440bab5eb99..333c798e6ada 100644 --- a/apisix/admin/init.lua +++ b/apisix/admin/init.lua @@ -407,7 +407,7 @@ local function schema_validate() if ok then core.response.exit(200) end - core.response.exit(400, {err = err}) + core.response.exit(400, {error_msg = err}) end diff --git a/docs/en/latest/admin-api.md b/docs/en/latest/admin-api.md index 0bdff6981c7c..cc9d60122cce 100644 --- a/docs/en/latest/admin-api.md +++ b/docs/en/latest/admin-api.md @@ -1561,5 +1561,5 @@ Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: * Access-Control-Max-Age: 3600 -{"err":"property \"uri\" validation failed: wrong type: expected string, got number"} +{"error_msg":"property \"uri\" validation failed: wrong type: expected string, got number"} ``` diff --git a/t/admin/schema-validate.t b/t/admin/schema-validate.t index dcd6d8dd3856..d4408523c814 100644 --- a/t/admin/schema-validate.t +++ b/t/admin/schema-validate.t @@ -92,4 +92,4 @@ location /t { } --- error_code: 400 --- response -{"err": {"property \"uri\" validation failed: wrong type: expected string, got number"}} +{"error_msg": {"property \"uri\" validation failed: wrong type: expected string, got number"}} From 466f99d85b8ab376eeb11fa9e987a3ed5004ab0d Mon Sep 17 00:00:00 2001 From: jinhua luo Date: Mon, 21 Aug 2023 23:54:18 +0800 Subject: [PATCH 3/7] Update docs/en/latest/admin-api.md Co-authored-by: Abhishek Choudhary --- docs/en/latest/admin-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/latest/admin-api.md b/docs/en/latest/admin-api.md index cc9d60122cce..09febd051f70 100644 --- a/docs/en/latest/admin-api.md +++ b/docs/en/latest/admin-api.md @@ -1515,7 +1515,7 @@ Proto resource request address: /apisix/admin/protos/{id} | create_time | False | Epoch timestamp (in seconds) of the created time. If missing, this field will be populated automatically. | 1602883670 | | update_time | False | Epoch timestamp (in seconds) of the updated time. If missing, this field will be populated automatically. | 1602883670 | -## Schema validate +## Schema validation Check the validity of a configuration against its entity schema. This allows you to test your input before submitting a request to the entity endpoints of the Admin API. From 231e992456b2abb721b16a41987cd4e240dc29e1 Mon Sep 17 00:00:00 2001 From: jinhua luo Date: Mon, 21 Aug 2023 23:54:34 +0800 Subject: [PATCH 4/7] Update docs/en/latest/admin-api.md Co-authored-by: Abhishek Choudhary --- docs/en/latest/admin-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/latest/admin-api.md b/docs/en/latest/admin-api.md index 09febd051f70..b010394fa315 100644 --- a/docs/en/latest/admin-api.md +++ b/docs/en/latest/admin-api.md @@ -1521,7 +1521,7 @@ Check the validity of a configuration against its entity schema. This allows you Note that this only performs the schema validation checks, checking that the input configuration is well-formed. Requests to the entity endpoint using the given configuration may still fail due to other reasons, such as invalid foreign key relationships or uniqueness check failures against the contents of the data store. -### Schema validate API +### Schema validation Schema validate request address: /apisix/admin/schema/validate/{resource} From bd4b4d85397245d043b06ba6061e46e88e575673 Mon Sep 17 00:00:00 2001 From: jinhua luo Date: Mon, 21 Aug 2023 23:54:42 +0800 Subject: [PATCH 5/7] Update docs/en/latest/admin-api.md Co-authored-by: Abhishek Choudhary --- docs/en/latest/admin-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/latest/admin-api.md b/docs/en/latest/admin-api.md index b010394fa315..6ce222a82bfb 100644 --- a/docs/en/latest/admin-api.md +++ b/docs/en/latest/admin-api.md @@ -1523,7 +1523,7 @@ Note that this only performs the schema validation checks, checking that the inp ### Schema validation -Schema validate request address: /apisix/admin/schema/validate/{resource} +Schema validation request address: /apisix/admin/schema/validate/{resource} ### Request Methods From ac0757bee7a95424d28871c745405b670e4a5014 Mon Sep 17 00:00:00 2001 From: kingluo Date: Tue, 22 Aug 2023 10:31:17 +0800 Subject: [PATCH 6/7] add more test cases --- t/admin/schema-validate.t | 251 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) diff --git a/t/admin/schema-validate.t b/t/admin/schema-validate.t index d4408523c814..006db9590776 100644 --- a/t/admin/schema-validate.t +++ b/t/admin/schema-validate.t @@ -93,3 +93,254 @@ location /t { --- error_code: 400 --- response {"error_msg": {"property \"uri\" validation failed: wrong type: expected string, got number"}} + + + +=== TEST 3: validate failed, length limit +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/schema/validate/routes', + ngx.HTTP_POST, + [[{ + "uri": "", + "upstream": { + "scheme": "https", + "type": "roundrobin", + "nodes": { + "nghttp2.org": 1 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + ngx.say(body) + return + end + } +} +--- error_code: 400 +--- response +{"error_msg":"property \"uri\" validation failed: string too short, expected at least 1, got 0"} + + + +=== TEST 4: validate failed, array type expected +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/schema/validate/routes', + ngx.HTTP_POST, + [[{ + "uris": "foobar", + "upstream": { + "scheme": "https", + "type": "roundrobin", + "nodes": { + "nghttp2.org": 1 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + ngx.say(body) + return + end + } +} +--- error_code: 400 +--- response +{"error_msg":"property \"uris\" validation failed: wrong type: expected array, got string"} + + + +=== TEST 5: validate failed, array size limit +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/schema/validate/routes', + ngx.HTTP_POST, + [[{ + "uris": [], + "upstream": { + "scheme": "https", + "type": "roundrobin", + "nodes": { + "nghttp2.org": 1 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + ngx.say(body) + return + end + } +} +--- error_code: 400 +--- response +{"error_msg":"property \"uris\" validation failed: expect array to have at least 1 items"} + + + +=== TEST 6: validate failed, array unique items +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/schema/validate/routes', + ngx.HTTP_POST, + [[{ + "uris": ["/foo", "/foo"], + "upstream": { + "scheme": "https", + "type": "roundrobin", + "nodes": { + "nghttp2.org": 1 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + ngx.say(body) + return + end + } +} +--- error_code: 400 +--- response +{"error_msg":"property \"uris\" validation failed: expected unique items but items 1 and 2 are equal"} + + + +=== TEST 7: validate failed, uri or uris is mandatory +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/schema/validate/routes', + ngx.HTTP_POST, + [[{ + "upstream": { + "scheme": "https", + "type": "roundrobin", + "nodes": { + "nghttp2.org": 1 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + ngx.say(body) + return + end + } +} +--- error_code: 400 +--- response +{"error_msg":"allOf 1 failed: value should match only one schema, but matches none"} + + + +=== TEST 8: validate failed, enum check +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/schema/validate/routes', + ngx.HTTP_POST, + [[{ + "status": 3, + "uri": "/foo", + "upstream": { + "scheme": "https", + "type": "roundrobin", + "nodes": { + "nghttp2.org": 1 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + ngx.say(body) + return + end + } +} +--- error_code: 400 +--- response +{"error_msg":"property \"status\" validation failed: matches none of the enum values"} + + + +=== TEST 9: validate failed, wrong combination +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/schema/validate/routes', + ngx.HTTP_POST, + [[{ + "script": "xxxxxxxxxxxxxxxxxxxxx", + "plugin_config_id": "foo" + }]] + ) + + if code >= 300 then + ngx.status = code + ngx.say(body) + return + end + } +} +--- error_code: 400 +--- response +{"error_msg":"allOf 1 failed: value should match only one schema, but matches none"} + + + +=== TEST 10: validate failed, id_schema check +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/schema/validate/routes', + ngx.HTTP_POST, + [[{ + "plugin_config_id": "@@@@@@@@@@@@@@@@", + "uri": "/foo", + "upstream": { + "scheme": "https", + "type": "roundrobin", + "nodes": { + "nghttp2.org": 1 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + ngx.say(body) + return + end + } +} +--- error_code: 400 +--- response +{"error_msg":"property \"plugin_config_id\" validation failed: object matches none of the required"} From aca82be9705452dc813c90d2b66c825c76eee0c6 Mon Sep 17 00:00:00 2001 From: kingluo Date: Tue, 22 Aug 2023 11:01:19 +0800 Subject: [PATCH 7/7] add more test cases --- t/admin/schema-validate.t | 54 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/t/admin/schema-validate.t b/t/admin/schema-validate.t index 006db9590776..46f51021edfd 100644 --- a/t/admin/schema-validate.t +++ b/t/admin/schema-validate.t @@ -344,3 +344,57 @@ location /t { --- error_code: 400 --- response {"error_msg":"property \"plugin_config_id\" validation failed: object matches none of the required"} + + + +=== TEST 11: upstream ok +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/schema/validate/upstreams', + ngx.HTTP_POST, + [[{ + "nodes":{ + "nghttp2.org":100 + }, + "type":"roundrobin" + }]] + ) + + if code >= 300 then + ngx.status = code + ngx.say(body) + return + end + } +} +--- error_code: 200 + + + +=== TEST 12: upstream failed, wrong nodes format +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/schema/validate/upstreams', + ngx.HTTP_POST, + [[{ + "nodes":[ + "nghttp2.org" + ], + "type":"roundrobin" + }]] + ) + + if code >= 300 then + ngx.status = code + ngx.say(body) + return + end + } +} +--- error_code: 400 +--- response +{"error_msg":"allOf 1 failed: value should match only one schema, but matches none"}