diff --git a/.pongo/redis-cluster.yml b/.pongo/redis-cluster.yml index 5d9e482..1ec6209 100644 --- a/.pongo/redis-cluster.yml +++ b/.pongo/redis-cluster.yml @@ -9,6 +9,7 @@ networks: services: redis-cluster: image: redis:latest + hostname: pongo-test-network command: redis-cli -p 7101 --cluster create 172.18.55.1:7101 172.18.55.2:7102 172.18.55.3:7103 --cluster-yes depends_on: - redis-1 diff --git a/kong-scalable-rate-limiter-1.1.0-1.rockspec b/kong-scalable-rate-limiter-1.1.0-1.rockspec index 558ebf1..88a7ca2 100644 --- a/kong-scalable-rate-limiter-1.1.0-1.rockspec +++ b/kong-scalable-rate-limiter-1.1.0-1.rockspec @@ -14,7 +14,7 @@ description = { } dependencies = { - + "lua-resty-ipmatcher == 0.6.1", } build = { diff --git a/kong/plugins/scalable-rate-limiter/policies/connection.lua b/kong/plugins/scalable-rate-limiter/policies/connection.lua index f7cad8b..145f1f9 100644 --- a/kong/plugins/scalable-rate-limiter/policies/connection.lua +++ b/kong/plugins/scalable-rate-limiter/policies/connection.lua @@ -3,6 +3,8 @@ local redis_cluster = require "resty.rediscluster" local resty_lock = require "resty.lock" local worker = ngx.worker local inspect = require "inspect" +local dns_client = require "kong.resty.dns.client" +local ipmatcher = require "resty.ipmatcher" local _M = {} local data = { @@ -10,6 +12,12 @@ local data = { } local function get_redis_config(source_config) + -- If not ipv4 and not ipv6 adress then we need to resolve hostname to ip + if not ipmatcher.parse_ipv4(source_config.redis_host) and not ipmatcher.parse_ipv6(source_config.redis_host) then + dns_client = require("kong.tools.dns")(kong.configuration) -- configure DNS client + source_config.redis_host = dns_client.toip(source_config.redis_host) + end + return { name = "d11-redis-cluster", serv_list = { diff --git a/spec/scalable-rate-limiter/01-access_spec.lua b/spec/scalable-rate-limiter/01-access_spec.lua index eba1b08..5a15b4b 100644 --- a/spec/scalable-rate-limiter/01-access_spec.lua +++ b/spec/scalable-rate-limiter/01-access_spec.lua @@ -3,6 +3,7 @@ local cjson = require "cjson" -- REDIS_HOST and REDIS_PORT taken from .pongo/redis-cluster.yml local REDIS_HOST = "172.18.55.1" +local REDIS_HOSTNAME = "pongo-test-network" local REDIS_PORT = 7101 local REDIS_PASSWORD = "" local REDIS_DATABASE = 1 @@ -75,6 +76,15 @@ for _, strategy in helpers.each_strategy() do limit_by = "service", batch_size = 2, } + + local limit_by_service_config_hostname = { + policy = policy, + minute = per_minute_limit, + redis_host = REDIS_HOSTNAME, + redis_port = REDIS_PORT, + limit_by = "service", + batch_size = 2, + } local limit_by_header_config = { policy = policy, @@ -206,6 +216,127 @@ for _, strategy in helpers.each_strategy() do end end) end) + + describe("global level plugin limit by service using redis hostname", function() + lazy_setup(function() + helpers.kill_all() + + bp, db = helpers.get_db_utils(strategy, nil, {"scalable-rate-limiter"}) + + -- Add request termination plugin at global level to return 200 response for all api calls + bp.plugins:insert { + name = "request-termination", + config = { + status_code = 200, + }, + } + + -- Global level Rate limiting plugin limit by service + bp.plugins:insert { + name = "scalable-rate-limiter", + config = limit_by_service_config_hostname, + } + + local service_1 = bp.services:insert { + protocol = "http", + host = mock_host, + port = mock_port, + name = "service_1", + } + + bp.routes:insert { + methods = {"GET"}, + protocols = {"http"}, + paths = {"/service_1_route_1"}, + strip_path = false, + preserve_host = true, + service = service_1, + } + + local service_2 = bp.services:insert { + protocol = "http", + host = mock_host, + port = mock_port, + name = "service_2", + } + + bp.routes:insert { + methods = {"GET"}, + protocols = {"http"}, + paths = {"/service_2_route_1"}, + strip_path = false, + preserve_host = true, + service = service_2, + } + + local service_3 = bp.services:insert { + protocol = "http", + host = mock_host, + port = mock_port, + name = "service_3", + } + + bp.routes:insert { + methods = {"GET"}, + protocols = {"http"}, + paths = {"/service_3_route_1"}, + strip_path = false, + preserve_host = true, + service = service_3, + } + + bp.routes:insert { + methods = {"GET"}, + protocols = {"http"}, + paths = {"/service_3_route_2"}, + strip_path = false, + preserve_host = true, + service = service_3, + } + + assert(helpers.start_kong({ + database = strategy, + nginx_conf = "spec/fixtures/custom_nginx.template", + plugins = "bundled,scalable-rate-limiter", + })) + end) + + lazy_teardown(function() + helpers.stop_kong() + assert(db:truncate()) + end) + + it_with_retry("blocks requests over limit on same service", function() + for i = 1, per_minute_limit do + local res = GET("/service_1_route_1", nil, 200) + end + + local res = GET("/service_1_route_1", nil, 429) + end) + + it_with_retry("blocks requests over limit on same service with multiple routes", function() + for i = 1, per_minute_limit/2 do + local res = GET("/service_3_route_1", nil, 200) + end + + for i = 1, per_minute_limit/2 do + local res = GET("/service_3_route_2", nil, 200) + end + + local res = GET("/service_3_route_1", nil, 429) + local res = GET("/service_3_route_2", nil, 429) + end) + + it_with_retry("allows requests upto limit on each different service", function() + for i = 1, per_minute_limit do + local res = GET("/service_1_route_1", nil, 200) + end + + for i = 1, per_minute_limit do + local res = GET("/service_2_route_1", nil, 200) + end + end) + end) describe("global level plugin limit by header", function() lazy_setup(function()