diff --git a/apisix/plugins/traffic-split.lua b/apisix/plugins/traffic-split.lua index 5364438e7ab3..f7e75af88655 100644 --- a/apisix/plugins/traffic-split.lua +++ b/apisix/plugins/traffic-split.lua @@ -278,8 +278,7 @@ function _M.access(conf, ctx) return end - local rr_up, err = core.lrucache.plugin_ctx(lrucache, ctx, nil, new_rr_obj, - weighted_upstreams) + local rr_up, err = lrucache(weighted_upstreams, nil, new_rr_obj, weighted_upstreams) if not rr_up then core.log.error("lrucache roundrobin failed: ", err) return 500 diff --git a/docs/en/latest/plugins/traffic-split.md b/docs/en/latest/plugins/traffic-split.md index 9189a81b7fbb..4882da1e123f 100644 --- a/docs/en/latest/plugins/traffic-split.md +++ b/docs/en/latest/plugins/traffic-split.md @@ -30,6 +30,7 @@ title: traffic-split - [Grayscale Release](#grayscale-release) - [Blue-green Release](#blue-green-release) - [Custom Release](#custom-release) + - [Matching rules correspond to upstream](#matching-rules-correspond-to-upstream) - [Disable Plugin](#disable-plugin) ## Name @@ -482,6 +483,98 @@ Content-Type: text/html; charset=utf-8 hello 1980 ``` +### Matching rules correspond to upstream + +By configuring multiple `rules`, we can achieve one-to-one correspondence between different matching rules and upstream. + +**Example:** + +When the request header `x-api-id` is equal to 1, it hits the upstream with port 1981; when `x-api-id` is equal to 2, it hits the upstream with port 1982; otherwise, it hits the upstream with port 1980 (the upstream response data is the corresponding port number). + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "/hello", + "plugins": { + "traffic-split": { + "rules": [ + { + "match": [ + { + "vars": [ + ["http_x-api-id","==","1"] + ] + } + ], + "weighted_upstreams": [ + { + "upstream": { + "name": "upstream-A", + "type": "roundrobin", + "nodes": { + "127.0.0.1:1981":1 + } + }, + "weight": 3 + } + ] + }, + { + "match": [ + { + "vars": [ + ["http_x-api-id","==","2"] + ] + } + ], + "weighted_upstreams": [ + { + "upstream": { + "name": "upstream-B", + "type": "roundrobin", + "nodes": { + "127.0.0.1:1982":1 + } + }, + "weight": 3 + } + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } +}' +``` + +**Test plugin:** + +The request header `x-api-id` is equal to 1, hitting the upstream with the 1981 port. + +```shell +$ curl http://127.0.0.1:9080/hello -H 'x-api-id: 1' +1981 +``` + +The request header `x-api-id` is equal to 2, hitting the upstream with the 1982 port. + +```shell +$ curl http://127.0.0.1:9080/hello -H 'x-api-id: 2' +1982 +``` + +The request header `x-api-id` is equal to 3, the rule does not match, and it hits the upstream with port 1980. + +```shell +$ curl http://127.0.0.1:9080/hello -H 'x-api-id: 3' +1980 +``` + ## Disable Plugin When you want to remove the traffic-split plugin, it's very simple, just delete the corresponding json configuration in the plugin configuration, no need to restart the service, it will take effect immediately: diff --git a/docs/zh/latest/plugins/traffic-split.md b/docs/zh/latest/plugins/traffic-split.md index 045ab54eb0d7..86e576d06f84 100644 --- a/docs/zh/latest/plugins/traffic-split.md +++ b/docs/zh/latest/plugins/traffic-split.md @@ -30,6 +30,7 @@ title: traffic-split - [灰度发布](#灰度发布) - [蓝绿发布](#蓝绿发布) - [自定义发布](#自定义发布) + - [匹配规则与上游对应](#匹配规则与上游对应) - [禁用插件](#禁用插件) ## 名字 @@ -493,6 +494,98 @@ Content-Type: text/html; charset=utf-8 hello 1980 ``` +### 匹配规则与上游对应 + +通过配置多个 `rules`,我们可以实现不同的匹配规则与上游一一对应。 + +**示例:** + +当请求头 `x-api-id` 等于 1 时,命中 1981 端口的上游;当 `x-api-id` 等于 2 时,命中 1982 端口的上游;否则,命中 1980 端口的上游(上游响应数据为对应的端口号)。 + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "/hello", + "plugins": { + "traffic-split": { + "rules": [ + { + "match": [ + { + "vars": [ + ["http_x-api-id","==","1"] + ] + } + ], + "weighted_upstreams": [ + { + "upstream": { + "name": "upstream-A", + "type": "roundrobin", + "nodes": { + "127.0.0.1:1981":1 + } + }, + "weight": 3 + } + ] + }, + { + "match": [ + { + "vars": [ + ["http_x-api-id","==","2"] + ] + } + ], + "weighted_upstreams": [ + { + "upstream": { + "name": "upstream-B", + "type": "roundrobin", + "nodes": { + "127.0.0.1:1982":1 + } + }, + "weight": 3 + } + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } +}' +``` + +**测试插件:** + +请求头 `x-api-id` 等于 1,命中带 1981 端口的上游。 + +```shell +$ curl http://127.0.0.1:9080/hello -H 'x-api-id: 1' +1981 +``` + +请求头 `x-api-id` 等于 2,命中带 1982 端口的上游。 + +```shell +$ curl http://127.0.0.1:9080/hello -H 'x-api-id: 2' +1982 +``` + +请求头 `x-api-id` 等于 3,规则不匹配,命中带 1980 端口的上游。 + +```shell +$ curl http://127.0.0.1:9080/hello -H 'x-api-id: 3' +1980 +``` + ## 禁用插件 当你想去掉 traffic-split 插件的时候,很简单,在插件的配置中把对应的 json 配置删除即可,无须重启服务,即刻生效: diff --git a/t/plugin/traffic-split2.t b/t/plugin/traffic-split2.t index 58df05f7b1c9..9fe275299420 100644 --- a/t/plugin/traffic-split2.t +++ b/t/plugin/traffic-split2.t @@ -408,3 +408,327 @@ hash_on: header chash_key: "world" hash_on: header chash_key: "hello" + + + +=== TEST 12: the plugin has multiple weighted_upstreams(upstream method) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PATCH, + [=[{ + "uri": "/server_port", + "plugins": { + "traffic-split": { + "rules": [ + { + "match": [ + { + "vars": [["arg_id","==","1"]] + } + ], + "weighted_upstreams": [ + { + "upstream": { + "name": "upstream_A", + "type": "roundrobin", + "nodes": { + "127.0.0.1:1981":1 + } + }, + "weight": 1 + } + ] + }, + { + "match": [ + { + "vars": [["arg_id","==","2"]] + } + ], + "weighted_upstreams": [ + { + "upstream": { + "name": "upstream_B", + "type": "roundrobin", + "nodes": { + "127.0.0.1:1982":1 + } + }, + "weight": 1 + } + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]=] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 13: hit each upstream separately +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local bodys = {} + for i = 1, 9, 3 do + local _, _, body = t('/server_port', ngx.HTTP_GET) + local _, _, body2 = t('/server_port?id=1', ngx.HTTP_GET) + local _, _, body3 = t('/server_port?id=2', ngx.HTTP_GET) + bodys[i] = body + bodys[i+1] = body2 + bodys[i+2] = body3 + end + + ngx.say(table.concat(bodys, ", ")) + } +} +--- response_body eval +qr/1980, 1981, 1982, 1980, 1981, 1982, 1980, 1981, 1982/ +--- no_error_log +[error] + + + +=== TEST 14: the plugin has multiple weighted_upstreams and has a default routing weight in weighted_upstreams +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PATCH, + [=[{ + "uri": "/server_port", + "plugins": { + "traffic-split": { + "rules": [ + { + "match": [ + { + "vars": [["arg_id","==","1"]] + } + ], + "weighted_upstreams": [ + { + "upstream": { + "name": "upstream_A", + "type": "roundrobin", + "nodes": { + "127.0.0.1:1981":1 + } + }, + "weight": 1 + }, + { + "weight": 1 + } + ] + }, + { + "match": [ + { + "vars": [["arg_id","==","2"]] + } + ], + "weighted_upstreams": [ + { + "upstream": { + "name": "upstream_B", + "type": "roundrobin", + "nodes": { + "127.0.0.1:1982":1 + } + }, + "weight": 1 + }, + { + "weight": 1 + } + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]=] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 15: every weighted_upstreams in the plugin is hit +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local bodys = {} + for i = 1, 8, 2 do + local _, _, body = t('/server_port?id=1', ngx.HTTP_GET) + local _, _, body2 = t('/server_port?id=2', ngx.HTTP_GET) + bodys[i] = body + bodys[i+1] = body2 + end + + table.sort(bodys) + ngx.say(table.concat(bodys, ", ")) + } +} +--- response_body eval +qr/1980, 1980, 1980, 1980, 1981, 1981, 1982, 1982/ +--- no_error_log +[error] + + + +=== TEST 16: set upstream(upstream_id: 1, upstream_id: 2) and add route +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/upstreams/1', + ngx.HTTP_PUT, + [[{ + "nodes": { + "127.0.0.1:1981": 1 + }, + "type": "roundrobin", + "desc": "new upstream A" + }]] + ) + + if code >= 300 then + ngx.status = code + ngx.say(body) + end + + code, body = t('/apisix/admin/upstreams/2', + ngx.HTTP_PUT, + [[{ + "nodes": { + "127.0.0.1:1982": 1 + }, + "type": "roundrobin", + "desc": "new upstream B" + }]] + ) + + if code >= 300 then + ngx.status = code + ngx.say(body) + end + + code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PATCH, + [=[{ + "uri": "/server_port", + "plugins": { + "traffic-split": { + "rules": [ + { + "match": [ + { + "vars": [["arg_id","==","1"]] + } + ], + "weighted_upstreams": [ + { + "upstream_id": 1, + "weight": 1 + } + ] + }, + { + "match": [ + { + "vars": [["arg_id","==","2"]] + } + ], + "weighted_upstreams": [ + { + "upstream_id": 2, + "weight": 1 + } + ] + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]=] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 17: hit each upstream separately +--- config +location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local bodys = {} + for i = 1, 9, 3 do + local _, _, body = t('/server_port', ngx.HTTP_GET) + local _, _, body2 = t('/server_port?id=1', ngx.HTTP_GET) + local _, _, body3 = t('/server_port?id=2', ngx.HTTP_GET) + bodys[i] = body + bodys[i+1] = body2 + bodys[i+2] = body3 + end + + ngx.say(table.concat(bodys, ", ")) + } +} +--- response_body eval +qr/1980, 1981, 1982, 1980, 1981, 1982, 1980, 1981, 1982/ +--- no_error_log +[error]