From c02d44f8e723c6f5e4b1f7c4ff82e82bda5229be Mon Sep 17 00:00:00 2001 From: YuanSheng Wang Date: Mon, 17 Jun 2019 23:17:41 +0800 Subject: [PATCH] feature: added test cases about round-robin load balance algorithms. (#123) * feature: added test cases about round-robin load balance algorithms. * feature: added test cases about consistent hash algorithms. --- lua/apisix/admin/upstreams.lua | 4 + lua/apisix/core/schema.lua | 64 +++++ t/APISix.pm | 13 +- t/{ => admin}/balancer.t | 0 t/admin/upstream.t | 31 +++ t/lib/server.lua | 24 ++ t/node/chash-balance.t | 175 ++++++++++++ .../{no-upstream.t => not-exist-upstream.t} | 10 + t/node/rr-balance.t | 251 ++++++++++++++++++ 9 files changed, 570 insertions(+), 2 deletions(-) rename t/{ => admin}/balancer.t (100%) create mode 100644 t/lib/server.lua create mode 100644 t/node/chash-balance.t rename t/node/{no-upstream.t => not-exist-upstream.t} (84%) create mode 100644 t/node/rr-balance.t diff --git a/lua/apisix/admin/upstreams.lua b/lua/apisix/admin/upstreams.lua index 54f988fd0bf0..caf753536326 100644 --- a/lua/apisix/admin/upstreams.lua +++ b/lua/apisix/admin/upstreams.lua @@ -29,6 +29,10 @@ function _M.put(uri_segs, conf) return 400, {error_msg = "invalid configuration: " .. err} end + if conf.type == "chash" and not conf.key then + return 400, {error_msg = "missing key"} + end + local key = "/upstreams/" .. id core.log.info("key: ", key) local res, err = core.etcd.set(key, conf) diff --git a/lua/apisix/core/schema.lua b/lua/apisix/core/schema.lua index 33f6495f0b69..0af39ad47ac7 100644 --- a/lua/apisix/core/schema.lua +++ b/lua/apisix/core/schema.lua @@ -34,6 +34,66 @@ local id_schema = { } } +-- todo: chash and roundrobin have different properties, we may support +-- this limitation later. + +-- { +-- "definitions": { +-- "nodes": { +-- "patternProperties": { +-- ".*": { +-- "minimum": 1, +-- "type": "integer" +-- } +-- }, +-- "minProperties": 1, +-- "type": "object" +-- } +-- }, +-- "type": "object", +-- "anyOf": [ +-- { +-- "properties": { +-- "type": { +-- "type": "string", +-- "enum": [ +-- "roundrobin" +-- ] +-- }, +-- "nodes": { +-- "$ref": "#/definitions/nodes" +-- } +-- }, +-- "required": [ +-- "type", +-- "nodes" +-- ], +-- "additionalProperties": false +-- }, +-- { +-- "properties": { +-- "type": { +-- "type": "string", +-- "enum": [ +-- "chash" +-- ] +-- }, +-- "nodes": { +-- "$ref": "#/definitions/nodes" +-- }, +-- "key": { +-- "type": "string" +-- } +-- }, +-- "required": [ +-- "key", +-- "type", +-- "nodes" +-- ], +-- "additionalProperties": false +-- } +-- ] +-- } local upstream_schema = { type = "object", @@ -52,6 +112,10 @@ local upstream_schema = { type = "string", enum = {"chash", "roundrobin"} }, + key = { + type = "string", + enum = {"remote_addr"}, + }, id = id_schema }, required = {"nodes", "type"}, diff --git a/t/APISix.pm b/t/APISix.pm index 86bf8515c389..b0ca62a282e6 100644 --- a/t/APISix.pm +++ b/t/APISix.pm @@ -4,6 +4,10 @@ use lib 'lib'; use Cwd qw(cwd); use Test::Nginx::Socket::Lua::Stream -Base; +log_level('info'); +no_long_string(); +no_shuffle(); + my $pwd = cwd(); sub read_file($) { @@ -60,11 +64,16 @@ _EOC_ server { listen 1980; + listen 1981; + listen 1982; - location /hello { - echo "hello world"; + location / { + content_by_lua_block { + require("lib.server").go() + } } } + _EOC_ $block->set_value("http_config", $http_config); diff --git a/t/balancer.t b/t/admin/balancer.t similarity index 100% rename from t/balancer.t rename to t/admin/balancer.t diff --git a/t/admin/upstream.t b/t/admin/upstream.t index b18a9d41caad..c34910c60ad9 100644 --- a/t/admin/upstream.t +++ b/t/admin/upstream.t @@ -416,6 +416,7 @@ GET /t local code, body = t('/apisix/admin/upstreams/1', ngx.HTTP_PUT, [[{ + "key": "remote_addr", "nodes": { "127.0.0.1:8080": 1 }, @@ -424,6 +425,7 @@ GET /t [[{ "node": { "value": { + "key": "remote_addr", "nodes": { "127.0.0.1:8080": 1 }, @@ -535,3 +537,32 @@ GET /t {"error_msg":"invalid configuration: invalid \"patternProperties\" in docuement at pointer \"#\/nodes\/127.0.0.1%3A8080\""} --- no_error_log [error] + + + +=== TEST 17: set upstream (missing key) +--- 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:8080": 1 + }, + "type": "chash" + }]] + ) + + ngx.status = code + ngx.print(body) + } + } +--- request +GET /t +--- error_code: 400 +--- response_body +{"error_msg":"missing key"} +--- no_error_log +[error] diff --git a/t/lib/server.lua b/t/lib/server.lua new file mode 100644 index 000000000000..a4c07e5c9432 --- /dev/null +++ b/t/lib/server.lua @@ -0,0 +1,24 @@ +local _M = {} + + +function _M.hello() + ngx.say("hello world") +end + + +function _M.server_port() + ngx.print(ngx.var.server_port) +end + + +function _M.go() + local action = string.sub(ngx.var.uri, 2) + if not _M[action] then + return ngx.exit(404) + end + + return _M[action]() +end + + +return _M diff --git a/t/node/chash-balance.t b/t/node/chash-balance.t new file mode 100644 index 000000000000..df932f2eba26 --- /dev/null +++ b/t/node/chash-balance.t @@ -0,0 +1,175 @@ +BEGIN { + if ($ENV{TEST_NGINX_CHECK_LEAK}) { + $SkipReason = "unavailable for the hup tests"; + + } else { + $ENV{TEST_NGINX_USE_HUP} = 1; + undef $ENV{TEST_NGINX_USE_STAP}; + } +} + +use t::APISix 'no_plan'; + +repeat_each(1); +log_level('info'); +no_root_location(); +no_shuffle(); + +run_tests(); + +__DATA__ + +=== TEST 1: set route(two upstream node) +--- 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": {}, + "uri": "/server_port", + "upstream": { + "key": "remote_addr", + "type": "chash", + "nodes": { + "127.0.0.1:1980": 1, + "127.0.0.1:1981": 1 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 2: hit routes +--- config + location /t { + content_by_lua_block { + local http = require "resty.http" + local uri = "http://127.0.0.1:" .. ngx.var.server_port + .. "/server_port" + + local ports_count = {} + for i = 1, 12 do + local httpc = http.new() + local res, err = httpc:request_uri(uri, {method = "GET"}) + if not res then + ngx.say(err) + return + end + ports_count[res.body] = (ports_count[res.body] or 0) + 1 + end + + local ports_arr = {} + for port, count in pairs(ports_count) do + table.insert(ports_arr, {port = port, count = count}) + end + + local function cmd(a, b) + return a.port > b.port + end + table.sort(ports_arr, cmd) + + ngx.say(require("cjson").encode(ports_arr)) + ngx.exit(200) + } + } +--- request +GET /t +--- response_body +[{"count":12,"port":"1980"}] +--- no_error_log +[error] + + + +=== TEST 3: set route(three upstream node) +--- 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": {}, + "uri": "/server_port", + "upstream": { + "key": "remote_addr", + "type": "chash", + "nodes": { + "127.0.0.1:1980": 1, + "127.0.0.1:1981": 1, + "127.0.0.1:1982": 1 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 4: hit routes +--- config + location /t { + content_by_lua_block { + local http = require "resty.http" + local uri = "http://127.0.0.1:" .. ngx.var.server_port + .. "/server_port" + + local ports_count = {} + for i = 1, 12 do + local httpc = http.new() + local res, err = httpc:request_uri(uri, {method = "GET"}) + if not res then + ngx.say(err) + return + end + ports_count[res.body] = (ports_count[res.body] or 0) + 1 + end + + local ports_arr = {} + for port, count in pairs(ports_count) do + table.insert(ports_arr, {port = port, count = count}) + end + + local function cmd(a, b) + return a.port > b.port + end + table.sort(ports_arr, cmd) + + ngx.say(require("cjson").encode(ports_arr)) + ngx.exit(200) + } + } +--- request +GET /t +--- response_body +[{"count":12,"port":"1982"}] +--- no_error_log +[error] diff --git a/t/node/no-upstream.t b/t/node/not-exist-upstream.t similarity index 84% rename from t/node/no-upstream.t rename to t/node/not-exist-upstream.t index 87d44490ea59..f7250d4865f7 100644 --- a/t/node/no-upstream.t +++ b/t/node/not-exist-upstream.t @@ -1,3 +1,13 @@ +BEGIN { + if ($ENV{TEST_NGINX_CHECK_LEAK}) { + $SkipReason = "unavailable for the hup tests"; + + } else { + $ENV{TEST_NGINX_USE_HUP} = 1; + undef $ENV{TEST_NGINX_USE_STAP}; + } +} + use t::APISix 'no_plan'; repeat_each(1); diff --git a/t/node/rr-balance.t b/t/node/rr-balance.t new file mode 100644 index 000000000000..4c9d06f9208b --- /dev/null +++ b/t/node/rr-balance.t @@ -0,0 +1,251 @@ +BEGIN { + if ($ENV{TEST_NGINX_CHECK_LEAK}) { + $SkipReason = "unavailable for the hup tests"; + + } else { + $ENV{TEST_NGINX_USE_HUP} = 1; + undef $ENV{TEST_NGINX_USE_STAP}; + } +} + +use t::APISix 'no_plan'; + +repeat_each(1); +log_level('info'); +no_root_location(); +no_shuffle(); + +run_tests(); + +__DATA__ + +=== TEST 1: set route(two upstream node) +--- 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": {}, + "uri": "/server_port", + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1, + "127.0.0.1:1981": 1 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 2: hit routes +--- config + location /t { + content_by_lua_block { + local http = require "resty.http" + local uri = "http://127.0.0.1:" .. ngx.var.server_port + .. "/server_port" + + local ports_count = {} + for i = 1, 12 do + local httpc = http.new() + local res, err = httpc:request_uri(uri, {method = "GET"}) + if not res then + ngx.say(err) + return + end + ports_count[res.body] = (ports_count[res.body] or 0) + 1 + end + + local ports_arr = {} + for port, count in pairs(ports_count) do + table.insert(ports_arr, {port = port, count = count}) + end + + local function cmd(a, b) + return a.port > b.port + end + table.sort(ports_arr, cmd) + + ngx.say(require("cjson").encode(ports_arr)) + ngx.exit(200) + } + } +--- request +GET /t +--- response_body +[{"count":6,"port":"1981"},{"count":6,"port":"1980"}] +--- no_error_log +[error] + + + +=== TEST 3: set route(three upstream node) +--- 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": {}, + "uri": "/server_port", + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1, + "127.0.0.1:1981": 1, + "127.0.0.1:1982": 1 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 4: hit routes +--- config + location /t { + content_by_lua_block { + local http = require "resty.http" + local uri = "http://127.0.0.1:" .. ngx.var.server_port + .. "/server_port" + + local ports_count = {} + for i = 1, 12 do + local httpc = http.new() + local res, err = httpc:request_uri(uri, {method = "GET"}) + if not res then + ngx.say(err) + return + end + ports_count[res.body] = (ports_count[res.body] or 0) + 1 + end + + local ports_arr = {} + for port, count in pairs(ports_count) do + table.insert(ports_arr, {port = port, count = count}) + end + + local function cmd(a, b) + return a.port > b.port + end + table.sort(ports_arr, cmd) + + ngx.say(require("cjson").encode(ports_arr)) + ngx.exit(200) + } + } +--- request +GET /t +--- response_body +[{"count":4,"port":"1982"},{"count":4,"port":"1981"},{"count":4,"port":"1980"}] +--- no_error_log +[error] + + + +=== TEST 5: set route(three upstream node and different weight) +--- 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": {}, + "uri": "/server_port", + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 3, + "127.0.0.1:1981": 2, + "127.0.0.1:1982": 1 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 6: hit routes +--- config + location /t { + content_by_lua_block { + local http = require "resty.http" + local uri = "http://127.0.0.1:" .. ngx.var.server_port + .. "/server_port" + + local ports_count = {} + for i = 1, 12 do + local httpc = http.new() + local res, err = httpc:request_uri(uri, {method = "GET"}) + if not res then + ngx.say(err) + return + end + ports_count[res.body] = (ports_count[res.body] or 0) + 1 + end + + local ports_arr = {} + for port, count in pairs(ports_count) do + table.insert(ports_arr, {port = port, count = count}) + end + + local function cmd(a, b) + return a.port > b.port + end + table.sort(ports_arr, cmd) + + ngx.say(require("cjson").encode(ports_arr)) + ngx.exit(200) + } + } +--- request +GET /t +--- response_body +[{"count":2,"port":"1982"},{"count":4,"port":"1981"},{"count":6,"port":"1980"}] +--- no_error_log +[error]