Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions apisix/plugin.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1295,6 +1295,16 @@ local function run_meta_pre_function(conf, api_ctx, name)
end
end

-- mark a plugin to be skipped for the rest of the request, so a plugin run as
-- a workflow action does not run again in the normal plugin chain
function _M.skip_plugin(ctx, plugin_name)
if not ctx._skip_plugins then
ctx._skip_plugins = {}
end
ctx._skip_plugins[plugin_name] = true
end


function _M.run_plugin(phase, plugins, api_ctx)
local plugin_run = false
api_ctx = api_ctx or ngx.ctx.api_ctx
Expand Down Expand Up @@ -1329,6 +1339,9 @@ function _M.run_plugin(phase, plugins, api_ctx)
run_meta_pre_function(conf, api_ctx, plugins[i]["name"])
plugin_run = true
api_ctx._plugin_name = plugins[i]["name"]
if api_ctx._skip_plugins and api_ctx._skip_plugins[api_ctx._plugin_name] then
goto CONTINUE
end
local code, body = phase_func(conf, api_ctx)
api_ctx._plugin_name = nil
if code or body then
Expand Down Expand Up @@ -1367,12 +1380,17 @@ function _M.run_plugin(phase, plugins, api_ctx)
plugin_run = true
run_meta_pre_function(conf, api_ctx, plugins[i]["name"])
api_ctx._plugin_name = plugins[i]["name"]
if api_ctx._skip_plugins and api_ctx._skip_plugins[api_ctx._plugin_name] then
goto CONTINUE
end
local span = tracer.start(api_ctx.ngx_ctx, "apisix.phase." .. phase
.. ".plugins." .. api_ctx._plugin_name)
phase_func(conf, api_ctx)
span:finish(api_ctx.ngx_ctx)
api_ctx._plugin_name = nil
end

::CONTINUE::
end

return api_ctx, plugin_run
Expand Down
3 changes: 3 additions & 0 deletions apisix/plugins/workflow.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
-- limitations under the License.
--
local core = require("apisix.core")
local plugin = require("apisix.plugin")
local expr = require("resty.expr.v1")
local ipairs = ipairs
local setmetatable = setmetatable
Expand Down Expand Up @@ -167,6 +168,8 @@ function _M.access(conf, ctx)
if match_result then
-- only one action is currently supported
local action = rule.actions[1]
-- skip the action plugin in the chain so it does not run twice
plugin.skip_plugin(ctx, action[1])

local action_name = action[1]
local action_conf = action[2]
Expand Down
6 changes: 6 additions & 0 deletions docs/en/latest/plugins/workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ import TabItem from '@theme/TabItem';

The `workflow` Plugin supports the conditional execution of user-defined actions to client traffic based on a given set of rules, defined using [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list). This provides a granular approach to traffic management.

:::note

When a Plugin such as `limit-count` or `limit-conn` is used as a `workflow` action, the same Plugin is automatically skipped in the normal Plugin chain for that request. This avoids running it twice (for example, counting a request twice against a rate limit) when the Plugin is also configured directly on the same Route, Service, Consumer, or Global Rule.

:::

## Attributes

| Name | Type | Required | Default | Valid values | Description |
Expand Down
137 changes: 137 additions & 0 deletions t/plugin/workflow-without-case.t
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,140 @@ passed
--- request
GET /hello
--- error_code: 403



=== TEST 3: create a route with key-auth & limit-count plugin
--- 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": {
"key-auth": {},
"limit-count": {
"count": 3,
"time_window": 10,
"rejected_code": 503
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)

if code >= 300 then
ngx.status = code
end

ngx.say(body)
}
}
--- response_body
passed



=== TEST 4: create a consumer rose
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "rose",
"plugins": {
"key-auth": {
"key": "rose"
}
}
}]]
)

if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed



=== TEST 5: create a consumer jack with workflow plugin
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "jack",
"plugins": {
"key-auth": {
"key": "jack"
},
"workflow": {
"rules": [
{
"case": [
["route_id", "==", "1"]
],
"actions": [
[
"limit-count",
{
"count": 5,
"time_window": 10,
"rejected_code": 429
}
]
]
}
]
}
}
}]]
)

if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed



=== TEST 6: send request with rose consumer, only the chain limit-count applies
--- pipelined_requests eval
["GET /hello", "GET /hello", "GET /hello", "GET /hello"]
--- more_headers
apikey: rose
--- error_code eval
[200, 200, 200, 503]



=== TEST 7: send request with jack consumer, the chain limit-count is skipped so only the workflow action counts
--- pipelined_requests eval
["GET /hello", "GET /hello", "GET /hello", "GET /hello", "GET /hello", "GET /hello"]
--- more_headers
apikey: jack
--- error_code eval
[200, 200, 200, 200, 200, 429]
Loading