This is an automated email from the ASF dual-hosted git repository.
spacewander pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new f5f402f44 feat: supports dynamic control of whether plugins are
running (#7453)
f5f402f44 is described below
commit f5f402f4484dc09300860a1798e003410e1fb456
Author: soulbird <[email protected]>
AuthorDate: Wed Jul 20 14:45:12 2022 +0800
feat: supports dynamic control of whether plugins are running (#7453)
---
apisix/plugin.lua | 48 +++++++-
apisix/schema_def.lua | 9 ++
docs/en/latest/terminology/plugin.md | 15 +++
docs/zh/latest/terminology/plugin.md | 15 +++
t/admin/plugins.t | 4 +-
t/plugin/plugin.t | 221 ++++++++++++++++++++++++++++++++++-
6 files changed, 306 insertions(+), 6 deletions(-)
diff --git a/apisix/plugin.lua b/apisix/plugin.lua
index b20ff2787..e87dbc750 100644
--- a/apisix/plugin.lua
+++ b/apisix/plugin.lua
@@ -19,6 +19,7 @@ local core = require("apisix.core")
local config_util = require("apisix.core.config_util")
local enable_debug = require("apisix.debug").enable_debug
local wasm = require("apisix.wasm")
+local expr = require("resty.expr.v1")
local ngx = ngx
local crc32 = ngx.crc32_short
local ngx_exit = ngx.exit
@@ -40,6 +41,9 @@ local stream_local_plugins_hash = core.table.new(0, 32)
local merged_route = core.lrucache.new({
ttl = 300, count = 512
})
+local expr_lrucache = core.lrucache.new({
+ ttl = 300, count = 512
+})
local local_conf
local check_plugin_metadata
@@ -381,6 +385,32 @@ local function trace_plugins_info_for_debug(ctx, plugins)
end
end
+local function meta_filter(ctx, plugin_name, plugin_conf)
+ local filter = plugin_conf._meta and plugin_conf._meta.filter
+ if not filter then
+ return true
+ end
+
+ local ex, ok, err
+ if ctx then
+ ex, err = expr_lrucache(plugin_name .. ctx.conf_type .. ctx.conf_id,
+ ctx.conf_version, expr.new, filter)
+ else
+ ex, err = expr.new(filter)
+ end
+ if not ex then
+ core.log.warn("failed to get the 'vars' expression: ", err ,
+ " plugin_name: ", plugin_name)
+ return true
+ end
+ ok, err = ex:eval()
+ if err then
+ core.log.warn("failed to run the 'vars' expression: ", err,
+ " plugin_name: ", plugin_name)
+ return true
+ end
+ return ok
+end
function _M.filter(ctx, conf, plugins, route_conf, phase)
local user_plugin_conf = conf.value.plugins
@@ -399,7 +429,12 @@ function _M.filter(ctx, conf, plugins, route_conf, phase)
local name = plugin_obj.name
local plugin_conf = user_plugin_conf[name]
- if type(plugin_conf) == "table" and not plugin_conf.disable then
+ if type(plugin_conf) ~= "table" then
+ goto continue
+ end
+
+ local matched = meta_filter(ctx, name, plugin_conf)
+ if not plugin_conf.disable and matched then
if plugin_obj.run_policy == "prefer_route" and route_plugin_conf
~= nil then
local plugin_conf_in_route = route_plugin_conf[name]
if plugin_conf_in_route and not plugin_conf_in_route.disable
then
@@ -412,9 +447,9 @@ function _M.filter(ctx, conf, plugins, route_conf, phase)
end
core.table.insert(plugins, plugin_obj)
core.table.insert(plugins, plugin_conf)
-
- ::continue::
end
+
+ ::continue::
end
trace_plugins_info_for_debug(ctx, plugins)
@@ -735,6 +770,13 @@ local function check_single_plugin_schema(name,
plugin_conf, schema_type, skip_d
.. name .. " err: " .. err
end
+ if plugin_conf._meta and plugin_conf._meta.filter then
+ ok, err = expr.new(plugin_conf._meta.filter)
+ if not ok then
+ return nil, "failed to validate the 'vars' expression: " .. err
+ end
+ end
+
plugin_conf.disable = disable
end
diff --git a/apisix/schema_def.lua b/apisix/schema_def.lua
index 16dccc6d8..232be9d86 100644
--- a/apisix/schema_def.lua
+++ b/apisix/schema_def.lua
@@ -956,6 +956,15 @@ _M.plugin_injected_schema = {
description = "priority of plugins by customized order",
type = "integer",
},
+ filter = {
+ description = "filter determines whether the plugin "..
+ "needs to be executed at runtime",
+ type = "array",
+ maxItems = 20,
+ items = {
+ type = "array"
+ }
+ }
}
}
}
diff --git a/docs/en/latest/terminology/plugin.md
b/docs/en/latest/terminology/plugin.md
index 6ab969769..f5c81c2b4 100644
--- a/docs/en/latest/terminology/plugin.md
+++ b/docs/en/latest/terminology/plugin.md
@@ -90,12 +90,27 @@ Some common configurations can be applied to the plugin
configuration. For examp
the configuration above means customizing the error response from the jwt-auth
plugin to '{"message": "Missing credential in request"}'.
+```
+{
+ "jwt-auth": {
+ "_meta": {
+ "filter": {
+ {"arg_version", "==", "v2"}
+ }
+ }
+ }
+}
+```
+
+This configuration example means that the `jwt-auth` plugin will only execute
if `version` in the request parameter equals `v2`.
+
### Plugin Common Configuration Under `_meta`
| Name | Type | Description |
|--------------|------|-------------|
| error_response | string/object | Custom error response |
| priority | integer | Custom plugin priority |
+| filter | array | Depending on the requested parameters, it is decided at
runtime whether the plugin should be executed. List of variables to match for
filtering requests for conditional traffic split. It is in the format {variable
operator value}. For example, `{"arg_name", "==", "json"}`. The variables here
are consistent with Nginx internal variables. For details on supported
operators, please see
[lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list). |
### Custom Plugin Priority
diff --git a/docs/zh/latest/terminology/plugin.md
b/docs/zh/latest/terminology/plugin.md
index 86bed6442..d6bea2e18 100644
--- a/docs/zh/latest/terminology/plugin.md
+++ b/docs/zh/latest/terminology/plugin.md
@@ -84,12 +84,27 @@ local _M = {
上面的配置意味着将 jwt-auth 插件的错误响应自定义为 '{"message": "Missing credential in request"}'。
+```
+{
+ "jwt-auth": {
+ "_meta": {
+ "filter": {
+ {"arg_version", "==", "v2"}
+ }
+ }
+ }
+}
+```
+
+这个配置示例意味着只有在请求参数中 `version` 等于 `v2` 时 `jwt-auth` 插件才会执行。
+
### 在 `_meta` 下的插件通用配置
| 名称 | 类型 | 描述 |
|--------------|------|----------------|
| error_response | string/object | 自定义错误响应 |
| priority | integer | 自定义插件优先级 |
+| filter | array | 根据请求的参数,在运行时控制插件是否执行。由一个或多个{var, operator,
val}元素组成列表,类似这样:{{var, operator, val}, {var, operator, val},
...}}。例如:`{"arg_name", "==", "json"}`,表示当前请求参数 name 是 json。这里的 var 与 Nginx
内部自身变量命名是保持一致。操作符的具体用法请看[lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list)
的 operator-list 部分。|
### 自定义插件优先级
diff --git a/t/admin/plugins.t b/t/admin/plugins.t
index d7881249d..e7a04bd97 100644
--- a/t/admin/plugins.t
+++ b/t/admin/plugins.t
@@ -265,7 +265,7 @@ plugins:
}
}
--- response_body eval
-qr/\{"metadata_schema":\{"properties":\{"ikey":\{"minimum":0,"type":"number"\},"skey":\{"type":"string"\}\},"required":\["ikey","skey"\],"type":"object"\},"priority":0,"schema":\{"\$comment":"this
is a mark for our injected plugin
schema","properties":\{"_meta":\{"properties":\{"error_response":\{"oneOf":\[\{"type":"string"\},\{"type":"object"\}\]\},"priority":\{"description":"priority
of plugins by customized
order","type":"integer"\}\},"type":"object"\},"disable":\{"type":"boolean"\},"
[...]
+qr/\{"metadata_schema":\{"properties":\{"ikey":\{"minimum":0,"type":"number"\},"skey":\{"type":"string"\}\},"required":\["ikey","skey"\],"type":"object"\},"priority":0,"schema":\{"\$comment":"this
is a mark for our injected plugin
schema","properties":\{"_meta":\{"properties":\{"error_response":\{"oneOf":\[\{"type":"string"\},\{"type":"object"\}\]\},"filter":\{"description":"filter
determines whether the plugin needs to be executed at
runtime","items":\{"type":"array"\},"maxItems":20,"ty [...]
@@ -366,7 +366,7 @@
qr/\{"properties":\{"password":\{"type":"string"\},"username":\{"type":"string"\
}
}
--- response_body
-{"priority":1003,"schema":{"$comment":"this is a mark for our injected plugin
schema","properties":{"_meta":{"properties":{"error_response":{"oneOf":[{"type":"string"},{"type":"object"}]},"priority":{"description":"priority
of plugins by customized
order","type":"integer"}},"type":"object"},"burst":{"minimum":0,"type":"integer"},"conn":{"exclusiveMinimum":0,"type":"integer"},"default_conn_delay":{"exclusiveMinimum":0,"type":"number"},"disable":{"type":"boolean"},"key":{"type":"string"},"
[...]
+{"priority":1003,"schema":{"$comment":"this is a mark for our injected plugin
schema","properties":{"_meta":{"properties":{"error_response":{"oneOf":[{"type":"string"},{"type":"object"}]},"filter":{"description":"filter
determines whether the plugin needs to be executed at
runtime","items":{"type":"array"},"maxItems":20,"type":"array"},"priority":{"description":"priority
of plugins by customized
order","type":"integer"}},"type":"object"},"burst":{"minimum":0,"type":"integer"},"conn":{"ex
[...]
diff --git a/t/plugin/plugin.t b/t/plugin/plugin.t
index e45f5d5f7..755294868 100644
--- a/t/plugin/plugin.t
+++ b/t/plugin/plugin.t
@@ -284,7 +284,7 @@ GET /hello
error_response = "OK"
}},
}) do
- local code, body = t('/apisix/admin/global_rules/1',
+ local code, body = t('/apisix/admin/plugin_configs/1',
ngx.HTTP_PUT,
{
plugins = {
@@ -306,3 +306,222 @@ GET /hello
{"error_msg":"failed to check the configuration of plugin jwt-auth err:
property \"_meta\" validation failed: wrong type: expected object, got boolean"}
{"error_msg":"failed to check the configuration of plugin jwt-auth err:
property \"_meta\" validation failed: property \"error_response\" validation
failed: value should match only one schema, but matches none"}
passed
+
+
+
+=== TEST 10: invalid _meta filter vars schema with wrong type
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+
+ local code, body = t('/apisix/admin/plugin_configs/1',
+ ngx.HTTP_PUT,
+ {
+ plugins = {
+ ["jwt-auth"] = {
+ _meta = {
+ filter = "arg_k == v"
+ }
+ }
+ }
+ }
+ )
+ if code >= 300 then
+ ngx.print(body)
+ else
+ ngx.say(body)
+ end
+ }
+ }
+--- response_body
+{"error_msg":"failed to check the configuration of plugin jwt-auth err:
property \"_meta\" validation failed: property \"filter\" validation failed:
wrong type: expected array, got string"}
+
+
+
+=== TEST 11: invalid _meta filter schema with wrong expr
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+
+ for _, filter in ipairs({
+ {"arg_name", "==", "json"},
+ {
+ {"arg_name", "*=", "json"}
+ }
+ }) do
+ local code, body = t('/apisix/admin/plugin_configs/1',
+ ngx.HTTP_PUT,
+ {
+ plugins = {
+ ["jwt-auth"] = {
+ _meta = {
+ filter = filter
+ }
+ }
+ }
+ }
+ )
+ if code >= 300 then
+ ngx.print(body)
+ else
+ ngx.say(body)
+ end
+ end
+ }
+ }
+--- response_body
+{"error_msg":"failed to check the configuration of plugin jwt-auth err:
property \"_meta\" validation failed: property \"filter\" validation failed:
failed to validate item 1: wrong type: expected array, got string"}
+{"error_msg":"failed to validate the 'vars' expression: invalid operator '*='"}
+
+
+
+=== TEST 12: proxy-rewrite plugin run with _meta filter vars
+--- 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 = {
+ ["proxy-rewrite"] = {
+ _meta = {
+ filter = {
+ {"arg_version", "==", "v2"}
+ }
+ },
+ uri = "/echo",
+ headers = {
+ ["X-Api-Version"] = "v2"
+ }
+ }
+ },
+ upstream = {
+ nodes = {
+ ["127.0.0.1:1980"] = 1
+ },
+ type = "roundrobin"
+ },
+ uri = "/hello"
+ }
+ )
+ if code >= 300 then
+ ngx.print(body)
+ else
+ ngx.say(body)
+ end
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 13: hit route: run proxy-rewrite plugin
+--- request
+GET /hello?version=v2
+--- response_headers
+x-api-version: v2
+
+
+
+=== TEST 14: hit route: not run proxy-rewrite plugin
+--- request
+GET /hello?version=v1
+--- response_body
+hello world
+
+
+
+=== TEST 15: different route,same plugin, different filter (for expr_lrucache)
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/2',
+ ngx.HTTP_PUT,
+ {
+ plugins = {
+ ["proxy-rewrite"] = {
+ _meta = {
+ filter = {
+ {"arg_version", "==", "v3"}
+ }
+ },
+ uri = "/echo",
+ headers = {
+ ["X-Api-Version"] = "v3"
+ }
+ }
+ },
+ upstream = {
+ nodes = {
+ ["127.0.0.1:1980"] = 1
+ },
+ type = "roundrobin"
+ },
+ uri = "/hello1"
+ }
+ )
+ if code >= 300 then
+ ngx.print(body)
+ else
+ ngx.say(body)
+ end
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 16: hit route: run proxy-rewrite plugin
+--- request
+GET /hello1?version=v3
+--- response_headers
+x-api-version: v3
+
+
+
+=== TEST 17: same plugin, same id between routes and global_rules, different
filter (for expr_lrucache)
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/global_rules/2',
+ ngx.HTTP_PUT,
+ {
+ plugins = {
+ ["proxy-rewrite"] = {
+ _meta = {
+ filter = {
+ {"arg_version", "==", "v4"}
+ }
+ },
+ uri = "/echo",
+ headers = {
+ ["X-Api-Version"] = "v4"
+ }
+ }
+ }
+ }
+ )
+ if code >= 300 then
+ ngx.print(body)
+ else
+ ngx.say(body)
+ end
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 18: hit route: run proxy-rewrite plugin
+--- request
+GET /hello1?version=v4
+--- response_headers
+x-api-version: v4