From 7f29e97c4cefcd33402a61aa624ff6b68fa82b3b Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Wed, 20 Jan 2021 17:39:47 +0800 Subject: [PATCH 1/7] feat(fault-injection): support conditional fault injection using Nginx variables close #2511 --- apisix/plugins/fault-injection.lua | 87 ++++++++++- doc/plugins/fault-injection.md | 222 +++++++++++++++++++++++++++ doc/zh-cn/plugins/fault-injection.md | 222 +++++++++++++++++++++++++++ t/plugin/fault-injection.t | 75 +++++++++ 4 files changed, 602 insertions(+), 4 deletions(-) diff --git a/apisix/plugins/fault-injection.lua b/apisix/plugins/fault-injection.lua index 7a5afb155331..9a179171277d 100644 --- a/apisix/plugins/fault-injection.lua +++ b/apisix/plugins/fault-injection.lua @@ -15,6 +15,7 @@ -- limitations under the License. -- local core = require("apisix.core") +local expr = require("resty.expr.v1") local sleep = core.sleep local random = math.random @@ -22,6 +23,53 @@ local random = math.random local plugin_name = "fault-injection" +local vars_schema = { + type = "array", + items = { + type = "array", + items = { + { + type = "string", + minLength = 1, + maxLength = 100 + }, + { + type = "string", + minLength = 1, + maxLength = 2 + } + }, + additionalItems = { + anyOf = { + {type = "string"}, + {type = "number"}, + {type = "boolean"}, + { + type = "array", + items = { + anyOf = { + { + type = "string", + minLength = 1, maxLength = 100 + }, + { + type = "number" + }, + { + type = "boolean" + } + } + }, + uniqueItems = true + } + } + }, + minItems = 0, + maxItems = 10 + } +} + + local schema = { type = "object", properties = { @@ -30,7 +78,8 @@ local schema = { properties = { http_status = {type = "integer", minimum = 200}, body = {type = "string", minLength = 0}, - percentage = {type = "integer", minimum = 0, maximum = 100} + percentage = {type = "integer", minimum = 0, maximum = 100}, + vars = vars_schema }, required = {"http_status"}, }, @@ -38,7 +87,8 @@ local schema = { type = "object", properties = { duration = {type = "number", minimum = 0}, - percentage = {type = "integer", minimum = 0, maximum = 100} + percentage = {type = "integer", minimum = 0, maximum = 100}, + vars = vars_schema }, required = {"duration"}, } @@ -64,6 +114,16 @@ local function sample_hit(percentage) end +local function vars_match(rules, ctx) + local expr, err = expr.new(rules) + if err then + core.log.error("vars expression does not match: ", err) + return nil, err + end + return expr:eval(ctx.var), nil +end + + function _M.check_schema(conf) local ok, err = core.schema.check(schema, conf) if not ok then @@ -77,11 +137,30 @@ end function _M.rewrite(conf, ctx) core.log.info("plugin rewrite phase, conf: ", core.json.delay_encode(conf)) - if conf.delay and sample_hit(conf.delay.percentage) then + local err + local abort_vars = true + if conf.abort and conf.abort.vars then + abort_vars, err = vars_match(conf.abort.vars, ctx) + if err then + return 500, err + end + end + core.log.info("abort_vars: ", abort_vars) + + local delay_vars = true + if conf.delay and conf.delay.vars then + delay_vars, err = vars_match(conf.delay.vars, ctx) + if err then + return 500, err + end + end + core.log.info("delay_vars: ", delay_vars) + + if conf.delay and sample_hit(conf.delay.percentage) and delay_vars then sleep(conf.delay.duration) end - if conf.abort and sample_hit(conf.abort.percentage) then + if conf.abort and sample_hit(conf.abort.percentage) and abort_vars then return conf.abort.http_status, core.utils.resolve_var(conf.abort.body, ctx.var) end end diff --git a/doc/plugins/fault-injection.md b/doc/plugins/fault-injection.md index 3e96c27a738f..88be29842f7b 100644 --- a/doc/plugins/fault-injection.md +++ b/doc/plugins/fault-injection.md @@ -30,8 +30,10 @@ Fault injection plugin, this plugin can be used with other plugins and will be e | abort.http_status | integer | required | | [200, ...] | user-specified http code returned to the client. | | abort.body | string | optional | | | response data returned to the client. Nginx variable can be used inside, like `client addr: $remote_addr\n` | | abort.percentage | integer | optional | | [0, 100] | percentage of requests to be aborted. | +| abort.vars | array[] | optional | | | The rules for executing fault injection will only be executed when the rules are matched. `vars` is a list of one or more {var, operator, val} elements, like this: {{var, operator, val}, {var, operator, val}, ...}}. For example: {"arg_name", "==", "json"}, which means that the current request parameter name is json. The var here is consistent with the naming of Nginx internal variables, so request_uri, host, etc. can also be used; for the operator part, the currently supported operators are ==, ~=, ~~, >, <, in, has and !. For the specific usage of operators, please see the `operator-list` part of [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list). | | delay.duration | number | required | | | delay time (can be decimal). | | delay.percentage | integer | optional | | [0, 100] | percentage of requests to be delayed. | +| delay.vars | array[] | optional | | | Execute the request delay rule, and the request will be delayed only after the rule matches. `vars` is a list of one or more {var, operator, val} elements, like this: {{var, operator, val}, {var, operator, val}, ...}}. For example: {"arg_name", "==", "json"}, which means that the current request parameter name is json. The var here is consistent with the naming of Nginx internal variables, so request_uri, host, etc. can also be used; for the operator part, the currently supported operators are ==, ~=, ~~, >, <, in, has and !. For the specific usage of operators, please see the `operator-list` part of [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list). | Note: One of `abort` and `delay` must be specified. @@ -119,6 +121,226 @@ user 0m0.007s sys 0m0.010s ``` +Example 3: Enable the `fault-injection` plugin for a specific route and specify the vars rule of the abort parameter. + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "plugins": { + "fault-injection": { + "abort": { + "http_status": 403, + "body": "Fault Injection!\n", + "vars": [ + [ "arg_name","==","jack" ] + ] + } + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" +}' +``` + +Test plugin: + +1. The vars rule fails to match, and the request returns upstream response data: + +```shell +$ curl http://127.0.0.1:9080/hello?name=allen -i +HTTP/1.1 200 OK +Content-Type: application/octet-stream +Transfer-Encoding: chunked +Connection: keep-alive +Date: Wed, 20 Jan 2021 07:21:57 GMT +Server: APISIX/2.2 + +hello +``` + +2. The vars rule is successfully matched and fault injection is performed: + +```shell +$ curl http://127.0.0.1:9080/hello?name=jack -i +HTTP/1.1 403 Forbidden +Date: Wed, 20 Jan 2021 07:23:37 GMT +Content-Type: text/plain; charset=utf-8 +Transfer-Encoding: chunked +Connection: keep-alive +Server: APISIX/2.2 + +Fault Injection! +``` + +Example 4: Enable the `fault-injection` plugin for a specific route and specify the vars rule for the delay parameter. + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "plugins": { + "fault-injection": { + "delay": { + "duration": 2, + "vars": [ + [ "arg_name","==","jack" ] + ] + } + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" +}' +``` + +1. The vars rule fails to match and the request is not delayed: + +```shell +$ time curl http://127.0.0.1:9080/hello?name=allen -i +HTTP/1.1 200 OK +Content-Type: application/octet-stream +Transfer-Encoding: chunked +Connection: keep-alive +Date: Wed, 20 Jan 2021 07:26:17 GMT +Server: APISIX/2.2 + +hello + +real 0m0.007s +user 0m0.003s +sys 0m0.003s +``` + +2. The vars rule is successfully matched, and the request is delayed for two seconds: + +```shell +$ time curl http://127.0.0.1:9080/hello?name=jack -iHTTP/1.1 200 OK +Content-Type: application/octet-stream +Transfer-Encoding: chunked +Connection: keep-alive +Date: Wed, 20 Jan 2021 07:57:50 GMT +Server: APISIX/2.2 + +hello + +real 0m2.009s +user 0m0.004s +sys 0m0.004s +``` + +Example 5: Enable the `fault-injection` plugin for a specific route, and specify the vars rules for the abort and delay parameters. + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "plugins": { + "fault-injection": { + "abort": { + "http_status": 403, + "body": "Fault Injection!\n", + "vars": [ + [ "arg_name","==","jack" ] + ] + }, + "delay": { + "duration": 2, + "vars": [ + [ "http_age","==","18" ] + ] + } + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" +}' +``` + +1. The vars rules of abort and delay fail to match: + +```shell +$ time curl http://127.0.0.1:9080/hello?name=allen -H 'age: 20' -i +HTTP/1.1 200 OK +Content-Type: application/octet-stream +Transfer-Encoding: chunked +Connection: keep-alive +Date: Wed, 20 Jan 2021 08:01:43 GMT +Server: APISIX/2.2 + +hello + +real 0m0.007s +user 0m0.003s +sys 0m0.003s +``` + +2. The abort vars rule fails to match, no fault injection is performed, but the request is delayed: + +```shell +$ time curl http://127.0.0.1:9080/hello?name=allen -H 'age: 18' -i +HTTP/1.1 200 OK +Content-Type: application/octet-stream +Transfer-Encoding: chunked +Connection: keep-alive +Date: Wed, 20 Jan 2021 08:19:03 GMT +Server: APISIX/2.2 + +hello + +real 0m2.009s +user 0m0.001s +sys 0m0.006s +``` + +3. The vars rule of delay fails to match, the request is not delayed, but fault injection is performed: + +```shell +time curl http://127.0.0.1:9080/hello?name=jack -H 'age: 20' -i +HTTP/1.1 403 Forbidden +Date: Wed, 20 Jan 2021 08:20:18 GMT +Content-Type: text/plain; charset=utf-8 +Transfer-Encoding: chunked +Connection: keep-alive +Server: APISIX/2.2 + +Fault Injection! + +real 0m0.007s +user 0m0.002s +sys 0m0.004s +``` + +4. The vars rules of abort and delay parameters match successfully, perform fault injection, and delay the request: + +```shell +$ time curl http://127.0.0.1:9080/hello?name=jack -H 'age: 18' -i +HTTP/1.1 403 Forbidden +Date: Wed, 20 Jan 2021 08:21:17 GMT +Content-Type: text/plain; charset=utf-8 +Transfer-Encoding: chunked +Connection: keep-alive +Server: APISIX/2.2 + +Fault Injection! + +real 0m2.006s +user 0m0.001s +sys 0m0.005s +``` + ## Disable Plugin Remove the corresponding JSON in the plugin configuration to disable the plugin immediately without restarting the service: diff --git a/doc/zh-cn/plugins/fault-injection.md b/doc/zh-cn/plugins/fault-injection.md index 45e5936279d3..60b826f4d185 100644 --- a/doc/zh-cn/plugins/fault-injection.md +++ b/doc/zh-cn/plugins/fault-injection.md @@ -30,8 +30,10 @@ | abort.http_status | integer | 必需 | | [200, ...] | 返回给客户端的 http 状态码 | | abort.body | string | 可选 | | | 返回给客户端的响应数据。支持使用 Nginx 变量,如 `client addr: $remote_addr\n`| | abort.percentage | integer | 可选 | | [0, 100] | 将被中断的请求占比 | +| abort.vars | array[] | 可选 | | | 执行故障注入的规则,当规则匹配通过后才会执行故障注。`vars` 由一个或多个{var, operator, val}元素组成的列表,类似这样:{{var, operator, val}, {var, operator, val}, ...}}。例如:{"arg_name", "==", "json"},表示当前请求参数 name 是 json。这里的 var 与 Nginx 内部自身变量命名是保持一致,所以也可以使用 request_uri、host 等;对于 operator 部分,目前已支持的运算符有 ==、~=、~~、>、<、in、has 和 ! 。操作符的具体用法请看 [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list) 的 `operator-list` 部分。 | | delay.duration | number | 必需 | | | 延迟时间,可以指定小数 | | delay.percentage | integer | 可选 | | [0, 100] | 将被延迟的请求占比 | +| delay.vars | array[] | 可选 | | [0, 100] | 执行请求延迟的规则,当规则匹配通过后才会延迟请求。`vars` 由一个或多个{var, operator, val}元素组成的列表,类似这样:{{var, operator, val}, {var, operator, val}, ...}}。例如:{"arg_name", "==", "json"},表示当前请求参数 name 是 json。这里的 var 与 Nginx 内部自身变量命名是保持一致,所以也可以使用 request_uri、host 等;对于 operator 部分,目前已支持的运算符有 ==、~=、~~、>、<、in、has 和 ! 。操作符的具体用法请看 [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list) 的 `operator-list` 部分。 | 注:参数 abort 和 delay 至少要存在一个。 @@ -119,6 +121,226 @@ user 0m0.007s sys 0m0.010s ``` +示例3:为特定路由启用 `fault-injection` 插件,并指定 abort 参数的 vars 规则。 + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "plugins": { + "fault-injection": { + "abort": { + "http_status": 403, + "body": "Fault Injection!\n", + "vars": [ + [ "arg_name","==","jack" ] + ] + } + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" +}' +``` + +测试: + +1、vars 规则匹配失败,请求返回上游响应数据: + +```shell +$ curl http://127.0.0.1:9080/hello?name=allen -i +HTTP/1.1 200 OK +Content-Type: application/octet-stream +Transfer-Encoding: chunked +Connection: keep-alive +Date: Wed, 20 Jan 2021 07:21:57 GMT +Server: APISIX/2.2 + +hello +``` + +2、vars 规则匹配成功,执行故障注入: + +```shell +$ curl http://127.0.0.1:9080/hello?name=jack -i +HTTP/1.1 403 Forbidden +Date: Wed, 20 Jan 2021 07:23:37 GMT +Content-Type: text/plain; charset=utf-8 +Transfer-Encoding: chunked +Connection: keep-alive +Server: APISIX/2.2 + +Fault Injection! +``` + +示例4:为特定路由启用 `fault-injection` 插件,并指定 delay 参数的 vars 规则。 + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "plugins": { + "fault-injection": { + "delay": { + "duration": 2, + "vars": [ + [ "arg_name","==","jack" ] + ] + } + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" +}' +``` + +1、vars 规则匹配失败,不延迟请求: + +```shell +$ time curl http://127.0.0.1:9080/hello?name=allen -i +HTTP/1.1 200 OK +Content-Type: application/octet-stream +Transfer-Encoding: chunked +Connection: keep-alive +Date: Wed, 20 Jan 2021 07:26:17 GMT +Server: APISIX/2.2 + +hello + +real 0m0.007s +user 0m0.003s +sys 0m0.003s +``` + +2、vars 规则匹配成功,延迟请求两秒: + +```shell +$ time curl http://127.0.0.1:9080/hello?name=jack -iHTTP/1.1 200 OK +Content-Type: application/octet-stream +Transfer-Encoding: chunked +Connection: keep-alive +Date: Wed, 20 Jan 2021 07:57:50 GMT +Server: APISIX/2.2 + +hello + +real 0m2.009s +user 0m0.004s +sys 0m0.004s +``` + +示例5:为特定路由启用 `fault-injection` 插件,并指定 abort 和 delay 参数的 vars 规则。 + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "plugins": { + "fault-injection": { + "abort": { + "http_status": 403, + "body": "Fault Injection!\n", + "vars": [ + [ "arg_name","==","jack" ] + ] + }, + "delay": { + "duration": 2, + "vars": [ + [ "http_age","==","18" ] + ] + } + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" +}' +``` + +1、abort 和 delay 的 vars 规则匹配失败: + +```shell +$ time curl http://127.0.0.1:9080/hello?name=allen -H 'age: 20' -i +HTTP/1.1 200 OK +Content-Type: application/octet-stream +Transfer-Encoding: chunked +Connection: keep-alive +Date: Wed, 20 Jan 2021 08:01:43 GMT +Server: APISIX/2.2 + +hello + +real 0m0.007s +user 0m0.003s +sys 0m0.003s +``` + +2、abort 的 vars 规则匹配失败,不执行故障注入,但延迟请求: + +```shell +$ time curl http://127.0.0.1:9080/hello?name=allen -H 'age: 18' -i +HTTP/1.1 200 OK +Content-Type: application/octet-stream +Transfer-Encoding: chunked +Connection: keep-alive +Date: Wed, 20 Jan 2021 08:19:03 GMT +Server: APISIX/2.2 + +hello + +real 0m2.009s +user 0m0.001s +sys 0m0.006s +``` + +3、delay 的 vars 规则匹配失败,不延迟请求,但执行故障注入: + +```shell +time curl http://127.0.0.1:9080/hello?name=jack -H 'age: 20' -i +HTTP/1.1 403 Forbidden +Date: Wed, 20 Jan 2021 08:20:18 GMT +Content-Type: text/plain; charset=utf-8 +Transfer-Encoding: chunked +Connection: keep-alive +Server: APISIX/2.2 + +Fault Injection! + +real 0m0.007s +user 0m0.002s +sys 0m0.004s +``` + +4、abort 和 delay 参数的 vars 规则匹配成功,执行故障注入,并延迟请求: + +```shell +$ time curl http://127.0.0.1:9080/hello?name=jack -H 'age: 18' -i +HTTP/1.1 403 Forbidden +Date: Wed, 20 Jan 2021 08:21:17 GMT +Content-Type: text/plain; charset=utf-8 +Transfer-Encoding: chunked +Connection: keep-alive +Server: APISIX/2.2 + +Fault Injection! + +real 0m2.006s +user 0m0.001s +sys 0m0.005s +``` + ### 禁用插件 移除插件配置中相应的 JSON 配置可立即禁用该插件,无需重启服务: diff --git a/t/plugin/fault-injection.t b/t/plugin/fault-injection.t index 439381140b20..433dedb42053 100644 --- a/t/plugin/fault-injection.t +++ b/t/plugin/fault-injection.t @@ -741,3 +741,78 @@ GET /hello --- response_body --- no_error_log [error] + + + +=== TEST 24: set route and configure the vars rule in abort(There is only one vars rule, and the operator is `==`.) +--- 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, + [[{ + "plugins": { + "fault-injection": { + "abort": { + "http_status": 403, + "body": "Fault Injection!\n", + "vars": [ + [ "arg_name","==","jack" ] + ] + } + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- error_code: 200 +--- response_body +passed +--- no_error_log +[error] +--- LAST + + +=== TEST 21: hit route(Missing request parameter name.) +--- request +GET /hello +--- response_body +hello world +--- no_error_log +[error] + + + +=== TEST 22: hit route(The rule of vars failed to match.) +--- request +GET /hello?name=jakc +--- response_body +hello world +--- no_error_log +[error] +--- LAST + + +=== TEST 23: hit route(The rules of vars match successfully.) +--- request +GET /hello?name=jack +--- error_code: 403 +--- response_body +Fault Injection! +--- no_error_log +[error] From 7027c36eb4ca17221125cf5326c5762bc48a0a59 Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Fri, 22 Jan 2021 19:28:04 +0800 Subject: [PATCH 2/7] update code and docs. --- apisix/plugins/fault-injection.lua | 97 +++++++++-------- doc/plugins/fault-injection.md | 149 ++++++++++++++++++++++++--- doc/zh-cn/plugins/fault-injection.md | 149 ++++++++++++++++++++++++--- t/plugin/fault-injection.t | 21 ++-- 4 files changed, 338 insertions(+), 78 deletions(-) diff --git a/apisix/plugins/fault-injection.lua b/apisix/plugins/fault-injection.lua index 9a179171277d..1c4d00bfadce 100644 --- a/apisix/plugins/fault-injection.lua +++ b/apisix/plugins/fault-injection.lua @@ -28,45 +28,49 @@ local vars_schema = { items = { type = "array", items = { - { - type = "string", - minLength = 1, - maxLength = 100 - }, - { - type = "string", - minLength = 1, - maxLength = 2 - } - }, - additionalItems = { - anyOf = { - {type = "string"}, - {type = "number"}, - {type = "boolean"}, + type = "array", + items = { { - type = "array", - items = { - anyOf = { - { - type = "string", - minLength = 1, maxLength = 100 - }, - { - type = "number" - }, - { - type = "boolean" + type = "string", + minLength = 1, + maxLength = 100 + }, + { + type = "string", + minLength = 1, + maxLength = 2 + } + }, + additionalItems = { + anyOf = { + {type = "string"}, + {type = "number"}, + {type = "boolean"}, + { + type = "array", + items = { + anyOf = { + { + type = "string", + minLength = 1, maxLength = 100 + }, + { + type = "number" + }, + { + type = "boolean" + } } - } - }, - uniqueItems = true + }, + uniqueItems = true + } } - } - }, - minItems = 0, - maxItems = 10 - } + }, + minItems = 0, + maxItems = 10 + } + }, + maxItems = 20 } @@ -114,13 +118,22 @@ local function sample_hit(percentage) end -local function vars_match(rules, ctx) - local expr, err = expr.new(rules) - if err then - core.log.error("vars expression does not match: ", err) - return nil, err +local function vars_match(vars, ctx) + local match_result + for _, var in ipairs(vars) do + local expr, err = expr.new(var) + if err then + core.log.error("vars expression does not match: ", err) + return nil, err + end + + match_result = expr:eval(ctx.var) + if match_result then + break + end end - return expr:eval(ctx.var), nil + + return match_result, nil end diff --git a/doc/plugins/fault-injection.md b/doc/plugins/fault-injection.md index 88be29842f7b..003cb65a8bcf 100644 --- a/doc/plugins/fault-injection.md +++ b/doc/plugins/fault-injection.md @@ -30,13 +30,43 @@ Fault injection plugin, this plugin can be used with other plugins and will be e | abort.http_status | integer | required | | [200, ...] | user-specified http code returned to the client. | | abort.body | string | optional | | | response data returned to the client. Nginx variable can be used inside, like `client addr: $remote_addr\n` | | abort.percentage | integer | optional | | [0, 100] | percentage of requests to be aborted. | -| abort.vars | array[] | optional | | | The rules for executing fault injection will only be executed when the rules are matched. `vars` is a list of one or more {var, operator, val} elements, like this: {{var, operator, val}, {var, operator, val}, ...}}. For example: {"arg_name", "==", "json"}, which means that the current request parameter name is json. The var here is consistent with the naming of Nginx internal variables, so request_uri, host, etc. can also be used; for the operator part, the currently supported operators are ==, ~=, ~~, >, <, in, has and !. For the specific usage of operators, please see the `operator-list` part of [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list). | +| abort.vars | array[] | optional | | | The rules for executing fault injection will only be executed when the rules are matched. `vars` is a list consisting of one or more [[var, operator, val]] elements, like this: [[[var, operator, val],[var, operator, val]], ...]. For example: [[["arg_name", "==", "json"]]], indicating that the current request parameter name is json. The var here is consistent with the naming of Nginx internal variables, so request_uri, host, etc. can also be used; for the operator part, the currently supported operators are ==, ~=, ~~, >, <, in, has and !. For specific usage of operators, please see the `operator-list` part of [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list). | | delay.duration | number | required | | | delay time (can be decimal). | | delay.percentage | integer | optional | | [0, 100] | percentage of requests to be delayed. | -| delay.vars | array[] | optional | | | Execute the request delay rule, and the request will be delayed only after the rule matches. `vars` is a list of one or more {var, operator, val} elements, like this: {{var, operator, val}, {var, operator, val}, ...}}. For example: {"arg_name", "==", "json"}, which means that the current request parameter name is json. The var here is consistent with the naming of Nginx internal variables, so request_uri, host, etc. can also be used; for the operator part, the currently supported operators are ==, ~=, ~~, >, <, in, has and !. For the specific usage of operators, please see the `operator-list` part of [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list). | +| delay.vars | array[] | optional | | | Execute the request delay rule, and the request will be delayed only after the rule matches. `vars` is a list consisting of one or more [[var, operator, val]] elements, like this: [[[var, operator, val],[var, operator, val]], ...]. For example: [[["arg_name", "==", "json"]]], indicating that the current request parameter name is json. The var here is consistent with the naming of Nginx internal variables, so request_uri, host, etc. can also be used; for the operator part, the currently supported operators are ==, ~=, ~~, >, <, in, has and !. For specific usage of operators, please see the `operator-list` part of [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list). | Note: One of `abort` and `delay` must be specified. +`vars` is composed of a three-level array structure, as shown below: + +```json +array[ + array[ + array[] + ], + array[ + array[] + ], + ...... +] +``` + +It can implement the `and/or` relationship between rules flexibly, such as the following three expressions: + +```json +[ + [ + [ "arg_name","==","jack" ], + [ "arg_age","==",18 ] + ], + [ + [ "arg_name2","==","allen" ] + ] +] +``` + +Indicates that the relationship between the first two expressions is `and`, and the relationship between the first two expressions and the third expression is `or`. + ## How To Enable ### Enable the plugin @@ -132,7 +162,9 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f1 "http_status": 403, "body": "Fault Injection!\n", "vars": [ - [ "arg_name","==","jack" ] + [ + [ "arg_name","==","jack" ] + ] ] } } @@ -152,7 +184,7 @@ Test plugin: 1. The vars rule fails to match, and the request returns upstream response data: ```shell -$ curl http://127.0.0.1:9080/hello?name=allen -i +$ curl "http://127.0.0.1:9080/hello?name=allen" -i HTTP/1.1 200 OK Content-Type: application/octet-stream Transfer-Encoding: chunked @@ -166,7 +198,7 @@ hello 2. The vars rule is successfully matched and fault injection is performed: ```shell -$ curl http://127.0.0.1:9080/hello?name=jack -i +$ curl "http://127.0.0.1:9080/hello?name=jack" -i HTTP/1.1 403 Forbidden Date: Wed, 20 Jan 2021 07:23:37 GMT Content-Type: text/plain; charset=utf-8 @@ -187,7 +219,9 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f1 "delay": { "duration": 2, "vars": [ - [ "arg_name","==","jack" ] + [ + [ "arg_name","==","jack" ] + ] ] } } @@ -202,10 +236,12 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f1 }' ``` +Test plugin: + 1. The vars rule fails to match and the request is not delayed: ```shell -$ time curl http://127.0.0.1:9080/hello?name=allen -i +$ time curl "http://127.0.0.1:9080/hello?name=allen" -i HTTP/1.1 200 OK Content-Type: application/octet-stream Transfer-Encoding: chunked @@ -223,7 +259,8 @@ sys 0m0.003s 2. The vars rule is successfully matched, and the request is delayed for two seconds: ```shell -$ time curl http://127.0.0.1:9080/hello?name=jack -iHTTP/1.1 200 OK +$ time curl "http://127.0.0.1:9080/hello?name=jack" -i +HTTP/1.1 200 OK Content-Type: application/octet-stream Transfer-Encoding: chunked Connection: keep-alive @@ -248,13 +285,17 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f1 "http_status": 403, "body": "Fault Injection!\n", "vars": [ - [ "arg_name","==","jack" ] + [ + [ "arg_name","==","jack" ] + ] ] }, "delay": { "duration": 2, "vars": [ - [ "http_age","==","18" ] + [ + [ "http_age","==","18" ] + ] ] } } @@ -269,10 +310,12 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f1 }' ``` +Test plugin: + 1. The vars rules of abort and delay fail to match: ```shell -$ time curl http://127.0.0.1:9080/hello?name=allen -H 'age: 20' -i +$ time curl "http://127.0.0.1:9080/hello?name=allen" -H 'age: 20' -i HTTP/1.1 200 OK Content-Type: application/octet-stream Transfer-Encoding: chunked @@ -290,7 +333,7 @@ sys 0m0.003s 2. The abort vars rule fails to match, no fault injection is performed, but the request is delayed: ```shell -$ time curl http://127.0.0.1:9080/hello?name=allen -H 'age: 18' -i +$ time curl "http://127.0.0.1:9080/hello?name=allen" -H 'age: 18' -i HTTP/1.1 200 OK Content-Type: application/octet-stream Transfer-Encoding: chunked @@ -308,7 +351,7 @@ sys 0m0.006s 3. The vars rule of delay fails to match, the request is not delayed, but fault injection is performed: ```shell -time curl http://127.0.0.1:9080/hello?name=jack -H 'age: 20' -i +$ time curl "http://127.0.0.1:9080/hello?name=jack" -H 'age: 20' -i HTTP/1.1 403 Forbidden Date: Wed, 20 Jan 2021 08:20:18 GMT Content-Type: text/plain; charset=utf-8 @@ -326,7 +369,7 @@ sys 0m0.004s 4. The vars rules of abort and delay parameters match successfully, perform fault injection, and delay the request: ```shell -$ time curl http://127.0.0.1:9080/hello?name=jack -H 'age: 18' -i +$ time curl "http://127.0.0.1:9080/hello?name=jack" -H 'age: 18' -i HTTP/1.1 403 Forbidden Date: Wed, 20 Jan 2021 08:21:17 GMT Content-Type: text/plain; charset=utf-8 @@ -341,6 +384,84 @@ user 0m0.001s sys 0m0.005s ``` +Example 6: Enable the `fault-injection` plugin for a specific route, and specify the vars rule of the abort parameter (the relationship of `or`). + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "plugins": { + "fault-injection": { + "abort": { + "http_status": 403, + "body": "Fault Injection!\n", + "vars": [ + [ + ["arg_name","==","jack"], + ["arg_age","!","<",18] + ], + [ + ["http_apikey","==","apisix-key"] + ] + ] + } + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" +}' +``` + +Indicates that when the request parameters name and age satisfy both `name == "jack"` and `age >= 18`, fault injection is performed. Or when the request header apikey satisfies `apikey == "apisix-key"`, fault injection is performed. + +Test plugin: + +1. The request parameter name and age match successfully, and the request header `apikey` is missing, and fault injection is performed: + +```shell +$ curl "http://127.0.0.1:9080/hello?name=jack&age=19" -i +HTTP/1.1 403 Forbidden +Date: Fri, 22 Jan 2021 11:05:46 GMT +Content-Type: text/plain; charset=utf-8 +Transfer-Encoding: chunked +Connection: keep-alive +Server: APISIX/2.2 + +Fault Injection! +``` + +2. The request header `apikey` is successfully matched, and the request parameters are missing, and fault injection is performed: + +```shell +$ curl http://127.0.0.1:9080/hello -H "apikey: apisix-key" -i +HTTP/1.1 403 Forbidden +Date: Fri, 22 Jan 2021 11:08:34 GMT +Content-Type: text/plain; charset=utf-8 +Transfer-Encoding: chunked +Connection: keep-alive +Server: APISIX/2.2 + +Fault Injection! +``` + +3. Both request parameters and request headers fail to match, and fault injection is not performed: + +```shell +$ curl http://127.0.0.1:9080/hello -i +HTTP/1.1 200 OK +Content-Type: application/octet-stream +Transfer-Encoding: chunked +Connection: keep-alive +Date: Fri, 22 Jan 2021 11:11:17 GMT +Server: APISIX/2.2 + +hello +``` + ## Disable Plugin Remove the corresponding JSON in the plugin configuration to disable the plugin immediately without restarting the service: diff --git a/doc/zh-cn/plugins/fault-injection.md b/doc/zh-cn/plugins/fault-injection.md index 60b826f4d185..54ecbd2c14ff 100644 --- a/doc/zh-cn/plugins/fault-injection.md +++ b/doc/zh-cn/plugins/fault-injection.md @@ -30,13 +30,43 @@ | abort.http_status | integer | 必需 | | [200, ...] | 返回给客户端的 http 状态码 | | abort.body | string | 可选 | | | 返回给客户端的响应数据。支持使用 Nginx 变量,如 `client addr: $remote_addr\n`| | abort.percentage | integer | 可选 | | [0, 100] | 将被中断的请求占比 | -| abort.vars | array[] | 可选 | | | 执行故障注入的规则,当规则匹配通过后才会执行故障注。`vars` 由一个或多个{var, operator, val}元素组成的列表,类似这样:{{var, operator, val}, {var, operator, val}, ...}}。例如:{"arg_name", "==", "json"},表示当前请求参数 name 是 json。这里的 var 与 Nginx 内部自身变量命名是保持一致,所以也可以使用 request_uri、host 等;对于 operator 部分,目前已支持的运算符有 ==、~=、~~、>、<、in、has 和 ! 。操作符的具体用法请看 [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list) 的 `operator-list` 部分。 | +| abort.vars | array[] | 可选 | | | 执行故障注入的规则,当规则匹配通过后才会执行故障注。`vars` 由一个或多个[[var, operator, val]]元素组成的列表,类似这样:[[[var, operator, val],[var, operator, val]], ...]。例如:[[["arg_name", "==", "json"]]],表示当前请求参数 name 是 json。这里的 var 与 Nginx 内部自身变量命名是保持一致,所以也可以使用 request_uri、host 等;对于 operator 部分,目前已支持的运算符有 ==、~=、~~、>、<、in、has 和 ! 。操作符的具体用法请看 [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list) 的 `operator-list` 部分。 | | delay.duration | number | 必需 | | | 延迟时间,可以指定小数 | | delay.percentage | integer | 可选 | | [0, 100] | 将被延迟的请求占比 | -| delay.vars | array[] | 可选 | | [0, 100] | 执行请求延迟的规则,当规则匹配通过后才会延迟请求。`vars` 由一个或多个{var, operator, val}元素组成的列表,类似这样:{{var, operator, val}, {var, operator, val}, ...}}。例如:{"arg_name", "==", "json"},表示当前请求参数 name 是 json。这里的 var 与 Nginx 内部自身变量命名是保持一致,所以也可以使用 request_uri、host 等;对于 operator 部分,目前已支持的运算符有 ==、~=、~~、>、<、in、has 和 ! 。操作符的具体用法请看 [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list) 的 `operator-list` 部分。 | +| delay.vars | array[] | 可选 | | | 执行请求延迟的规则,当规则匹配通过后才会延迟请求。`vars` 由一个或多个[[var, operator, val]]元素组成的列表,类似这样:[[[var, operator, val],[var, operator, val]], ...]。例如:[[["arg_name", "==", "json"]]],表示当前请求参数 name 是 json。这里的 var 与 Nginx 内部自身变量命名是保持一致,所以也可以使用 request_uri、host 等;对于 operator 部分,目前已支持的运算符有 ==、~=、~~、>、<、in、has 和 ! 。操作符的具体用法请看 [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list) 的 `operator-list` 部分。 | 注:参数 abort 和 delay 至少要存在一个。 +`vars` 是由三层数组的结构组成,如下所示: + +```json +array[ + array[ + array[] + ], + array[ + array[] + ], + ...... +] +``` + +它可以灵活的实现规则之间的 `and/or` 关系,例如下面的三个表达式: + +```json +[ + [ + [ "arg_name","==","jack" ], + [ "arg_age","==",18 ] + ], + [ + [ "arg_name2","==","allen" ] + ] +] +``` + +表示前两个表达式之间的关系是 `and` ,而前两个和第三个表达式之间的关系是 `or`。 + ## 示例 ### 启用插件 @@ -132,7 +162,9 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f1 "http_status": 403, "body": "Fault Injection!\n", "vars": [ - [ "arg_name","==","jack" ] + [ + [ "arg_name","==","jack" ] + ] ] } } @@ -152,7 +184,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f1 1、vars 规则匹配失败,请求返回上游响应数据: ```shell -$ curl http://127.0.0.1:9080/hello?name=allen -i +$ curl "http://127.0.0.1:9080/hello?name=allen" -i HTTP/1.1 200 OK Content-Type: application/octet-stream Transfer-Encoding: chunked @@ -166,7 +198,7 @@ hello 2、vars 规则匹配成功,执行故障注入: ```shell -$ curl http://127.0.0.1:9080/hello?name=jack -i +$ curl "http://127.0.0.1:9080/hello?name=jack" -i HTTP/1.1 403 Forbidden Date: Wed, 20 Jan 2021 07:23:37 GMT Content-Type: text/plain; charset=utf-8 @@ -187,7 +219,9 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f1 "delay": { "duration": 2, "vars": [ - [ "arg_name","==","jack" ] + [ + [ "arg_name","==","jack" ] + ] ] } } @@ -202,10 +236,12 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f1 }' ``` +测试: + 1、vars 规则匹配失败,不延迟请求: ```shell -$ time curl http://127.0.0.1:9080/hello?name=allen -i +$ time "curl http://127.0.0.1:9080/hello?name=allen" -i HTTP/1.1 200 OK Content-Type: application/octet-stream Transfer-Encoding: chunked @@ -223,7 +259,8 @@ sys 0m0.003s 2、vars 规则匹配成功,延迟请求两秒: ```shell -$ time curl http://127.0.0.1:9080/hello?name=jack -iHTTP/1.1 200 OK +$ time curl "http://127.0.0.1:9080/hello?name=jack" -i +HTTP/1.1 200 OK Content-Type: application/octet-stream Transfer-Encoding: chunked Connection: keep-alive @@ -248,13 +285,17 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f1 "http_status": 403, "body": "Fault Injection!\n", "vars": [ - [ "arg_name","==","jack" ] + [ + [ "arg_name","==","jack" ] + ] ] }, "delay": { "duration": 2, "vars": [ - [ "http_age","==","18" ] + [ + [ "http_age","==","18" ] + ] ] } } @@ -269,10 +310,12 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f1 }' ``` +测试: + 1、abort 和 delay 的 vars 规则匹配失败: ```shell -$ time curl http://127.0.0.1:9080/hello?name=allen -H 'age: 20' -i +$ time curl "http://127.0.0.1:9080/hello?name=allen" -H 'age: 20' -i HTTP/1.1 200 OK Content-Type: application/octet-stream Transfer-Encoding: chunked @@ -290,7 +333,7 @@ sys 0m0.003s 2、abort 的 vars 规则匹配失败,不执行故障注入,但延迟请求: ```shell -$ time curl http://127.0.0.1:9080/hello?name=allen -H 'age: 18' -i +$ time curl "http://127.0.0.1:9080/hello?name=allen" -H 'age: 18' -i HTTP/1.1 200 OK Content-Type: application/octet-stream Transfer-Encoding: chunked @@ -308,7 +351,7 @@ sys 0m0.006s 3、delay 的 vars 规则匹配失败,不延迟请求,但执行故障注入: ```shell -time curl http://127.0.0.1:9080/hello?name=jack -H 'age: 20' -i +$ time curl "http://127.0.0.1:9080/hello?name=jack" -H 'age: 20' -i HTTP/1.1 403 Forbidden Date: Wed, 20 Jan 2021 08:20:18 GMT Content-Type: text/plain; charset=utf-8 @@ -326,7 +369,7 @@ sys 0m0.004s 4、abort 和 delay 参数的 vars 规则匹配成功,执行故障注入,并延迟请求: ```shell -$ time curl http://127.0.0.1:9080/hello?name=jack -H 'age: 18' -i +$ time curl "http://127.0.0.1:9080/hello?name=jack" -H 'age: 18' -i HTTP/1.1 403 Forbidden Date: Wed, 20 Jan 2021 08:21:17 GMT Content-Type: text/plain; charset=utf-8 @@ -341,6 +384,84 @@ user 0m0.001s sys 0m0.005s ``` +示例6:为特定路由启用 `fault-injection` 插件,并指定 abort 参数的 vars 规则(`or` 的关系)。 + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "plugins": { + "fault-injection": { + "abort": { + "http_status": 403, + "body": "Fault Injection!\n", + "vars": [ + [ + ["arg_name","==","jack"], + ["arg_age","!","<",18] + ], + [ + ["http_apikey","==","apisix-key"] + ] + ] + } + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" +}' +``` + +表示当请求参数 name 和 age 同时满足 `name == "jack"`、`age >= 18` 时,执行故障注入。或请求头 apikey 满足 `apikey == "apisix-key"` 时,执行故障注入。 + +测试: + +1、请求参数 name 和 age 匹配成功,缺少请求头 `apikey`, 执行故障注入: + +```shell +$ curl "http://127.0.0.1:9080/hello?name=jack&age=19" -i +HTTP/1.1 403 Forbidden +Date: Fri, 22 Jan 2021 11:05:46 GMT +Content-Type: text/plain; charset=utf-8 +Transfer-Encoding: chunked +Connection: keep-alive +Server: APISIX/2.2 + +Fault Injection! +``` + +2、请求头 `apikey` 匹配成功,缺少请求参数,执行故障注入: + +```shell +$ curl http://127.0.0.1:9080/hello -H "apikey: apisix-key" -i +HTTP/1.1 403 Forbidden +Date: Fri, 22 Jan 2021 11:08:34 GMT +Content-Type: text/plain; charset=utf-8 +Transfer-Encoding: chunked +Connection: keep-alive +Server: APISIX/2.2 + +Fault Injection! +``` + +3、请求参数与请求头都匹配失败,不执行故障注入: + +```shell +$ curl http://127.0.0.1:9080/hello -i +HTTP/1.1 200 OK +Content-Type: application/octet-stream +Transfer-Encoding: chunked +Connection: keep-alive +Date: Fri, 22 Jan 2021 11:11:17 GMT +Server: APISIX/2.2 + +hello +``` + ### 禁用插件 移除插件配置中相应的 JSON 配置可立即禁用该插件,无需重启服务: diff --git a/t/plugin/fault-injection.t b/t/plugin/fault-injection.t index 433dedb42053..28f763cbe089 100644 --- a/t/plugin/fault-injection.t +++ b/t/plugin/fault-injection.t @@ -744,21 +744,26 @@ GET /hello -=== TEST 24: set route and configure the vars rule in abort(There is only one vars rule, and the operator is `==`.) +=== TEST 24: set route and configure the vars rule in abort --- 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, - [[{ + [=[{ "plugins": { "fault-injection": { "abort": { "http_status": 403, "body": "Fault Injection!\n", "vars": [ - [ "arg_name","==","jack" ] + [ + [ "arg_name","==","jack" ] + ], + [ + [ "arg_age","==",18 ] + ] ] } } @@ -770,7 +775,7 @@ GET /hello "type": "roundrobin" }, "uri": "/hello" - }]] + }]=] ) if code >= 300 then ngx.status = code @@ -785,10 +790,10 @@ GET /t passed --- no_error_log [error] ---- LAST -=== TEST 21: hit route(Missing request parameter name.) + +=== TEST 21: hit route(Missing request parameter) --- request GET /hello --- response_body @@ -800,12 +805,12 @@ hello world === TEST 22: hit route(The rule of vars failed to match.) --- request -GET /hello?name=jakc +GET /hello?name=allen --- response_body hello world --- no_error_log [error] ---- LAST + === TEST 23: hit route(The rules of vars match successfully.) From 8278a2d502b1672939254b5c6f64ec4219e6819d Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Sat, 23 Jan 2021 16:54:27 +0800 Subject: [PATCH 3/7] add test ceses and fix ci faild. --- apisix/plugins/fault-injection.lua | 1 + t/plugin/fault-injection.t | 188 +++++++++++++++++++++++++++-- 2 files changed, 182 insertions(+), 7 deletions(-) diff --git a/apisix/plugins/fault-injection.lua b/apisix/plugins/fault-injection.lua index 1c4d00bfadce..4e761010123f 100644 --- a/apisix/plugins/fault-injection.lua +++ b/apisix/plugins/fault-injection.lua @@ -19,6 +19,7 @@ local expr = require("resty.expr.v1") local sleep = core.sleep local random = math.random +local ipairs = ipairs local plugin_name = "fault-injection" diff --git a/t/plugin/fault-injection.t b/t/plugin/fault-injection.t index 28f763cbe089..bf2cae0765e6 100644 --- a/t/plugin/fault-injection.t +++ b/t/plugin/fault-injection.t @@ -759,10 +759,11 @@ GET /hello "body": "Fault Injection!\n", "vars": [ [ - [ "arg_name","==","jack" ] + ["arg_name","==","jack"], + [ "arg_age","!","<",18 ] ], [ - [ "arg_age","==",18 ] + [ "http_apikey","==","api-key" ] ] ] } @@ -793,17 +794,44 @@ passed -=== TEST 21: hit route(Missing request parameter) +=== TEST 25: hit the route (all vars rules pass), execute abort +--- request +GET /hello?name=jack&age=18 +--- more_headers +apikey: api-key +--- error_code: 403 +--- response_body +Fault Injection! +--- no_error_log +[error] + + + +=== TEST 26: hit the route (missing apikey), execute abort +--- request +GET /hello?name=jack&age=20 +--- error_code: 403 +--- response_body +Fault Injection! +--- no_error_log +[error] + + + +=== TEST 27: hit the route (missing request parameters), execute abort --- request GET /hello +--- more_headers +apikey:api-key +--- error_code: 403 --- response_body -hello world +Fault Injection! --- no_error_log [error] -=== TEST 22: hit route(The rule of vars failed to match.) +=== TEST 28: hit route(`vars` do not match, `age` is missing) --- request GET /hello?name=allen --- response_body @@ -813,11 +841,157 @@ hello world -=== TEST 23: hit route(The rules of vars match successfully.) +=== TEST 29: hit route(all `vars` do not match) --- request -GET /hello?name=jack +GET /hello +--- response_body +hello world +--- no_error_log +[error] + + + +=== TEST 30: set route and configure the vars rule in delay +--- 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, + [=[{ + "uri": "/hello", + "plugins": { + "fault-injection": { + "delay": { + "duration": 2, + "vars": [ + [ + ["arg_name","==","jack"], + [ "arg_age","!","<",18 ] + ] + ] + } + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + } + }]=] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- error_code: 200 +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 31: hit route(delay 2 seconds and return hello world) +--- request +GET /hello?name=jack&age=22 +--- response_body +hello world +--- no_error_log +[error] + + + +=== TEST 32: hit route (no wait and return hello1 world) +--- request +GET /hello HTTP/1.1 +--- error_code: 200 +--- response_body +hello world +--- no_error_log +[error] + + + +=== TEST 33: set route and configure the vars rule in abort and delay +--- 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, + [=[{ + "plugins": { + "fault-injection": { + "abort": { + "http_status": 403, + "body": "Fault Injection!\n", + "vars": [ + [ + ["arg_name","==","jack"], + ["arg_age","!","<",18] + ] + ] + }, + "delay": { + "duration": 2, + "vars": [ + [ + ["http_apikey","==","api-key"] + ] + ] + } + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]=] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- error_code: 200 +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 34: hit the route (all vars rules are passed), execute abort and delay +--- request +GET /hello?name=jack&age=18 +--- more_headers +apikey: api-key --- error_code: 403 --- response_body Fault Injection! --- no_error_log [error] + + + +=== TEST 35: hit the route (abort rule does not match), only execute delay +--- request +GET /hello?name=jack&age=16 +--- more_headers +apikey: api-key +--- response_body +hello world +--- no_error_log +[error] From 1f04d58d36271b4e9e65a487fdbc25cbf3929c00 Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Mon, 25 Jan 2021 15:02:49 +0800 Subject: [PATCH 4/7] fix review. --- apisix/plugins/fault-injection.lua | 55 ++++--------- doc/plugins/fault-injection.md | 22 +---- doc/zh-cn/plugins/fault-injection.md | 22 +---- t/plugin/fault-injection.t | 115 ++++++++++++++++++++++++--- 4 files changed, 128 insertions(+), 86 deletions(-) diff --git a/apisix/plugins/fault-injection.lua b/apisix/plugins/fault-injection.lua index 4e761010123f..faba1b8eead6 100644 --- a/apisix/plugins/fault-injection.lua +++ b/apisix/plugins/fault-injection.lua @@ -30,43 +30,6 @@ local vars_schema = { type = "array", items = { type = "array", - items = { - { - type = "string", - minLength = 1, - maxLength = 100 - }, - { - type = "string", - minLength = 1, - maxLength = 2 - } - }, - additionalItems = { - anyOf = { - {type = "string"}, - {type = "number"}, - {type = "boolean"}, - { - type = "array", - items = { - anyOf = { - { - type = "string", - minLength = 1, maxLength = 100 - }, - { - type = "number" - }, - { - type = "boolean" - } - } - }, - uniqueItems = true - } - } - }, minItems = 0, maxItems = 10 } @@ -124,7 +87,7 @@ local function vars_match(vars, ctx) for _, var in ipairs(vars) do local expr, err = expr.new(var) if err then - core.log.error("vars expression does not match: ", err) + core.log.error("failed to create vars expression: ", err) return nil, err end @@ -144,6 +107,20 @@ function _M.check_schema(conf) return false, err end + if conf.abort and conf.abort.vars then + local ok, err = core.schema.check(vars_schema, conf.abort.vars) + if not ok then + return false, err + end + end + + if conf.delay and conf.delay.vars then + local ok, err = core.schema.check(vars_schema, conf.delay.vars) + if not ok then + return false, err + end + end + return true end @@ -156,6 +133,7 @@ function _M.rewrite(conf, ctx) if conf.abort and conf.abort.vars then abort_vars, err = vars_match(conf.abort.vars, ctx) if err then + -- the error log has been recorded in the vars_match method return 500, err end end @@ -165,6 +143,7 @@ function _M.rewrite(conf, ctx) if conf.delay and conf.delay.vars then delay_vars, err = vars_match(conf.delay.vars, ctx) if err then + -- the error log has been recorded in the vars_match method return 500, err end end diff --git a/doc/plugins/fault-injection.md b/doc/plugins/fault-injection.md index 003cb65a8bcf..973a72c30236 100644 --- a/doc/plugins/fault-injection.md +++ b/doc/plugins/fault-injection.md @@ -30,28 +30,14 @@ Fault injection plugin, this plugin can be used with other plugins and will be e | abort.http_status | integer | required | | [200, ...] | user-specified http code returned to the client. | | abort.body | string | optional | | | response data returned to the client. Nginx variable can be used inside, like `client addr: $remote_addr\n` | | abort.percentage | integer | optional | | [0, 100] | percentage of requests to be aborted. | -| abort.vars | array[] | optional | | | The rules for executing fault injection will only be executed when the rules are matched. `vars` is a list consisting of one or more [[var, operator, val]] elements, like this: [[[var, operator, val],[var, operator, val]], ...]. For example: [[["arg_name", "==", "json"]]], indicating that the current request parameter name is json. The var here is consistent with the naming of Nginx internal variables, so request_uri, host, etc. can also be used; for the operator part, the currently supported operators are ==, ~=, ~~, >, <, in, has and !. For specific usage of operators, please see the `operator-list` part of [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list). | +| abort.vars | array[] | optional | | | The rules for executing fault injection will only be executed when the rules are matched. `vars` is a list of expressions, which is from the [lua-resty-expr](https://github.com/api7/lua-resty-expr). | | delay.duration | number | required | | | delay time (can be decimal). | | delay.percentage | integer | optional | | [0, 100] | percentage of requests to be delayed. | -| delay.vars | array[] | optional | | | Execute the request delay rule, and the request will be delayed only after the rule matches. `vars` is a list consisting of one or more [[var, operator, val]] elements, like this: [[[var, operator, val],[var, operator, val]], ...]. For example: [[["arg_name", "==", "json"]]], indicating that the current request parameter name is json. The var here is consistent with the naming of Nginx internal variables, so request_uri, host, etc. can also be used; for the operator part, the currently supported operators are ==, ~=, ~~, >, <, in, has and !. For specific usage of operators, please see the `operator-list` part of [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list). | +| delay.vars | array[] | optional | | | Execute the request delay rule, and the request will be delayed only after the rule matches. `vars` is a list of expressions, which is from the [lua-resty-expr](https://github.com/api7/lua-resty-expr). | Note: One of `abort` and `delay` must be specified. -`vars` is composed of a three-level array structure, as shown below: - -```json -array[ - array[ - array[] - ], - array[ - array[] - ], - ...... -] -``` - -It can implement the `and/or` relationship between rules flexibly, such as the following three expressions: +`vars` is composed of a three-layer array structure, which can flexibly implement the `and/or` relationship between rules. Example: ```json [ @@ -65,7 +51,7 @@ It can implement the `and/or` relationship between rules flexibly, such as the f ] ``` -Indicates that the relationship between the first two expressions is `and`, and the relationship between the first two expressions and the third expression is `or`. +This means that the relationship between the first two expressions is `and`, and the relationship between the first two expressions and the third expression is `or`. ## How To Enable diff --git a/doc/zh-cn/plugins/fault-injection.md b/doc/zh-cn/plugins/fault-injection.md index 54ecbd2c14ff..6a3a166e82b9 100644 --- a/doc/zh-cn/plugins/fault-injection.md +++ b/doc/zh-cn/plugins/fault-injection.md @@ -30,28 +30,14 @@ | abort.http_status | integer | 必需 | | [200, ...] | 返回给客户端的 http 状态码 | | abort.body | string | 可选 | | | 返回给客户端的响应数据。支持使用 Nginx 变量,如 `client addr: $remote_addr\n`| | abort.percentage | integer | 可选 | | [0, 100] | 将被中断的请求占比 | -| abort.vars | array[] | 可选 | | | 执行故障注入的规则,当规则匹配通过后才会执行故障注。`vars` 由一个或多个[[var, operator, val]]元素组成的列表,类似这样:[[[var, operator, val],[var, operator, val]], ...]。例如:[[["arg_name", "==", "json"]]],表示当前请求参数 name 是 json。这里的 var 与 Nginx 内部自身变量命名是保持一致,所以也可以使用 request_uri、host 等;对于 operator 部分,目前已支持的运算符有 ==、~=、~~、>、<、in、has 和 ! 。操作符的具体用法请看 [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list) 的 `operator-list` 部分。 | +| abort.vars | array[] | 可选 | | | 执行故障注入的规则,当规则匹配通过后才会执行故障注。`vars` 是一个表达式的列表,来自 [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list)。 | | delay.duration | number | 必需 | | | 延迟时间,可以指定小数 | | delay.percentage | integer | 可选 | | [0, 100] | 将被延迟的请求占比 | -| delay.vars | array[] | 可选 | | | 执行请求延迟的规则,当规则匹配通过后才会延迟请求。`vars` 由一个或多个[[var, operator, val]]元素组成的列表,类似这样:[[[var, operator, val],[var, operator, val]], ...]。例如:[[["arg_name", "==", "json"]]],表示当前请求参数 name 是 json。这里的 var 与 Nginx 内部自身变量命名是保持一致,所以也可以使用 request_uri、host 等;对于 operator 部分,目前已支持的运算符有 ==、~=、~~、>、<、in、has 和 ! 。操作符的具体用法请看 [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list) 的 `operator-list` 部分。 | +| delay.vars | array[] | 可选 | | | 执行请求延迟的规则,当规则匹配通过后才会延迟请求。`vars` 是一个表达式列表,来自 [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list)。 | 注:参数 abort 和 delay 至少要存在一个。 -`vars` 是由三层数组的结构组成,如下所示: - -```json -array[ - array[ - array[] - ], - array[ - array[] - ], - ...... -] -``` - -它可以灵活的实现规则之间的 `and/or` 关系,例如下面的三个表达式: +`vars` 是由三层数组的结构组成,它可以灵活的实现规则之间的 `and/or` 关系,示例: ```json [ @@ -65,7 +51,7 @@ array[ ] ``` -表示前两个表达式之间的关系是 `and` ,而前两个和第三个表达式之间的关系是 `or`。 +这表示前两个表达式之间的关系是 `and` ,而前两个和第三个表达式之间的关系是 `or`。 ## 示例 diff --git a/t/plugin/fault-injection.t b/t/plugin/fault-injection.t index bf2cae0765e6..f6888e81fdb7 100644 --- a/t/plugin/fault-injection.t +++ b/t/plugin/fault-injection.t @@ -744,7 +744,98 @@ GET /hello -=== TEST 24: set route and configure the vars rule in abort +=== TEST 24: vars schema validation passed +--- config + location /t { + content_by_lua_block { + local plugin = require("apisix.plugins.fault-injection") + local ok, err = plugin.check_schema({ + abort = { + http_status = 403, + body = "Fault Injection!\n", + vars = { + { + {"arg_name","==","jack"}, + {"arg_age","!","<",18} + }, + { + {"http_apikey","==","api-key"} + } + } + }, + delay = { + duration = 2, + vars = { + { + {"arg_name","==","jack"}, + {"arg_age","!","<",18} + }, + { + {"http_apikey","==","api-key"} + } + } + } + }) + if not ok then + ngx.say(err) + end + + ngx.say("done") + } + } +--- request +GET /t +--- response_body +done +--- no_error_log +[error] + + + +=== TEST 25: vars schema validation failed(abort failed) +--- config + location /t { + content_by_lua_block { + local plugin = require("apisix.plugins.fault-injection") + local ok, err = plugin.check_schema({ + abort = { + http_status = 403, + body = "Fault Injection!\n", + vars = { + {"arg_name","==","jack"}, + {"arg_age","!","<",18} + } + }, + delay = { + duration = 2, + vars = { + { + {"arg_name","==","jack"}, + {"arg_age","!","<",18} + }, + { + {"http_apikey","==","api-key"} + } + } + } + }) + if not ok then + ngx.say(err) + end + + ngx.say("done") + } + } +--- request +GET /t +--- response_body eval +qr/property \"abort\" validation failed: property \"vars\" validation failed: failed to validate item 1:.*/ +--- no_error_log +[error] + + + +=== TEST 26: set route and configure the vars rule in abort --- config location /t { content_by_lua_block { @@ -794,7 +885,7 @@ passed -=== TEST 25: hit the route (all vars rules pass), execute abort +=== TEST 27: hit the route (all vars rules pass), execute abort --- request GET /hello?name=jack&age=18 --- more_headers @@ -807,7 +898,7 @@ Fault Injection! -=== TEST 26: hit the route (missing apikey), execute abort +=== TEST 28: hit the route (missing apikey), execute abort --- request GET /hello?name=jack&age=20 --- error_code: 403 @@ -818,7 +909,7 @@ Fault Injection! -=== TEST 27: hit the route (missing request parameters), execute abort +=== TEST 29: hit the route (missing request parameters), execute abort --- request GET /hello --- more_headers @@ -831,7 +922,7 @@ Fault Injection! -=== TEST 28: hit route(`vars` do not match, `age` is missing) +=== TEST 30: hit route(`vars` do not match, `age` is missing) --- request GET /hello?name=allen --- response_body @@ -841,7 +932,7 @@ hello world -=== TEST 29: hit route(all `vars` do not match) +=== TEST 31: hit route(all `vars` do not match) --- request GET /hello --- response_body @@ -851,7 +942,7 @@ hello world -=== TEST 30: set route and configure the vars rule in delay +=== TEST 32: set route and configure the vars rule in delay --- config location /t { content_by_lua_block { @@ -897,7 +988,7 @@ passed -=== TEST 31: hit route(delay 2 seconds and return hello world) +=== TEST 33: hit route(delay 2 seconds and return hello world) --- request GET /hello?name=jack&age=22 --- response_body @@ -907,7 +998,7 @@ hello world -=== TEST 32: hit route (no wait and return hello1 world) +=== TEST 34: hit route (no wait and return hello1 world) --- request GET /hello HTTP/1.1 --- error_code: 200 @@ -918,7 +1009,7 @@ hello world -=== TEST 33: set route and configure the vars rule in abort and delay +=== TEST 35: set route and configure the vars rule in abort and delay --- config location /t { content_by_lua_block { @@ -973,7 +1064,7 @@ passed -=== TEST 34: hit the route (all vars rules are passed), execute abort and delay +=== TEST 36: hit the route (all vars rules are passed), execute abort and delay --- request GET /hello?name=jack&age=18 --- more_headers @@ -986,7 +1077,7 @@ Fault Injection! -=== TEST 35: hit the route (abort rule does not match), only execute delay +=== TEST 37: hit the route (abort rule does not match), only execute delay --- request GET /hello?name=jack&age=16 --- more_headers From 86cee2e71f21a331718bb3afde7bec44d1121dff Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Mon, 25 Jan 2021 15:22:45 +0800 Subject: [PATCH 5/7] update docs. --- doc/plugins/fault-injection.md | 2 +- doc/zh-cn/plugins/fault-injection.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/plugins/fault-injection.md b/doc/plugins/fault-injection.md index 973a72c30236..c0b255605a26 100644 --- a/doc/plugins/fault-injection.md +++ b/doc/plugins/fault-injection.md @@ -37,7 +37,7 @@ Fault injection plugin, this plugin can be used with other plugins and will be e Note: One of `abort` and `delay` must be specified. -`vars` is composed of a three-layer array structure, which can flexibly implement the `and/or` relationship between rules. Example: +The `vars` is a list of expression which is from the `lua-resty-expr`, which can flexibly implement the `and/or` relationship between rules. Example: ```json [ diff --git a/doc/zh-cn/plugins/fault-injection.md b/doc/zh-cn/plugins/fault-injection.md index 6a3a166e82b9..2931bbdbb48a 100644 --- a/doc/zh-cn/plugins/fault-injection.md +++ b/doc/zh-cn/plugins/fault-injection.md @@ -37,7 +37,7 @@ 注:参数 abort 和 delay 至少要存在一个。 -`vars` 是由三层数组的结构组成,它可以灵活的实现规则之间的 `and/or` 关系,示例: +`vars` 是由 `lua-resty-expr` 的表达式组成的列表,它可以灵活的实现规则之间的 `and/or` 关系,示例: ```json [ From 8ba5c7083acf03f2b3ee463adca6eb75ceef9e3e Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Mon, 25 Jan 2021 19:56:38 +0800 Subject: [PATCH 6/7] update code. --- apisix/plugins/fault-injection.lua | 63 ++++++++++++------------------ t/plugin/fault-injection.t | 12 +++--- 2 files changed, 30 insertions(+), 45 deletions(-) diff --git a/apisix/plugins/fault-injection.lua b/apisix/plugins/fault-injection.lua index faba1b8eead6..9c9fef4fe17b 100644 --- a/apisix/plugins/fault-injection.lua +++ b/apisix/plugins/fault-injection.lua @@ -24,20 +24,6 @@ local ipairs = ipairs local plugin_name = "fault-injection" -local vars_schema = { - type = "array", - items = { - type = "array", - items = { - type = "array", - minItems = 0, - maxItems = 10 - } - }, - maxItems = 20 -} - - local schema = { type = "object", properties = { @@ -47,7 +33,10 @@ local schema = { http_status = {type = "integer", minimum = 200}, body = {type = "string", minLength = 0}, percentage = {type = "integer", minimum = 0, maximum = 100}, - vars = vars_schema + vars = { + type = "array", + maxItems = 20 + } }, required = {"http_status"}, }, @@ -56,7 +45,10 @@ local schema = { properties = { duration = {type = "number", minimum = 0}, percentage = {type = "integer", minimum = 0, maximum = 100}, - vars = vars_schema + vars = { + type = "array", + maxItems = 20 + } }, required = {"duration"}, } @@ -85,19 +77,14 @@ end local function vars_match(vars, ctx) local match_result for _, var in ipairs(vars) do - local expr, err = expr.new(var) - if err then - core.log.error("failed to create vars expression: ", err) - return nil, err - end - + local expr, _ = expr.new(var) match_result = expr:eval(ctx.var) if match_result then break end end - return match_result, nil + return match_result end @@ -108,16 +95,22 @@ function _M.check_schema(conf) end if conf.abort and conf.abort.vars then - local ok, err = core.schema.check(vars_schema, conf.abort.vars) - if not ok then - return false, err + for _, var in ipairs(conf.abort.vars) do + local expr, err = expr.new(var) + if err then + core.log.error("failed to create vars expression: ", err) + return false, err + end end end if conf.delay and conf.delay.vars then - local ok, err = core.schema.check(vars_schema, conf.delay.vars) - if not ok then - return false, err + for _, var in ipairs(conf.delay.vars) do + local expr, err = expr.new(var) + if err then + core.log.error("failed to create vars expression: ", err) + return false, err + end end end @@ -131,21 +124,13 @@ function _M.rewrite(conf, ctx) local err local abort_vars = true if conf.abort and conf.abort.vars then - abort_vars, err = vars_match(conf.abort.vars, ctx) - if err then - -- the error log has been recorded in the vars_match method - return 500, err - end + abort_vars = vars_match(conf.abort.vars, ctx) end core.log.info("abort_vars: ", abort_vars) local delay_vars = true if conf.delay and conf.delay.vars then - delay_vars, err = vars_match(conf.delay.vars, ctx) - if err then - -- the error log has been recorded in the vars_match method - return 500, err - end + delay_vars = vars_match(conf.delay.vars, ctx) end core.log.info("delay_vars: ", delay_vars) diff --git a/t/plugin/fault-injection.t b/t/plugin/fault-injection.t index f6888e81fdb7..d0469f51ee37 100644 --- a/t/plugin/fault-injection.t +++ b/t/plugin/fault-injection.t @@ -802,8 +802,7 @@ done http_status = 403, body = "Fault Injection!\n", vars = { - {"arg_name","==","jack"}, - {"arg_age","!","<",18} + {"arg_name","==","jack"} } }, delay = { @@ -828,10 +827,11 @@ done } --- request GET /t ---- response_body eval -qr/property \"abort\" validation failed: property \"vars\" validation failed: failed to validate item 1:.*/ ---- no_error_log -[error] +--- response_body +invalid expression +done +--- error_log eval +qr/failed to create vars expression:.*/ From 6c912b5d4dc9f30012e7b919860f73670c4083a8 Mon Sep 17 00:00:00 2001 From: root <2226815922@qq.com> Date: Mon, 25 Jan 2021 21:12:59 +0800 Subject: [PATCH 7/7] fix ci error. --- apisix/plugins/fault-injection.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apisix/plugins/fault-injection.lua b/apisix/plugins/fault-injection.lua index 9c9fef4fe17b..ac2846db7f80 100644 --- a/apisix/plugins/fault-injection.lua +++ b/apisix/plugins/fault-injection.lua @@ -96,7 +96,7 @@ function _M.check_schema(conf) if conf.abort and conf.abort.vars then for _, var in ipairs(conf.abort.vars) do - local expr, err = expr.new(var) + local _, err = expr.new(var) if err then core.log.error("failed to create vars expression: ", err) return false, err @@ -106,7 +106,7 @@ function _M.check_schema(conf) if conf.delay and conf.delay.vars then for _, var in ipairs(conf.delay.vars) do - local expr, err = expr.new(var) + local _, err = expr.new(var) if err then core.log.error("failed to create vars expression: ", err) return false, err @@ -121,7 +121,6 @@ end function _M.rewrite(conf, ctx) core.log.info("plugin rewrite phase, conf: ", core.json.delay_encode(conf)) - local err local abort_vars = true if conf.abort and conf.abort.vars then abort_vars = vars_match(conf.abort.vars, ctx)