From 592c7561fb1aec6fcbf13cf2da11e2bb850bf3ff Mon Sep 17 00:00:00 2001 From: 70335265 Date: Mon, 9 Apr 2018 08:44:59 +0900 Subject: [PATCH 1/7] have whitespace between the test blocks --- spec/policy/rate_limit/rate_limit_spec.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spec/policy/rate_limit/rate_limit_spec.lua b/spec/policy/rate_limit/rate_limit_spec.lua index 31bf6b9e4..f787eeacc 100644 --- a/spec/policy/rate_limit/rate_limit_spec.lua +++ b/spec/policy/rate_limit/rate_limit_spec.lua @@ -67,6 +67,7 @@ describe('Rate limit policy', function() local rate_limit_policy = RateLimitPolicy.new(config) rate_limit_policy:access() end) + it('invalid limiter values', function() local config = { limiters = { @@ -78,6 +79,7 @@ describe('Rate limit policy', function() rate_limit_policy:access() assert.spy(ngx_exit_spy).was_called_with(500) end) + it('no redis url', function() local config = { limiters = { @@ -89,6 +91,7 @@ describe('Rate limit policy', function() local rate_limit_policy = RateLimitPolicy.new(config) rate_limit_policy:access() end) + it('invalid redis url', function() local config = { limiters = { @@ -100,6 +103,7 @@ describe('Rate limit policy', function() rate_limit_policy:access() assert.spy(ngx_exit_spy).was_called_with(500) end) + it('rejected (conn)', function() local config = { limiters = { @@ -112,6 +116,7 @@ describe('Rate limit policy', function() rate_limit_policy:access() assert.spy(ngx_exit_spy).was_called_with(429) end) + it('rejected (req)', function() local config = { limiters = { @@ -124,6 +129,7 @@ describe('Rate limit policy', function() rate_limit_policy:access() assert.spy(ngx_exit_spy).was_called_with(429) end) + it('rejected (count)', function() local config = { limiters = { @@ -136,6 +142,7 @@ describe('Rate limit policy', function() rate_limit_policy:access() assert.spy(ngx_exit_spy).was_called_with(429) end) + it('delay (conn)', function() local config = { limiters = { @@ -148,6 +155,7 @@ describe('Rate limit policy', function() rate_limit_policy:access() assert.spy(ngx_sleep_spy).was_called_with(match.is_gt(0.001)) end) + it('delay (req)', function() local config = { limiters = { @@ -161,6 +169,7 @@ describe('Rate limit policy', function() assert.spy(ngx_sleep_spy).was_called_with(match.is_gt(0.001)) end) end) + describe('.log', function() it('success in leaving', function() local config = { From dbe4ede7fe373265d7e847e6122842992269eeaa Mon Sep 17 00:00:00 2001 From: 70335265 Date: Mon, 9 Apr 2018 08:47:13 +0900 Subject: [PATCH 2/7] change ">= 0.001" to ">0" --- gateway/src/apicast/policy/rate_limit/rate_limit.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gateway/src/apicast/policy/rate_limit/rate_limit.lua b/gateway/src/apicast/policy/rate_limit/rate_limit.lua index 1b057b8f6..aba4d3a5a 100644 --- a/gateway/src/apicast/policy/rate_limit/rate_limit.lua +++ b/gateway/src/apicast/policy/rate_limit/rate_limit.lua @@ -156,7 +156,7 @@ function _M:access() ctx.keys = keys_committed end - if delay >= 0.001 then + if delay > 0 then ngx.log(ngx.WARN, 'need to delay by: ', delay, 's, states: ', table.concat(states, ", ")) ngx.sleep(delay) end From 12c19149e398df28a23785d8772e59a01df60b03 Mon Sep 17 00:00:00 2001 From: 70335265 Date: Mon, 9 Apr 2018 08:50:11 +0900 Subject: [PATCH 3/7] change description of the json schema --- .../policy/rate_limit/apicast-policy.json | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/gateway/src/apicast/policy/rate_limit/apicast-policy.json b/gateway/src/apicast/policy/rate_limit/apicast-policy.json index ad8316849..b22508f58 100644 --- a/gateway/src/apicast/policy/rate_limit/apicast-policy.json +++ b/gateway/src/apicast/policy/rate_limit/apicast-policy.json @@ -17,23 +17,23 @@ "name": { "type": "string", "enum": ["connections"], - "description": "limiting request concurrency (or concurrent connections)" + "description": "Limiting request concurrency (or concurrent connections)" }, "key": { - "description": "Key of limiter", + "description": "The user key corresponding to the limiter object. This must be unique in the scope.", "type": "string" }, "conn": { "type": "integer", - "description": "the maximum number of concurrent requests allowed" + "description": "The maximum number of concurrent requests allowed" }, "burst": { "type": "integer", - "description": "the number of excessive concurrent requests (or connections) allowed to be delayed" + "description": "The number of excessive concurrent requests (or connections) allowed to be delayed" }, "delay": { "type": "number", - "description": "the default processing latency of a typical connection (or request)" + "description": "The default processing latency of a typical connection (or request)" } } }, { @@ -42,19 +42,19 @@ "name": { "type": "string", "enum": ["leaky_bucket"], - "description": "limiting request rate" + "description": "Limiting request rate" }, "key": { - "description": "Key of limiter", + "description": "The user key corresponding to the limiter object. This must be unique in the scope.", "type": "string" }, "rate": { "type": "integer", - "description": "the specified request rate (number per second) threshold" + "description": "The specified request rate (number per second) threshold" }, "burst": { "type": "integer", - "description": "the number of excessive requests per second allowed to be delayed" + "description": "The number of excessive requests per second allowed to be delayed" } } }, { @@ -63,19 +63,19 @@ "name": { "type": "string", "enum": ["fixed_window"], - "description": "limiting request counts" + "description": "Limiting request counts" }, "key": { - "description": "Key of limiter", + "description": "The user key corresponding to the limiter object. This must be unique in the scope.", "type": "string" }, "count": { "type": "integer", - "description": "the specified number of requests threshold" + "description": "The specified number of requests threshold" }, "window": { "type": "integer", - "description": "the time window in seconds before the request count is reset" + "description": "The time window in seconds before the request count is reset" } } }] @@ -87,11 +87,11 @@ }, "status_code_rejected": { "type": "integer", - "description": "the status code when requests over the limit, default 429" + "description": "The status code when requests over the limit, default 429" }, "logging_only": { "type": "boolean", - "description": "if true, the request goes through when there is some issue with rate limiting, default false" + "description": "If true, the request goes through when there is some issue with rate limiting, default false" } } } From 3ffff7a9a44a84f2c9acff585194cc6edb618dce Mon Sep 17 00:00:00 2001 From: 70335265 Date: Mon, 9 Apr 2018 09:14:24 +0900 Subject: [PATCH 4/7] change to table.insert --- gateway/src/apicast/policy/rate_limit/rate_limit.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gateway/src/apicast/policy/rate_limit/rate_limit.lua b/gateway/src/apicast/policy/rate_limit/rate_limit.lua index aba4d3a5a..65c097449 100644 --- a/gateway/src/apicast/policy/rate_limit/rate_limit.lua +++ b/gateway/src/apicast/policy/rate_limit/rate_limit.lua @@ -121,8 +121,8 @@ function _M:access() end end - limiters[#limiters + 1] = lim - keys[#keys + 1] = limiter.key + table.insert(limiters, lim) + table.insert(keys, limiter.key) end @@ -145,8 +145,8 @@ function _M:access() for i, lim in ipairs(limiters) do if lim.is_committed and lim:is_committed() then - connections_committed[#connections_committed + 1] = lim - keys_committed[#keys_committed + 1] = keys[i] + table.insert(connections_committed, lim) + table.insert(keys_committed, keys[i]) end end From d5c2d13806965292abc3faeffeaefb31ce6cb87d Mon Sep 17 00:00:00 2001 From: 70335265 Date: Tue, 10 Apr 2018 09:38:46 +0900 Subject: [PATCH 5/7] remove try function --- .../apicast/policy/rate_limit/rate_limit.lua | 23 +------------------ spec/policy/rate_limit/rate_limit_spec.lua | 12 ---------- t/apicast-policy-rate-limit.t | 2 +- 3 files changed, 2 insertions(+), 35 deletions(-) diff --git a/gateway/src/apicast/policy/rate_limit/rate_limit.lua b/gateway/src/apicast/policy/rate_limit/rate_limit.lua index 65c097449..3f84a0776 100644 --- a/gateway/src/apicast/policy/rate_limit/rate_limit.lua +++ b/gateway/src/apicast/policy/rate_limit/rate_limit.lua @@ -26,27 +26,6 @@ local traffic_limiters = { end } -local function try(f, catch_f) - local status, exception = pcall(f) - if not status then - catch_f(exception) - end -end - -local function init_limiter(config) - local lim, limerr - try( - function() - lim, limerr = traffic_limiters[config.name](config) - end, - function(e) - return nil, e - end - ) - - return lim, limerr -end - local function redis_shdict(url) local options = { url = url } local redis, err = ts.connect_redis(options) @@ -104,7 +83,7 @@ function _M:access() local keys = {} for _, limiter in ipairs(self.limiters) do - local lim, initerr = init_limiter(limiter) + local lim, initerr = traffic_limiters[limiter.name](limiter) if not lim then ngx.log(ngx.ERR, "unknown limiter: ", limiter.name, ", err: ", initerr) error(self.logging_only, 500) diff --git a/spec/policy/rate_limit/rate_limit_spec.lua b/spec/policy/rate_limit/rate_limit_spec.lua index f787eeacc..b303d69c0 100644 --- a/spec/policy/rate_limit/rate_limit_spec.lua +++ b/spec/policy/rate_limit/rate_limit_spec.lua @@ -68,18 +68,6 @@ describe('Rate limit policy', function() rate_limit_policy:access() end) - it('invalid limiter values', function() - local config = { - limiters = { - {name = "fixed_window", key = 'test1', count = 0, window = 10} - }, - redis_url = 'redis://'..redis_host..':'..redis_port..'/1' - } - local rate_limit_policy = RateLimitPolicy.new(config) - rate_limit_policy:access() - assert.spy(ngx_exit_spy).was_called_with(500) - end) - it('no redis url', function() local config = { limiters = { diff --git a/t/apicast-policy-rate-limit.t b/t/apicast-policy-rate-limit.t index 3bce64e6e..12a201f2e 100644 --- a/t/apicast-policy-rate-limit.t +++ b/t/apicast-policy-rate-limit.t @@ -54,7 +54,7 @@ Return 500 code. GET / --- error_code: 500 --- error_log -unknown limiter +assertion failed! === TEST 3: Invalid redis url. Return 500 code. From e66bb690a508d950b7d197145738a2c9c5149efc Mon Sep 17 00:00:00 2001 From: 70335265 Date: Tue, 10 Apr 2018 11:34:25 +0900 Subject: [PATCH 6/7] change to define redis just once --- .../apicast/policy/rate_limit/rate_limit.lua | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/gateway/src/apicast/policy/rate_limit/rate_limit.lua b/gateway/src/apicast/policy/rate_limit/rate_limit.lua index 3f84a0776..03d1d1656 100644 --- a/gateway/src/apicast/policy/rate_limit/rate_limit.lua +++ b/gateway/src/apicast/policy/rate_limit/rate_limit.lua @@ -82,6 +82,17 @@ function _M:access() local limiters = {} local keys = {} + local red + if self.redis_url then + local rederr + red, rederr = redis_shdict(self.redis_url) + if not red then + ngx.log(ngx.ERR, "failed to connect Redis: ", rederr) + error(self.logging_only, 500) + return + end + end + for _, limiter in ipairs(self.limiters) do local lim, initerr = traffic_limiters[limiter.name](limiter) if not lim then @@ -90,22 +101,13 @@ function _M:access() return end - if self.redis_url then - local rediserr - lim.dict, rediserr = redis_shdict(self.redis_url) - if not lim.dict then - ngx.log(ngx.ERR, "failed to connect Redis: ", rediserr) - error(self.logging_only, 500) - return - end - end + lim.dict = red or lim.dict table.insert(limiters, lim) table.insert(keys, limiter.key) end - local states = {} local connections_committed = {} local keys_committed = {} @@ -147,16 +149,20 @@ local function checkin(_, ctx, time, semaphore, redis_url, logging_only) local keys = ctx.keys local latency = tonumber(time) - for i, lim in ipairs(limiters) do - if redis_url then - local rediserr - lim.dict, rediserr = redis_shdict(redis_url) - if not lim.dict then - ngx.log(ngx.ERR, "failed to connect Redis: ", rediserr) - error(logging_only, 500) - return - end + local red + if redis_url then + local rederr + red, rederr = redis_shdict(redis_url) + if not red then + ngx.log(ngx.ERR, "failed to connect Redis: ", rederr) + error(logging_only, 500) + return end + end + + for i, lim in ipairs(limiters) do + lim.dict = red or lim.dict + local conn, err = lim:leaving(keys[i], latency) if not conn then ngx.log(ngx.ERR, "failed to record the connection leaving request: ", err) From 4ab2ac80d1bd6f952de4a0068932b5a992619e0b Mon Sep 17 00:00:00 2001 From: 70335265 Date: Tue, 10 Apr 2018 09:29:01 +0900 Subject: [PATCH 7/7] add "scope" of the key and "error_settings" and define "minimum" --- .../policies/rate_limit_configuration.lua | 6 +- .../policy/rate_limit/apicast-policy.json | 166 +++++++++++++++--- .../apicast/policy/rate_limit/rate_limit.lua | 63 +++++-- spec/policy/rate_limit/rate_limit_spec.lua | 46 +++-- t/apicast-policy-rate-limit.t | 130 +++++++++----- 5 files changed, 316 insertions(+), 95 deletions(-) diff --git a/examples/policies/rate_limit_configuration.lua b/examples/policies/rate_limit_configuration.lua index a0eb868b2..9f730de82 100644 --- a/examples/policies/rate_limit_configuration.lua +++ b/examples/policies/rate_limit_configuration.lua @@ -4,20 +4,20 @@ local rate_limit_policy = require('apicast.policy.rate_limit').new({ limiters = { { name = "connections", - key = "limit1", + key = {name = "limit1"}, conn = 20, burst = 10, delay = 0.5 }, { name = "leaky_bucket", - key = "limit2", + key = {name = "limit2"}, rate = 18, burst = 9 }, { name = "fixed_window", - key = "limit3", + key = {name = "limit3"}, count = 10, window = 10 }}, diff --git a/gateway/src/apicast/policy/rate_limit/apicast-policy.json b/gateway/src/apicast/policy/rate_limit/apicast-policy.json index b22508f58..be933890b 100644 --- a/gateway/src/apicast/policy/rate_limit/apicast-policy.json +++ b/gateway/src/apicast/policy/rate_limit/apicast-policy.json @@ -11,7 +11,7 @@ "description": "List of limiters to be applied", "type": "array", "items": { - "oneOf": [{ + "anyOf": [{ "type": "object", "properties": { "name": { @@ -20,20 +20,45 @@ "description": "Limiting request concurrency (or concurrent connections)" }, "key": { - "description": "The user key corresponding to the limiter object. This must be unique in the scope.", - "type": "string" + "description": "The key corresponding to the limiter object", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the key, must be unique in the scope" + }, + "scope": { + "type": "string", + "description": "Scope of the key", + "default": "global", + "oneOf": [{ + "enum": ["global"], + "description": "Global scope, affecting to all services" + }, { + "enum": ["service"], + "description": "Service scope, affecting to one service" + }] + }, + "service_name": { + "type": "string", + "description": "Name of service, necessary for service scope" + } + } }, "conn": { "type": "integer", - "description": "The maximum number of concurrent requests allowed" + "description": "The maximum number of concurrent requests allowed", + "exclusiveminimum": 0 }, "burst": { "type": "integer", - "description": "The number of excessive concurrent requests (or connections) allowed to be delayed" + "description": "The number of excessive concurrent requests (or connections) allowed to be delayed", + "minimum": 0 }, "delay": { "type": "number", - "description": "The default processing latency of a typical connection (or request)" + "description": "The default processing latency of a typical connection (or request)", + "exclusiveMinimum": 0 } } }, { @@ -45,16 +70,40 @@ "description": "Limiting request rate" }, "key": { - "description": "The user key corresponding to the limiter object. This must be unique in the scope.", - "type": "string" + "description": "The key corresponding to the limiter object", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the key, must be unique in the scope" + }, + "scope": { + "type": "string", + "description": "Scope of the key", + "default": "global", + "oneOf": [{ + "enum": ["global"], + "description": "Global scope, affecting to all services" + }, { + "enum": ["service"], + "description": "Service scope, affecting to one service" + }] + }, + "service_name": { + "type": "string", + "description": "Name of service, necessary for service scope" + } + } }, "rate": { "type": "integer", - "description": "The specified request rate (number per second) threshold" + "description": "The specified request rate (number per second) threshold", + "exclusiveMinimum": 0 }, "burst": { "type": "integer", - "description": "The number of excessive requests per second allowed to be delayed" + "description": "The number of excessive requests per second allowed to be delayed", + "minimum": 0 } } }, { @@ -66,16 +115,40 @@ "description": "Limiting request counts" }, "key": { - "description": "The user key corresponding to the limiter object. This must be unique in the scope.", - "type": "string" + "description": "The key corresponding to the limiter object", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the key, must be unique in the scope" + }, + "scope": { + "type": "string", + "description": "Scope of the key", + "default": "global", + "oneOf": [{ + "enum": ["global"], + "description": "Global scope, affecting to all services" + }, { + "enum": ["service"], + "description": "Service scope, affecting to one service" + }] + }, + "service_name": { + "type": "string", + "description": "Name of service, necessary for service scope" + } + } }, "count": { "type": "integer", - "description": "The specified number of requests threshold" + "description": "The specified number of requests threshold", + "exclusiveMinimum": 0 }, "window": { "type": "integer", - "description": "The time window in seconds before the request count is reset" + "description": "The time window in seconds before the request count is reset", + "exclusiveMinimum": 0 } } }] @@ -85,13 +158,64 @@ "description": "URL of Redis", "type": "string" }, - "status_code_rejected": { - "type": "integer", - "description": "The status code when requests over the limit, default 429" - }, - "logging_only": { - "type": "boolean", - "description": "If true, the request goes through when there is some issue with rate limiting, default false" + "error_settings": { + "description": "List of error settings", + "type": "array", + "items": { + "anyOf": [{ + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["limits_exceeded"], + "description": "The error setting when requests over the limit" + }, + "status_code": { + "type": "integer", + "description": "The status code when requests over the limit", + "default": 429 + }, + "error_handling": { + "type": "string", + "description": "How to handle an error", + "default": "exit", + "oneOf": [{ + "enum": ["exit"], + "description": "Respond with an error" + }, { + "enum": ["log"], + "description": "Let the request go through and only output logs" + }] + } + } + }, { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["configuration_issue"], + "description": "The error setting when there is some configuration issue" + }, + "status_code": { + "type": "integer", + "description": "The status code when there is some configuration issue", + "default": 500 + }, + "error_handling": { + "type": "string", + "description": "How to handle an error", + "default": "exit", + "oneOf": [{ + "enum": ["exit"], + "description": "Respond with an error" + }, { + "enum": ["log"], + "description": "Let the request go through and only output logs" + }] + } + } + }] + } } } } diff --git a/gateway/src/apicast/policy/rate_limit/rate_limit.lua b/gateway/src/apicast/policy/rate_limit/rate_limit.lua index 03d1d1656..e839f8944 100644 --- a/gateway/src/apicast/policy/rate_limit/rate_limit.lua +++ b/gateway/src/apicast/policy/rate_limit/rate_limit.lua @@ -26,6 +26,17 @@ local traffic_limiters = { end } +local default_error_settings = { + limits_exceeded = { + status_code = 429, + error_handling = "exit" + }, + configuration_issue = { + status_code = 500, + error_handling = "exit" + } +} + local function redis_shdict(url) local options = { url = url } local redis, err = ts.connect_redis(options) @@ -61,19 +72,35 @@ local function redis_shdict(url) } end -local function error(logging_only, status_code) - if not logging_only then - return ngx.exit(status_code) +local function error(error_settings, type) + if error_settings[type]["error_handling"] == "exit" then + return ngx.exit(error_settings[type]["status_code"]) end end +local function init_error_settings(config_error_settings) + local error_settings = default_error_settings + if config_error_settings then + for _, error_setting in pairs(config_error_settings) do + if error_setting.type then + if error_setting.status_code then + error_settings[error_setting.type]["status_code"] = error_setting.status_code + end + if error_setting.error_handling then + error_settings[error_setting.type]["error_handling"] = error_setting.error_handling + end + end + end + end + return error_settings +end + function _M.new(config) local self = new() self.config = config or {} self.limiters = config.limiters self.redis_url = config.redis_url - self.status_code_rejected = config.status_code_rejected or 429 - self.logging_only = config.logging_only or false + self.error_settings = init_error_settings(config.error_settings) return self end @@ -88,7 +115,7 @@ function _M:access() red, rederr = redis_shdict(self.redis_url) if not red then ngx.log(ngx.ERR, "failed to connect Redis: ", rederr) - error(self.logging_only, 500) + error(self.error_settings, "configuration_issue") return end end @@ -97,14 +124,22 @@ function _M:access() local lim, initerr = traffic_limiters[limiter.name](limiter) if not lim then ngx.log(ngx.ERR, "unknown limiter: ", limiter.name, ", err: ", initerr) - error(self.logging_only, 500) + error(self.error_settings, "configuration_issue") return end lim.dict = red or lim.dict table.insert(limiters, lim) - table.insert(keys, limiter.key) + + local key + if limiter.key.scope == "service" then + key = limiter.key.service_name.."_"..limiter.name.."_"..limiter.key.name + else + key = limiter.name.."_"..limiter.key.name + end + + table.insert(keys, key) end @@ -116,11 +151,11 @@ function _M:access() if not delay then if comerr == "rejected" then ngx.log(ngx.WARN, "Requests over the limit.") - error(self.logging_only, self.status_code_rejected) + error(self.error_settings, "limits_exceeded") return end ngx.log(ngx.ERR, "failed to limit traffic: ", comerr) - error(self.logging_only, 500) + error(self.error_settings, "configuration_issue") return end @@ -144,7 +179,7 @@ function _M:access() end -local function checkin(_, ctx, time, semaphore, redis_url, logging_only) +local function checkin(_, ctx, time, semaphore, redis_url, error_settings) local limiters = ctx.limiters local keys = ctx.keys local latency = tonumber(time) @@ -155,7 +190,7 @@ local function checkin(_, ctx, time, semaphore, redis_url, logging_only) red, rederr = redis_shdict(redis_url) if not red then ngx.log(ngx.ERR, "failed to connect Redis: ", rederr) - error(logging_only, 500) + error(error_settings, "configuration_issue") return end end @@ -166,7 +201,7 @@ local function checkin(_, ctx, time, semaphore, redis_url, logging_only) local conn, err = lim:leaving(keys[i], latency) if not conn then ngx.log(ngx.ERR, "failed to record the connection leaving request: ", err) - error(logging_only, 500) + error(error_settings, "configuration_issue") return end end @@ -182,7 +217,7 @@ function _M:log() local limiters = ctx.limiters if limiters and next(limiters) ~= nil then local semaphore = ngx_semaphore.new() - ngx.timer.at(0, checkin, ngx.ctx, ngx.var.request_time, semaphore, self.redis_url, self.logging_only) + ngx.timer.at(0, checkin, ngx.ctx, ngx.var.request_time, semaphore, self.redis_url, self.error_settings) semaphore:wait(10) end end diff --git a/spec/policy/rate_limit/rate_limit_spec.lua b/spec/policy/rate_limit/rate_limit_spec.lua index b303d69c0..8a34457b5 100644 --- a/spec/policy/rate_limit/rate_limit_spec.lua +++ b/spec/policy/rate_limit/rate_limit_spec.lua @@ -50,7 +50,7 @@ describe('Rate limit policy', function() local redis = require('resty.redis'):new() redis:connect(redis_host, redis_port) redis:select(1) - redis:del('test1', 'test2', 'test3') + redis:del('connections_test1', 'leaky_bucket_test2', 'fixed_window_test3', 'bank_A_leaky_bucket_test4') init_val() end) @@ -58,9 +58,9 @@ describe('Rate limit policy', function() it('success with multiple limiters', function() local config = { limiters = { - {name = "connections", key = 'test1', conn = 20, burst = 10, delay = 0.5}, - {name = "leaky_bucket", key = 'test2', rate = 18, burst = 9}, - {name = "fixed_window", key = 'test3', count = 10, window = 10} + {name = "connections", key = {name = 'test1'}, conn = 20, burst = 10, delay = 0.5}, + {name = "leaky_bucket", key = {name = 'test2'}, rate = 18, burst = 9}, + {name = "fixed_window", key = {name = 'test3'}, count = 10, window = 10} }, redis_url = 'redis://'..redis_host..':'..redis_port..'/1' } @@ -71,9 +71,9 @@ describe('Rate limit policy', function() it('no redis url', function() local config = { limiters = { - {name = "connections", key = 'test1', conn = 20, burst = 10, delay = 0.5}, - {name = "leaky_bucket", key = 'test2', rate = 18, burst = 9}, - {name = "fixed_window", key = 'test3', count = 10, window = 10} + {name = "connections", key = {name = 'test1'}, conn = 20, burst = 10, delay = 0.5}, + {name = "leaky_bucket", key = {name = 'test2'}, rate = 18, burst = 9}, + {name = "fixed_window", key = {name = 'test3'}, count = 10, window = 10} } } local rate_limit_policy = RateLimitPolicy.new(config) @@ -83,7 +83,7 @@ describe('Rate limit policy', function() it('invalid redis url', function() local config = { limiters = { - {name = "connections", key = 'test1', conn = 20, burst = 10, delay = 0.5} + {name = "connections", key = {name = 'test1'}, conn = 20, burst = 10, delay = 0.5} }, redis_url = 'redis://invalidhost:'..redis_port..'/1' } @@ -95,7 +95,7 @@ describe('Rate limit policy', function() it('rejected (conn)', function() local config = { limiters = { - {name = "connections", key = 'test1', conn = 1, burst = 0, delay = 0.5} + {name = "connections", key = {name = 'test1'}, conn = 1, burst = 0, delay = 0.5} }, redis_url = 'redis://'..redis_host..':'..redis_port..'/1' } @@ -108,7 +108,7 @@ describe('Rate limit policy', function() it('rejected (req)', function() local config = { limiters = { - {name = "leaky_bucket", key = 'test1', rate = 1, burst = 0} + {name = "leaky_bucket", key = {name = 'test2'}, rate = 1, burst = 0} }, redis_url = 'redis://'..redis_host..':'..redis_port..'/1' } @@ -121,7 +121,7 @@ describe('Rate limit policy', function() it('rejected (count)', function() local config = { limiters = { - {name = "fixed_window", key = 'test1', count = 1, window = 10} + {name = "fixed_window", key = {name = 'test3'}, count = 1, window = 10} }, redis_url = 'redis://'..redis_host..':'..redis_port..'/1' } @@ -134,7 +134,7 @@ describe('Rate limit policy', function() it('delay (conn)', function() local config = { limiters = { - {name = "connections", key = 'test1', conn = 1, burst = 1, delay = 2} + {name = "connections", key = {name = 'test1'}, conn = 1, burst = 1, delay = 2} }, redis_url = 'redis://'..redis_host..':'..redis_port..'/1' } @@ -147,7 +147,25 @@ describe('Rate limit policy', function() it('delay (req)', function() local config = { limiters = { - {name = "leaky_bucket", key = 'test1', rate = 1, burst = 1} + {name = "leaky_bucket", key = {name = 'test2', scope = 'global'}, rate = 1, burst = 1} + }, + redis_url = 'redis://'..redis_host..':'..redis_port..'/1' + } + local rate_limit_policy = RateLimitPolicy.new(config) + rate_limit_policy:access() + rate_limit_policy:access() + assert.spy(ngx_sleep_spy).was_called_with(match.is_gt(0.001)) + end) + + it('delay (req) service scope', function() + local config = { + limiters = { + { + name = "leaky_bucket", + key = {name = 'test4', scope = 'service', service_name = 'bank_A'}, + rate = 1, + burst = 1 + } }, redis_url = 'redis://'..redis_host..':'..redis_port..'/1' } @@ -162,7 +180,7 @@ describe('Rate limit policy', function() it('success in leaving', function() local config = { limiters = { - {name = "connections", key = 'test1', conn = 20, burst = 10, delay = 0.5} + {name = "connections", key = {name = 'test1'}, conn = 20, burst = 10, delay = 0.5} }, redis_url = 'redis://'..redis_host..':'..redis_port..'/1' } diff --git a/t/apicast-policy-rate-limit.t b/t/apicast-policy-rate-limit.t index 12a201f2e..6934e2192 100644 --- a/t/apicast-policy-rate-limit.t +++ b/t/apicast-policy-rate-limit.t @@ -10,8 +10,8 @@ run_tests(); __DATA__ -=== TEST 2: Invalid limiter value. -Return 500 code. +=== TEST 1: Delay (conn) service scope. +Return 200 code. --- http_config include $TEST_NGINX_UPSTREAM_CONFIG; lua_package_path "$TEST_NGINX_LUA_PATH"; @@ -30,10 +30,26 @@ Return 500 code. configuration = { limiters = { { - name = "fixed_window", - key = "test2", - count = 0, - window = 10 + name = "connections", + key = {name = "test1", scope = "service", service_name = "service_C"}, + conn = 1, + burst = 1, + delay = 2 + } + }, + redis_url = "redis://$TEST_NGINX_REDIS_HOST:$TEST_NGINX_REDIS_PORT/1" + } + }, + { + name = "apicast.policy.rate_limit", + configuration = { + limiters = { + { + name = "connections", + key = {name = "test1", scope = "service", service_name = "service_C"}, + conn = 1, + burst = 1, + delay = 2 } }, redis_url = "redis://$TEST_NGINX_REDIS_HOST:$TEST_NGINX_REDIS_PORT/1" @@ -49,12 +65,28 @@ Return 500 code. --- config include $TEST_NGINX_APICAST_CONFIG; + resolver $TEST_NGINX_RESOLVER; ---- request -GET / ---- error_code: 500 ---- error_log -assertion failed! + location /transactions/authrep.xml { + content_by_lua_block { ngx.exit(200) } + } + + location /flush_redis { + content_by_lua_block { + local env = require('resty.env') + local redis_host = "$TEST_NGINX_REDIS_HOST" or '127.0.0.1' + local redis_port = "$TEST_NGINX_REDIS_PORT" or 6379 + local redis = require('resty.redis'):new() + redis:connect(redis_host, redis_port) + redis:select(1) + redis:del("service_C_connections_test1") + } + } + +--- pipelined_requests eval +["GET /flush_redis","GET /"] +--- error_code eval +[200, 200] === TEST 3: Invalid redis url. Return 500 code. @@ -77,7 +109,7 @@ Return 500 code. limiters = { { name = "connections", - key = "test3", + key = {name = "test3"}, conn = 20, burst = 10, delay = 0.5 @@ -122,14 +154,16 @@ Return 200 code. limiters = { { name = "connections", - key = "test4", + key = {name = "test4"}, conn = 1, burst = 0, delay = 2 } }, redis_url = "redis://$TEST_NGINX_REDIS_HOST:$TEST_NGINX_REDIS_PORT/1", - logging_only = true + error_settings = { + {type = "limits_exceeded", error_handling = "log"} + } } }, { @@ -138,14 +172,16 @@ Return 200 code. limiters = { { name = "connections", - key = "test4", + key = {name = "test4"}, conn = 1, burst = 0, delay = 2 } }, redis_url = "redis://$TEST_NGINX_REDIS_HOST:$TEST_NGINX_REDIS_PORT/1", - logging_only = true + error_settings = { + {type = "limits_exceeded", error_handling = "log"} + } } } } @@ -168,7 +204,7 @@ Return 200 code. local redis = require('resty.redis'):new() redis:connect(redis_host, redis_port) redis:select(1) - redis:del("test4") + redis:del("connections_test4") } } @@ -198,7 +234,7 @@ Return 200 code. limiters = { { name = "connections", - key = "test5", + key = {name = "test5"}, conn = 20, burst = 10, delay = 0.5 @@ -244,20 +280,20 @@ Return 200 code. limiters = { { name = "leaky_bucket", - key = "test6_1", + key = {name = "test6_1"}, rate = 20, burst = 10 }, { name = "connections", - key = "test6_2", + key = {name = "test6_2"}, conn = 20, burst = 10, delay = 0.5 }, { name = "fixed_window", - key = "test6_3", + key = {name = "test6_3"}, count = 20, window = 10 } @@ -285,7 +321,7 @@ Return 200 code. local redis = require('resty.redis'):new() redis:connect(redis_host, redis_port) redis:select(1) - redis:del("test6_1", "test6_2", "test6_3") + redis:del("leaky_bucket_test6_1", "connections_test6_2", "fixed_window_test6_3") } } @@ -318,7 +354,7 @@ Return 429 code. limiters = { { name = "connections", - key = "test7", + key = {name = "test7"}, conn = 1, burst = 0, delay = 2 @@ -333,7 +369,7 @@ Return 429 code. limiters = { { name = "connections", - key = "test7", + key = {name = "test7"}, conn = 1, burst = 0, delay = 2 @@ -362,7 +398,7 @@ Return 429 code. local redis = require('resty.redis'):new() redis:connect(redis_host, redis_port) redis:select(1) - redis:del("test7") + redis:del("connections_test7") } } @@ -394,13 +430,15 @@ Return 503 code. limiters = { { name = "leaky_bucket", - key = "test8", + key = {name = "test8"}, rate = 1, burst = 0 } }, redis_url = "redis://$TEST_NGINX_REDIS_HOST:$TEST_NGINX_REDIS_PORT/1", - status_code_rejected = 503 + error_settings = { + {type = "limits_exceeded", status_code = 503} + } } } } @@ -423,7 +461,7 @@ Return 503 code. local redis = require('resty.redis'):new() redis:connect(redis_host, redis_port) redis:select(1) - redis:del("test8") + redis:del("leaky_bucket_test8") } } @@ -455,12 +493,15 @@ Return 429 code. limiters = { { name = "fixed_window", - key = "test9", + key = {name = "test9", scope = "global"}, count = 1, window = 10 } }, - redis_url = "redis://$TEST_NGINX_REDIS_HOST:$TEST_NGINX_REDIS_PORT/1" + redis_url = "redis://$TEST_NGINX_REDIS_HOST:$TEST_NGINX_REDIS_PORT/1", + error_settings = { + {type = "limits_exceeded", status_code = 429} + } } } } @@ -483,7 +524,7 @@ Return 429 code. local redis = require('resty.redis'):new() redis:connect(redis_host, redis_port) redis:select(1) - redis:del("test9") + redis:del("fixed_window_test9") } } @@ -515,7 +556,7 @@ Return 200 code. limiters = { { name = "connections", - key = "test10", + key = {name = "test10"}, conn = 1, burst = 1, delay = 2 @@ -530,7 +571,7 @@ Return 200 code. limiters = { { name = "connections", - key = "test10", + key = {name = "test10"}, conn = 1, burst = 1, delay = 2 @@ -563,7 +604,7 @@ Return 200 code. local redis = require('resty.redis'):new() redis:connect(redis_host, redis_port) redis:select(1) - redis:del("test10") + redis:del("connections_test10") } } @@ -593,7 +634,7 @@ Return 200 code. limiters = { { name = "leaky_bucket", - key = "test11", + key = {name = "test11"}, rate = 1, burst = 1 } @@ -625,7 +666,7 @@ Return 200 code. local redis = require('resty.redis'):new() redis:connect(redis_host, redis_port) redis:select(1) - redis:del("test11") + redis:del("leaky_bucket_test11") } } @@ -655,7 +696,7 @@ Return 429 code. limiters = { { name = "connections", - key = "test12", + key = {name = "test12"}, conn = 1, burst = 0, delay = 2 @@ -669,7 +710,7 @@ Return 429 code. limiters = { { name = "connections", - key = "test12", + key = {name = "test12"}, conn = 1, burst = 0, delay = 2 @@ -715,10 +756,13 @@ Return 429 code. limiters = { { name = "leaky_bucket", - key = "test13", + key = {name = "test13"}, rate = 1, burst = 0 } + }, + error_settings = { + {type = "limits_exceeded", error_handling = "exit"} } } } @@ -759,7 +803,7 @@ Return 429 code. limiters = { { name = "fixed_window", - key = "test14", + key = {name = "test14"}, count = 1, window = 10 } @@ -803,7 +847,7 @@ Return 200 code. limiters = { { name = "connections", - key = "test15", + key = {name = "test15"}, conn = 1, burst = 1, delay = 2 @@ -817,7 +861,7 @@ Return 200 code. limiters = { { name = "connections", - key = "test15", + key = {name = "test15"}, conn = 1, burst = 1, delay = 2 @@ -867,7 +911,7 @@ Return 200 code. limiters = { { name = "leaky_bucket", - key = "test16", + key = {name = "test16"}, rate = 1, burst = 1 }