diff --git a/kong/api/routes/upstreams.lua b/kong/api/routes/upstreams.lua index ed3871416ea2..5b97bb612b97 100644 --- a/kong/api/routes/upstreams.lua +++ b/kong/api/routes/upstreams.lua @@ -44,25 +44,30 @@ return { end, POST = function(self, dao_factory, helpers) - local cleanup_factor = 10 -- when to cleanup; invalid-entries > (valid-ones * cleanup_factor) + -- when to cleanup: invalid-entries > (valid-ones * cleanup_factor) + local cleanup_factor = 10 --cleaning up history, check if it's necessary... local target_history = dao_factory.targets:find_all( { upstream_id = self.params.upstream_id }) + if target_history then --ignoring errors here, will be caught when posting below -- sort the targets for _,target in ipairs(target_history) do target.order = target.created_at..":"..target.id end + -- sort table in reverse order table.sort(target_history, function(a,b) return a.order>b.order end) -- do clean up local cleaned = {} local delete = {} + for _, entry in ipairs(target_history) do if cleaned[entry.target] then -- we got a newer entry for this target than this, so this one can go delete[#delete+1] = entry + else -- haven't got this one, so this is the last one for this target cleaned[entry.target] = true @@ -77,7 +82,9 @@ return { -- either nothing left, or when 10x more outdated than active entries if (#cleaned == 0 and #delete > 0) or (#delete >= (math.max(#cleaned,1)*cleanup_factor)) then - ngx.log(ngx.WARN, "Starting cleanup of target table for upstream "..tostring(self.params.upstream_id)) + + ngx.log(ngx.INFO, "[admin api] Starting cleanup of target table for upstream ", + tostring(self.params.upstream_id)) local cnt = 0 for _, entry in ipairs(delete) do -- not sending update events, one event at the end, based on the @@ -90,11 +97,13 @@ return { -- in case another kong-node does the same cleanup simultaneously cnt = cnt + 1 end - ngx.log(ngx.WARN, "Finished cleanup of target table for upstream ".. - tostring(self.params.upstream_id).." removed "..tostring(cnt).." target entries") + + ngx.log(ngx.INFO, "[admin api] Finished cleanup of target table", + " for upstream ", tostring(self.params.upstream_id), + " removed ", tostring(cnt), " target entries") end end - + crud.post(self.params, dao_factory.targets) end, }, diff --git a/kong/core/balancer.lua b/kong/core/balancer.lua index 1ba41f51f95a..40ba2b32fb74 100644 --- a/kong/core/balancer.lua +++ b/kong/core/balancer.lua @@ -6,11 +6,11 @@ local dns_client = require "resty.dns.client" -- due to startup/require order, local ring_balancer = require "resty.dns.balancer" local toip = dns_client.toip -local ngx_log = ngx.log -local ngx_ERR = ngx.ERR -local ngx_DEBUG = ngx.DEBUG +local log = ngx.log -local empty = pl_tablex.readonly {} +local ERROR = ngx.ERR +local DEBUG = ngx.DEBUG +local EMPTY_T = pl_tablex.readonly {} --=========================================================== -- Ring-balancer based resolution @@ -18,10 +18,10 @@ local empty = pl_tablex.readonly {} local balancers = {} -- table holding our balancer objects, indexed by upstream name -- caching logic; --- we retain 3 entities; --- 1) list of upstreams, to be invalidated on any upstream change --- 2) individual upstreams, to be invalidated on individual basis --- 3) target history for an upstream, invalidated when; +-- we retain 3 entities: +-- 1) list of upstreams: to be invalidated on any upstream change +-- 2) individual upstreams: to be invalidated on individual basis +-- 3) target history for an upstream, invalidated when: -- a) along with the upstream it belongs to -- b) upon any target change for the upstream (can only add entries) -- Distinction between 1 and 2 makes it possible to invalidate individual @@ -30,7 +30,7 @@ local balancers = {} -- table holding our balancer objects, indexed by upstream -- Implements a simple dictionary with all upstream-ids indexed -- by their name. local function load_upstreams_dict_into_memory() - ngx_log(ngx_DEBUG, "fetching all upstreams") + log(DEBUG, "fetching all upstreams") local upstreams, err = singletons.dao.upstreams:find_all() if err then return nil, err @@ -55,11 +55,10 @@ end -- loads a single upstream entity local function load_upstream_into_memory(upstream_id) - ngx_log(ngx_DEBUG, "fetching upstream: ",tostring(name)," ",tostring(upstream_id)) + log(DEBUG, "fetching upstream: ", tostring(upstream_id)) + local upstream, err = singletons.dao.upstreams:find_all {id = upstream_id} - if not upstream then - return nil, err - end + if not upstream then return nil, err end upstream = upstream[1] -- searched by id, so only 1 row in the returned set @@ -87,7 +86,7 @@ end -- @return upstream table, or `false` if not found, or nil+error local function get_upstream(upstream_name) local upstreams_dict, err = cache.get_or_set(cache.upstreams_dict_key(), - nil, load_upstreams_dict_into_memory) + nil, load_upstreams_dict_into_memory) if err then return nil, err end @@ -95,33 +94,33 @@ local function get_upstream(upstream_name) local upstream_id = upstreams_dict[upstream_name] if not upstream_id then return false end -- no upstream by this name - return cache.get_or_set(cache.upstream_key(upstream_id), nil, load_upstream_into_memory, upstream_id) + return cache.get_or_set(cache.upstream_key(upstream_id), nil, + load_upstream_into_memory, upstream_id) end -- loads the target history for an upstream -- @param upstream_id Upstream uuid for which to load the target history local function load_targets_into_memory(upstream_id) - ngx_log(ngx_DEBUG, "fetching targets for upstream: ",tostring(upstream_id)) + log(DEBUG, "fetching targets for upstream: ",tostring(upstream_id)) + local target_history, err = singletons.dao.targets:find_all {upstream_id = upstream_id} - if err then - return nil, err - end + if err then return nil, err end - -- some raw data updates + -- perform some raw data updates for _, target in ipairs(target_history) do -- split `target` field into `name` and `port` local port target.name, port = string.match(target.target, "^(.-):(%d+)$") target.port = tonumber(port) - -- need exact order, so order by created time and uuid + + -- need exact order, so create sort-key by created-time and uuid target.order = target.created_at..":"..target.id end - - -- order by time - table.sort(target_history, function(a,b) return a.order 0 then assert(rb:addHost(target.name, target.port, target.weight)) else assert(rb:removeHost(target.name, target.port)) end + rb.__targets_history[i] = { name = target.name, port = target.port, @@ -146,7 +147,7 @@ local function apply_history(rb, history, start) order = target.order, } end - + return true end @@ -160,15 +161,16 @@ local get_balancer = function(target) -- first go and find the upstream object, from cache or the db local upstream, err = get_upstream(hostname) if err then - return nil, err -- there was an error - elseif upstream == false then - return false -- no upstream by this name + return nil, err -- there was an error + end + + if upstream == false then + return false -- no upstream by this name end -- we've got the upstream, now fetch its targets, from cache or the db local targets_history, err = cache.get_or_set(cache.targets_key(upstream.id), nil, load_targets_into_memory, upstream.id) - if err then return nil, err end @@ -176,16 +178,20 @@ local get_balancer = function(target) local balancer = balancers[upstream.name] -- always exists, created upon fetching upstream -- check history state + -- NOTE: in the code below variables are similarly named, but the + -- ones with `__`-prefixed, are the ones on the `balancer` object, and the + -- regular ones are the ones we just fetched and are comparing against local __size = #balancer.__targets_history local size = #targets_history + if __size ~= size or balancer.__targets_history[__size].order ~= targets_history[size].order then -- last entries in history don't match, so we must do some updates. - + -- compare balancer history with db-loaded history local last_equal_index = 0 -- last index where history is the same for i, entry in ipairs(balancer.__targets_history) do - if entry.order ~= (targets_history[i] or empty).order then + if entry.order ~= (targets_history[i] or EMPTY_T).order then last_equal_index = i - 1 break end @@ -194,6 +200,7 @@ local get_balancer = function(target) if last_equal_index == __size then -- history is the same, so we only need to add new entries apply_history(balancer, targets_history, last_equal_index + 1) + else -- history not the same. -- TODO: ideally we would undo the last ones until we're equal again @@ -205,9 +212,9 @@ local get_balancer = function(target) dns = dns_client, }) if not balancer then return balancer, err end + balancer.__targets_history = {} balancers[upstream.name] = balancer -- overwrite our existing one - apply_history(balancer, targets_history, 1) end end @@ -229,21 +236,23 @@ end -- @return true on success, nil+error otherwise local function execute(target) local upstream = target.upstream - + if target.type ~= "name" then -- it's an ip address (v4 or v6), so nothing we can do... target.ip = upstream.host - target.port = upstream.port or 80 + target.port = upstream.port or 80 -- TODO: remove this fallback value return true end - + -- when tries == 0 it runs before the `balancer` context (in the `access` context), -- when tries >= 2 then it performs a retry in the `balancer` context local dns_cache_only = target.tries ~= 0 local balancer + if dns_cache_only then -- retry, so balancer is already set if there was one balancer = target.balancer + else local err -- first try, so try and find a matching balancer/upstream object @@ -264,30 +273,36 @@ local function execute(target) if not ip then if port == "No peers are available" then -- in this case a "503 service unavailable", others will be a 500. - ngx_log(ngx_ERR, "Failure to get a peer from the ring-balancer '",upstream.host,"'; ",port) + log(ERROR, "failure to get a peer from the ring-balancer '", + upstream.host, "'; ", port) return responses.send(503) end + return nil, port -- some other error end + target.ip = ip target.port = port target.hostname = hostname return true - else - -- have to do a regular DNS lookup - local ip, port = toip(upstream.host, upstream.port, dns_cache_only) - if not ip then - return nil, port - end - target.ip = ip - target.port = port - return true end + + -- have to do a regular DNS lookup + local ip, port = toip(upstream.host, upstream.port, dns_cache_only) + if not ip then + return nil, port + end + + target.ip = ip + target.port = port + return true end return { execute = execute, - _load_upstreams_dict_into_memory = load_upstreams_dict_into_memory, -- exported for test purposes - _load_upstream_into_memory = load_upstream_into_memory, -- exported for test purposes - _load_targets_into_memory = load_targets_into_memory, -- exported for test purposes + + -- ones below are exported for test purposes + _load_upstreams_dict_into_memory = load_upstreams_dict_into_memory, + _load_upstream_into_memory = load_upstream_into_memory, + _load_targets_into_memory = load_targets_into_memory, } \ No newline at end of file diff --git a/kong/core/handler.lua b/kong/core/handler.lua index 436dcbcca4da..2b94429cc043 100644 --- a/kong/core/handler.lua +++ b/kong/core/handler.lua @@ -56,17 +56,20 @@ return { -- hostname = nil, -- the hostname that belongs to the ip address returned by the balancer } ngx.ctx.balancer_address = balancer_address + local ok, err = balancer_execute(balancer_address) if not ok then return responses.send_HTTP_INTERNAL_SERVER_ERROR("failed the initial ".. "dns/balancer resolve for '"..balancer_address.upstream.host.. - "' with; "..tostring(err)) + "' with: "..tostring(err)) end + if balancer_address.hostname and not ngx.ctx.api.preserve_host then ngx.var.upstream_host = balancer_address.hostname else ngx.var.upstream_host = upstream_host end + end, -- Only executed if the `resolver` module found an API and allows nginx to proxy it. after = function() diff --git a/kong/dao/migrations/cassandra.lua b/kong/dao/migrations/cassandra.lua index e3c905a7870e..a06cf9df3a25 100644 --- a/kong/dao/migrations/cassandra.lua +++ b/kong/dao/migrations/cassandra.lua @@ -181,6 +181,13 @@ return { }, { name = "2016-09-16-141423_upstreams", + -- Note on the timestamps; + -- The Cassandra timestamps are created in Lua code, and hence ALL entities + -- will now be created in millisecond precision. The existing entries will + -- remain in second precision, but new ones (for ALL entities!) will be + -- in millisecond precision. + -- This differs from the Postgres one where only the new entities (upstreams + -- and targets) will get millisecond precision. up = [[ CREATE TABLE IF NOT EXISTS upstreams( id uuid, diff --git a/kong/dao/migrations/postgres.lua b/kong/dao/migrations/postgres.lua index ef505136f2a1..8a18923cfe29 100644 --- a/kong/dao/migrations/postgres.lua +++ b/kong/dao/migrations/postgres.lua @@ -159,9 +159,8 @@ return { { name = "2016-09-16-141423_upstreams", -- Note on the timestamps below; these use a precision of milliseconds - -- this differs from the other tables above, as they only use second precision - -- The Cassandra timestamps are created in Lua code, and have hence ALL at - -- the same time been updated to millisecond precision. + -- this differs from the other tables above, as they only use second precision. + -- This differs from the change to the Cassandra entities. up = [[ CREATE TABLE IF NOT EXISTS upstreams( id uuid PRIMARY KEY, diff --git a/kong/dao/schemas/targets.lua b/kong/dao/schemas/targets.lua index 30b0bf579772..1550030e2fc6 100644 --- a/kong/dao/schemas/targets.lua +++ b/kong/dao/schemas/targets.lua @@ -5,10 +5,10 @@ local Errors = require "kong.dao.errors" local utils = require "kong.tools.utils" -local default_port = 8000 -local default_weight = 100 -local weight_min, weight_max = 0, 1000 -local weight_msg = "weight must be from "..weight_min.." to "..weight_max +local DEFAULT_PORT = 8000 +local DEFAULT_WEIGHT = 100 +local WEIGHT_MIN, WEIGHT_MAX = 0, 1000 +local WEIGHT_MSG = "weight must be from "..WEIGHT_MIN.." to "..WEIGHT_MAX return { table = "targets", @@ -38,14 +38,14 @@ return { -- weight in the loadbalancer algorithm. -- to disable an entry, set the weight to 0 type = "number", - default = default_weight, + default = DEFAULT_WEIGHT, }, }, self_check = function(schema, config, dao, is_updating) -- check weight - if config.weight < weight_min or config.weight > weight_max then - return false, Errors.schema(weight_msg) + if config.weight < WEIGHT_MIN or config.weight > WEIGHT_MAX then + return false, Errors.schema(WEIGHT_MSG) end -- check the target @@ -53,7 +53,7 @@ return { if not p then return false, Errors.schema("Invalid target; not a valid hostname or ip address") end - config.target = utils.format_host(p, default_port) + config.target = utils.format_host(p, DEFAULT_PORT) return true end, diff --git a/kong/dao/schemas/upstreams.lua b/kong/dao/schemas/upstreams.lua index e39c8c7e2be9..2038a0526d69 100644 --- a/kong/dao/schemas/upstreams.lua +++ b/kong/dao/schemas/upstreams.lua @@ -1,9 +1,9 @@ local Errors = require "kong.dao.errors" local utils = require "kong.tools.utils" -local default_slots = 100 -local slots_min, slots_max = 10, 2^16 -local slots_msg = "number of slots must be between "..slots_min.." and "..slots_max +local DEFAULT_SLOTS = 100 +local SLOTS_MIN, SLOTS_MAX = 10, 2^16 +local SLOTS_MSG = "number of slots must be between "..SLOTS_MIN.." and "..SLOTS_MAX return { table = "upstreams", @@ -29,12 +29,12 @@ return { slots = { -- the number of slots in the loadbalancer algorithm type = "number", - default = default_slots, + default = DEFAULT_SLOTS, }, orderlist = { -- a list of sequential, but randomly ordered, integer numbers. In the datastore -- because all Kong nodes need the exact-same 'randomness'. If changed, consistency is lost. - -- must have exactly `slots` number of entries, so regenerated whenever `slots` is changed. + -- must have exactly `slots` number of entries. type = "array", default = {}, } @@ -54,14 +54,15 @@ return { end -- check the slots number - if config.slots < slots_min or config.slots > slots_max then - return false, Errors.schema(slots_msg) + if config.slots < SLOTS_MIN or config.slots > SLOTS_MAX then + return false, Errors.schema(SLOTS_MSG) end -- check the order array local order = config.orderlist if #order == config.slots then -- array size unchanged, check consistency + local t = utils.shallow_copy(order) table.sort(t) local count, max = 0, 0 @@ -69,18 +70,23 @@ return { if (i ~= v) then return false, Errors.schema("invalid orderlist") end + count = count + 1 if i > max then max = i end end + if (count ~= config.slots) or (max ~= config.slots) then return false, Errors.schema("invalid orderlist") end + else -- size mismatch if #order > 0 then + -- size given, but doesn't match the size of the also given orderlist return false, Errors.schema("size mismatch between 'slots' and 'orderlist'") end - -- No list given, regenerate order array + + -- No list given, generate order array local t = {} for i = 1, config.slots do t[i] = { @@ -88,15 +94,21 @@ return { order = math.random(1, config.slots), } end + + -- sort the array (we don't check for -accidental- duplicates as the + -- id field is used for the order and that one is always unique) table.sort(t, function(a,b) return a.order < b.order end) + + -- replace the created 'record' with only the id for i, v in ipairs(t) do t[i] = v.id end config.orderlist = t end + return true end, marshall_event = function(self, t) diff --git a/kong/kong.lua b/kong/kong.lua index ba8f6c5bd25a..ce5d20179410 100644 --- a/kong/kong.lua +++ b/kong/kong.lua @@ -209,7 +209,7 @@ function Kong.balancer() if not ok then return responses.send_HTTP_INTERNAL_SERVER_ERROR("failed to retry the ".. "dns/balancer resolver for '"..addr.upstream.host.. - "' with; "..tostring(err)) + "' with: "..tostring(err)) end else -- first try, so set the max number of retries diff --git a/spec/01-unit/08-entities_schemas_spec.lua b/spec/01-unit/08-entities_schemas_spec.lua index a6e89b5d0e1a..8176ddd9ca46 100644 --- a/spec/01-unit/08-entities_schemas_spec.lua +++ b/spec/01-unit/08-entities_schemas_spec.lua @@ -28,7 +28,7 @@ describe("Entities Schemas", function() describe("APIs", function() it("should refuse an empty object", function() local valid, errors = validate_entity({}, api_schema) - assert.False(valid) + assert.is_false(valid) assert.truthy(errors) end) @@ -38,7 +38,7 @@ describe("Entities Schemas", function() local t = {name = name, upstream_url = "http://mockbin.com", request_host = "mockbin.com"} local valid, errors = validate_entity(t, api_schema) - assert.False(valid) + assert.is_false(valid) assert.truthy(errors) assert.equal("name must only contain alphanumeric and '., -, _, ~' characters", errors.name) end @@ -52,7 +52,7 @@ describe("Entities Schemas", function() request_host = "mockbin.com", upstream_url = "asdasd" }, api_schema) - assert.False(valid) + assert.is_false(valid) assert.equal("upstream_url is not a url", errors.upstream_url) end) it("should return error with wrong upstream_url protocol", function() @@ -61,7 +61,7 @@ describe("Entities Schemas", function() request_host = "mockbin.com", upstream_url = "wot://mockbin.com/" }, api_schema) - assert.False(valid) + assert.is_false(valid) assert.equal("Supported protocols are HTTP and HTTPS", errors.upstream_url) end) it("should validate with upper case protocol", function() @@ -71,7 +71,7 @@ describe("Entities Schemas", function() upstream_url = "HTTP://mockbin.com/world" }, api_schema) assert.falsy(errors) - assert.True(valid) + assert.is_true(valid) end) end) @@ -80,7 +80,7 @@ describe("Entities Schemas", function() local t = {request_host = "", upstream_url = "http://mockbin.com", name = "mockbin"} local valid, errors = validate_entity(t, api_schema) - assert.False(valid) + assert.is_false(valid) assert.equal("At least a 'request_host' or a 'request_path' must be specified", errors.request_host) assert.equal("At least a 'request_host' or a 'request_path' must be specified", errors.request_path) end) @@ -95,7 +95,7 @@ describe("Entities Schemas", function() local valid, errors = validate_entity(t, api_schema) assert.equal("Invalid value: "..v, (errors and errors.request_host or "")) assert.falsy(errors.request_path) - assert.False(valid) + assert.is_false(valid) end end) it("should accept valid request_host", function() @@ -113,7 +113,7 @@ describe("Entities Schemas", function() local t = {request_host = v, upstream_url = "http://mockbin.com", name = "mockbin"} local valid, errors = validate_entity(t, api_schema) assert.falsy(errors) - assert.True(valid) + assert.is_true(valid) end end) it("should accept valid wildcard request_host", function() @@ -123,7 +123,7 @@ describe("Entities Schemas", function() local t = {request_host = v, upstream_url = "http://mockbin.com", name = "mockbin"} local valid, errors = validate_entity(t, api_schema) assert.falsy(errors) - assert.True(valid) + assert.is_true(valid) end end) it("should refuse request_host with more than one wildcard", function() @@ -134,7 +134,7 @@ describe("Entities Schemas", function() } local valid, errors = validate_entity(api_t, api_schema) - assert.False(valid) + assert.is_false(valid) assert.equal("Only one wildcard is allowed: *.mockbin.*", errors.request_host) end) it("should refuse invalid wildcard request_host placement", function() @@ -144,7 +144,7 @@ describe("Entities Schemas", function() local t = {request_host = v, upstream_url = "http://mockbin.com", name = "mockbin"} local valid, errors = validate_entity(t, api_schema) assert.equal("Invalid wildcard placement: "..v, (errors and errors.request_host or "")) - assert.False(valid) + assert.is_false(valid) end end) it("should refuse invalid wildcard request_host", function() @@ -159,7 +159,7 @@ describe("Entities Schemas", function() local valid, errors = validate_entity(t, api_schema) assert.equal("Invalid value: "..v, (errors and errors.request_host or "")) assert.falsy(errors.request_path) - assert.False(valid) + assert.is_false(valid) end end) end) @@ -169,7 +169,7 @@ describe("Entities Schemas", function() local t = {request_path = "", upstream_url = "http://mockbin.com"} local valid, errors = validate_entity(t, api_schema) - assert.False(valid) + assert.is_false(valid) assert.equal("At least a 'request_host' or a 'request_path' must be specified", errors.request_host) assert.equal("At least a 'request_host' or a 'request_path' must be specified", errors.request_path) end) @@ -179,7 +179,7 @@ describe("Entities Schemas", function() for _, v in ipairs(invalids) do local t = {request_path = v, upstream_url = "http://mockbin.com", name = "mockbin"} local valid, errors = validate_entity(t, api_schema) - assert.False(valid) + assert.is_false(valid) assert.equal("must only contain alphanumeric and '., -, _, ~, /, %' characters", errors.request_path) end end) @@ -190,7 +190,7 @@ describe("Entities Schemas", function() local t = {request_path = v, upstream_url = "http://mockbin.com", name = "mockbin"} local valid, errors = validate_entity(t, api_schema) assert.falsy(errors) - assert.True(valid) + assert.is_true(valid) end end) it("should not accept bad %-encoded characters", function() @@ -206,7 +206,7 @@ describe("Entities Schemas", function() for i, v in ipairs(invalids) do local t = {request_path = v, upstream_url = "http://mockbin.com", name = "mockbin"} local valid, errors = validate_entity(t, api_schema) - assert.False(valid) + assert.is_false(valid) assert.equal("must use proper encoding; '"..errstr[i].."' is invalid", errors.request_path) end end) @@ -216,7 +216,7 @@ describe("Entities Schemas", function() local t = {request_path = v, upstream_url = "http://mockbin.com", name = "mockbin"} local valid, errors = validate_entity(t, api_schema) assert.falsy(errors) - assert.True(valid) + assert.is_true(valid) end end) it("should not accept without prefix slash", function() @@ -225,7 +225,7 @@ describe("Entities Schemas", function() for _, v in ipairs(invalids) do local t = {request_path = v, upstream_url = "http://mockbin.com", name = "mockbin"} local valid, errors = validate_entity(t, api_schema) - assert.False(valid) + assert.is_false(valid) assert.equal("must be prefixed with slash: '"..v.."'", errors.request_path) end end) @@ -236,7 +236,7 @@ describe("Entities Schemas", function() upstream_url = "http://mockbin.com" }, api_schema) assert.falsy(errors) - assert.True(valid) + assert.is_true(valid) end) it("should not accept invalid URI", function() local invalids = {"//status", "/status//123", "/status/123//"} @@ -244,7 +244,7 @@ describe("Entities Schemas", function() for _, v in ipairs(invalids) do local t = {request_path = v, upstream_url = "http://mockbin.com", name = "mockbin"} local valid, errors = validate_entity(t, api_schema) - assert.False(valid) + assert.is_false(valid) assert.equal("invalid: '"..v.."'", errors.request_path) end end) @@ -256,7 +256,7 @@ describe("Entities Schemas", function() local valid, errors = validate_entity(t, api_schema) assert.falsy(errors) assert.equal(string.sub(v, 1, -2), t.request_path) - assert.True(valid) + assert.is_true(valid) end end) end) @@ -268,7 +268,7 @@ describe("Entities Schemas", function() local t = {request_host = "mydomain.com", upstream_url = "http://mockbin.com", name = "mockbin", retries = v} local valid, errors = validate_entity(t, api_schema) assert.falsy(errors) - assert.True(valid) + assert.is_true(valid) end end) it("rejects invalid values", function() @@ -276,7 +276,7 @@ describe("Entities Schemas", function() for _, v in ipairs(valids) do local t = {request_host = "mydomain.com", upstream_url = "http://mockbin.com", name = "mockbin", retries = v} local valid, errors = validate_entity(t, api_schema) - assert.False(valid) + assert.is_false(valid) assert.equal("retries must be an integer, from 0 to 32767", errors.retries) end end) @@ -288,14 +288,14 @@ describe("Entities Schemas", function() upstream_url = "http://mockbin.com" }, api_schema) assert.falsy(errors) - assert.True(valid) + assert.is_true(valid) end) it("should complain if missing request_host and request_path", function() local valid, errors = validate_entity({ name = "mockbin" }, api_schema) - assert.False(valid) + assert.is_false(valid) assert.equal("At least a 'request_host' or a 'request_path' must be specified", errors.request_path) assert.equal("At least a 'request_host' or a 'request_path' must be specified", errors.request_host) @@ -303,7 +303,7 @@ describe("Entities Schemas", function() name = "mockbin", request_path = true }, api_schema) - assert.False(valid) + assert.is_false(valid) assert.equal("request_path is not a string", errors.request_path) assert.equal("At least a 'request_host' or a 'request_path' must be specified", errors.request_host) end) @@ -313,7 +313,7 @@ describe("Entities Schemas", function() local valid, errors = validate_entity(t, api_schema) assert.falsy(errors) - assert.True(valid) + assert.is_true(valid) assert.equal("mockbin.com", t.name) end) @@ -322,7 +322,7 @@ describe("Entities Schemas", function() local valid, errors = validate_entity(t, api_schema) assert.falsy(errors) - assert.True(valid) + assert.is_true(valid) assert.equal("mockbin", t.name) end) @@ -330,14 +330,14 @@ describe("Entities Schemas", function() local t = {upstream_url = "http://mockbin.com", request_host = "mockbin.com"} local valid, errors = validate_entity(t, api_schema) - assert.True(valid) + assert.is_true(valid) assert.falsy(errors) assert.equal("mockbin.com", t.name) t = {upstream_url = "http://mockbin.com", request_path = "/mockbin/status"} valid, errors = validate_entity(t, api_schema) - assert.True(valid) + assert.is_true(valid) assert.falsy(errors) assert.equal("mockbin-status", t.name) end) @@ -350,17 +350,17 @@ describe("Entities Schemas", function() describe("Consumers", function() it("should require a `custom_id` or `username`", function() local valid, errors = validate_entity({}, consumer_schema) - assert.False(valid) + assert.is_false(valid) assert.equal("At least a 'custom_id' or a 'username' must be specified", errors.username) assert.equal("At least a 'custom_id' or a 'username' must be specified", errors.custom_id) valid, errors = validate_entity({ username = "" }, consumer_schema) - assert.False(valid) + assert.is_false(valid) assert.equal("At least a 'custom_id' or a 'username' must be specified", errors.username) assert.equal("At least a 'custom_id' or a 'username' must be specified", errors.custom_id) valid, errors = validate_entity({ username = true }, consumer_schema) - assert.False(valid) + assert.is_false(valid) assert.equal("username is not a string", errors.username) assert.equal("At least a 'custom_id' or a 'username' must be specified", errors.custom_id) end) @@ -380,20 +380,20 @@ describe("Entities Schemas", function() it("should not validate if the plugin doesn't exist (not installed)", function() local valid, errors = validate_entity({name = "world domination"}, plugins_schema) - assert.False(valid) + assert.is_false(valid) assert.equal("Plugin \"world domination\" not found", errors.config) end) it("should validate a plugin configuration's `config` field", function() -- Success local plugin = {name = "key-auth", api_id = "stub", config = {key_names = {"x-kong-key"}}} local valid = validate_entity(plugin, plugins_schema, {dao = dao_stub}) - assert.True(valid) + assert.is_true(valid) -- Failure plugin = {name = "rate-limiting", api_id = "stub", config = { second = "hello" }} local valid, errors = validate_entity(plugin, plugins_schema, {dao = dao_stub}) - assert.False(valid) + assert.is_false(valid) assert.equal("second is not a number", errors["config.second"]) end) it("should have an empty config if none is specified and if the config schema does not have default", function() @@ -401,7 +401,7 @@ describe("Entities Schemas", function() local plugin = {name = "key-auth", api_id = "stub"} local valid = validate_entity(plugin, plugins_schema, {dao = dao_stub}) assert.same({key_names = {"apikey"}, hide_credentials = false, anonymous = false}, plugin.config) - assert.True(valid) + assert.is_true(valid) end) it("should be valid if no value is specified for a subfield and if the config schema has default as empty array", function() -- Insert response-transformer, whose default config has no default values, and should be empty @@ -425,7 +425,7 @@ describe("Entities Schemas", function() json = {} } }, plugin2.config) - assert.True(valid) + assert.is_true(valid) end) describe("self_check", function() @@ -442,11 +442,11 @@ describe("Entities Schemas", function() end local valid, _, err = validate_entity({name = "stub", api_id = "0000", consumer_id = "0000", config = {string = "foo"}}, plugins_schema) - assert.False(valid) + assert.is_false(valid) assert.equal("No consumer can be configured for that plugin", err.message) valid, err = validate_entity({name = "stub", api_id = "0000", config = {string = "foo"}}, plugins_schema, {dao = dao_stub}) - assert.True(valid) + assert.is_true(valid) assert.falsy(err) end) end) @@ -462,35 +462,35 @@ describe("Entities Schemas", function() it("should require a valid `name` and no port", function() local valid, errors, check valid, errors = validate_entity({}, upstreams_schema) - assert.False(valid) + assert.is_false(valid) assert.equal("name is required", errors.name) valid, errors, check = validate_entity({ name = "123.123.123.123" }, upstreams_schema) - assert.False(valid) - assert.Nil(errors) + assert.is_false(valid) + assert.is_nil(errors) assert.equal("Invalid name; no ip addresses allowed", check.message) valid, errors, check = validate_entity({ name = "\\\\bad\\\\////name////" }, upstreams_schema) - assert.False(valid) - assert.Nil(errors) + assert.is_false(valid) + assert.is_nil(errors) assert.equal("Invalid name; must be a valid hostname", check.message) valid, errors, check = validate_entity({ name = "name:80" }, upstreams_schema) - assert.False(valid) - assert.Nil(errors) + assert.is_false(valid) + assert.is_nil(errors) assert.equal("Invalid name; no port allowed", check.message) valid, errors, check = validate_entity({ name = "valid.host.name" }, upstreams_schema) - assert.True(valid) - assert.Nil(errors) - assert.Nil(check) + assert.is_true(valid) + assert.is_nil(errors) + assert.is_nil(check) end) it("should require (optional) slots in a valid range", function() local valid, errors, check, _ local data = { name = "valid.host.name" } valid, _, _ = validate_entity(data, upstreams_schema) - assert.True(valid) + assert.is_true(valid) assert.equal(slots_default, data.slots) local bad_slots = { -1, slots_min - 1, slots_max + 1 } @@ -500,8 +500,8 @@ describe("Entities Schemas", function() slots = slots, } valid, errors, check = validate_entity(data, upstreams_schema) - assert.False(valid) - assert.Nil(errors) + assert.is_false(valid) + assert.is_nil(errors) assert.equal("number of slots must be between "..slots_min.." and "..slots_max, check.message) end @@ -512,9 +512,9 @@ describe("Entities Schemas", function() slots = slots, } valid, errors, check = validate_entity(data, upstreams_schema) - assert.True(valid) - assert.Nil(errors) - assert.Nil(check) + assert.is_true(valid) + assert.is_nil(errors) + assert.is_nil(check) end end) @@ -547,9 +547,9 @@ describe("Entities Schemas", function() slots = math.random(slots_min, slots_max) } valid, errors, check = validate_entity(data, upstreams_schema) - assert.True(valid) - assert.Nil(errors) - assert.Nil(check) + assert.is_true(valid) + assert.is_nil(errors) + assert.is_nil(check) validate_order(data.orderlist, data.slots) end @@ -560,9 +560,9 @@ describe("Entities Schemas", function() orderlist = utils.shallow_copy(lst) } valid, errors, check = validate_entity(data, upstreams_schema) - assert.True(valid) - assert.Nil(errors) - assert.Nil(check) + assert.is_true(valid) + assert.is_nil(errors) + assert.is_nil(check) assert.same(lst, data.orderlist) data = { @@ -571,8 +571,8 @@ describe("Entities Schemas", function() orderlist = { 9,7,5,3,1,2,4,6,8 } -- too short (9) } valid, errors, check = validate_entity(data, upstreams_schema) - assert.False(valid) - assert.Nil(errors) + assert.is_false(valid) + assert.is_nil(errors) assert.are.equal("size mismatch between 'slots' and 'orderlist'",check.message) data = { @@ -581,8 +581,8 @@ describe("Entities Schemas", function() orderlist = { 9,7,5,3,1,2,4,6,8,10,11 } -- too long (11) } valid, errors, check = validate_entity(data, upstreams_schema) - assert.False(valid) - assert.Nil(errors) + assert.is_false(valid) + assert.is_nil(errors) assert.are.equal("size mismatch between 'slots' and 'orderlist'",check.message) data = { @@ -591,8 +591,8 @@ describe("Entities Schemas", function() orderlist = { 9,7,5,3,1,2,4,6,8,8 } -- a double value (2x 8, no 10) } valid, errors, check = validate_entity(data, upstreams_schema) - assert.False(valid) - assert.Nil(errors) + assert.is_false(valid) + assert.is_nil(errors) assert.are.equal("invalid orderlist",check.message) data = { @@ -601,8 +601,8 @@ describe("Entities Schemas", function() orderlist = { 9,7,5,3,1,2,4,6,8,11 } -- a hole (10 missing) } valid, errors, check = validate_entity(data, upstreams_schema) - assert.False(valid) - assert.Nil(errors) + assert.is_false(valid) + assert.is_nil(errors) assert.are.equal("invalid orderlist",check.message) end) @@ -620,21 +620,21 @@ describe("Entities Schemas", function() local valid, errors, check valid, errors, check = validate_entity({}, targets_schema) - assert.False(valid) + assert.is_false(valid) assert.equal(errors.target, "target is required") assert.is_nil(check) local names = { "valid.name", "valid.name:8080", "12.34.56.78", "1.2.3.4:123" } for _, name in ipairs(names) do valid, errors, check = validate_entity({ target = name }, targets_schema) - assert.True(valid) + assert.is_true(valid) assert.is_nil(errors) assert.is_nil(check) end valid, errors, check = validate_entity({ target = "\\\\bad\\\\////name////" }, targets_schema) - assert.False(valid) - assert.Nil(errors) + assert.is_false(valid) + assert.is_nil(errors) assert.equal("Invalid target; not a valid hostname or ip address", check.message) end) @@ -648,7 +648,7 @@ describe("Entities Schemas", function() for i, name in ipairs(names_in) do local data = { target = name } valid, errors, check = validate_entity(data, targets_schema) - assert.True(valid) + assert.is_true(valid) assert.is_nil(errors) assert.is_nil(check) assert.equal(names_out[i], data.target) @@ -661,7 +661,7 @@ describe("Entities Schemas", function() weights = { -10, weight_min - 1, weight_max + 1 } for _, weight in ipairs(weights) do valid, errors, check = validate_entity({ target = "1.2.3.4", weight = weight }, targets_schema) - assert.False(valid) + assert.is_false(valid) assert.is_nil(errors) assert.equal("weight must be from "..weight_min.." to "..weight_max, check.message) end @@ -669,7 +669,7 @@ describe("Entities Schemas", function() weights = { weight_min, weight_default, weight_max } for _, weight in ipairs(weights) do valid, errors, check = validate_entity({ target = "1.2.3.4", weight = weight }, targets_schema) - assert.True(valid) + assert.is_true(valid) assert.is_nil(errors) assert.is_nil(check) end @@ -683,7 +683,7 @@ describe("Entities Schemas", function() local valid, errors = validate_entity(t, api_schema, { update = true }) - assert.False(valid) + assert.is_false(valid) assert.same({ request_host = "At least a 'request_host' or a 'request_path' must be specified" }, errors) diff --git a/spec/01-unit/13-balancer_spec.lua b/spec/01-unit/13-balancer_spec.lua index 036cfe32f0e1..031f19d0f384 100644 --- a/spec/01-unit/13-balancer_spec.lua +++ b/spec/01-unit/13-balancer_spec.lua @@ -91,7 +91,7 @@ describe("Balancer", function() assert.equal(upstreams_dict[u.name], u.id) upstreams_dict[u.name] = nil -- remove each match end - assert.is.Nil(next(upstreams_dict)) -- should be empty now + assert.is_nil(next(upstreams_dict)) -- should be empty now end) end) diff --git a/spec/02-integration/04-core/02-hooks_spec.lua b/spec/02-integration/04-core/02-hooks_spec.lua index 928277f399c5..e05b97d5f17c 100644 --- a/spec/02-integration/04-core/02-hooks_spec.lua +++ b/spec/02-integration/04-core/02-hooks_spec.lua @@ -1041,1047 +1041,3 @@ describe("Core Hooks", function() end) end) end) ---[==[ -local helpers = require "spec.helpers" -local cjson = require "cjson" -local cache = require "kong.tools.database_cache" -local pl_tablex = require "pl.tablex" -local pl_utils = require "pl.utils" -local pl_path = require "pl.path" -local pl_file = require "pl.file" -local pl_stringx = require "pl.stringx" - -local api_client - -local function get_cache(key) - local r = assert(api_client:send { - method = "GET", - path = "/cache/"..key, - headers = {} - }) - assert.response(r).has.status(200) - return assert.response(r).has.jsonbody() -end - -describe("Core Hooks", function() - describe("Global", function() - describe("Global Plugin entity invalidation on API", function() - local client - local plugin - - before_each(function() - helpers.start_kong() - client = helpers.proxy_client() - api_client = helpers.admin_client() - - assert(helpers.dao.apis:insert { - request_host = "hooks1.com", - upstream_url = "http://mockbin.com" - }) - - plugin = assert(helpers.dao.plugins:insert { - name = "rate-limiting", - config = { minute = 10 } - }) - end) - after_each(function() - if client and api_client then - client:close() - api_client:close() - end - helpers.stop_kong() - end) - - it("should invalidate a global plugin when deleting", function() - -- Making a request to populate the cache - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks1.com" - } - }) - assert.res_status(200, res) - assert.is_string(res.headers["X-RateLimit-Limit-minute"]) - - -- Make sure the cache is populated - get_cache(cache.plugin_key("rate-limiting", nil, nil)) - - -- Delete plugin - local res = assert(api_client:send { - method = "DELETE", - path = "/plugins/"..plugin.id - }) - assert.res_status(204, res) - - -- Wait for cache to be invalidated - helpers.wait_for_invalidation(cache.plugin_key("rate-limiting", nil, nil)) - - -- Consuming the API again without any authorization - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks1.com" - } - }) - assert.res_status(200, res) - assert.is_nil(res.headers["X-RateLimit-Limit-minute"]) - end) - end) - - describe("Global Plugin entity invalidation on Consumer", function() - local client - local plugin, consumer - - setup(function() - helpers.dao:truncate_tables() - end) - before_each(function() - helpers.start_kong() - client = helpers.proxy_client() - api_client = helpers.admin_client() - - assert(helpers.dao.apis:insert { - request_host = "hooks1.com", - upstream_url = "http://mockbin.com" - }) - - assert(helpers.dao.plugins:insert { - name = "key-auth", - config = {} - }) - - consumer = assert(helpers.dao.consumers:insert { - username = "test" - }) - assert(helpers.dao.keyauth_credentials:insert { - key = "kong", - consumer_id = consumer.id - }) - - plugin = assert(helpers.dao.plugins:insert { - name = "rate-limiting", - consumer_id = consumer.id, - config = { minute = 10 } - }) - end) - after_each(function() - if client and api_client then - client:close() - api_client:close() - end - helpers.stop_kong() - end) - - it("should invalidate a global plugin when deleting", function() - -- Making a request to populate the cache - local res = assert(client:send { - method = "GET", - path = "/status/200?apikey=kong", - headers = { - ["Host"] = "hooks1.com" - } - }) - assert.res_status(200, res) - assert.is_string(res.headers["X-RateLimit-Limit-minute"]) - - -- Make sure the cache is populated - get_cache(cache.plugin_key("rate-limiting", nil, consumer.id)) - - -- Delete plugin - local res = assert(api_client:send { - method = "DELETE", - path = "/plugins/"..plugin.id - }) - assert.res_status(204, res) - - -- Wait for cache to be invalidated - helpers.wait_for_invalidation(cache.plugin_key("rate-limiting", nil, consumer.id)) - - -- Consuming the API again without any authorization - local res = assert(client:send { - method = "GET", - path = "/status/200?apikey=kong", - headers = { - ["Host"] = "hooks1.com" - } - }) - assert.res_status(200, res) - assert.is_nil(res.headers["X-RateLimit-Limit-minute"]) - - -- Delete consumer - local res = assert(api_client:send { - method = "DELETE", - path = "/consumers/"..consumer.id - }) - assert.res_status(204, res) - - local res = assert(client:send { - method = "GET", - path = "/status/200?apikey=kong", - headers = { - ["Host"] = "hooks1.com" - } - }) - assert.res_status(200, res) - assert.is_nil(res.headers["X-RateLimit-Limit-minute"]) - - local res = assert(api_client:send { - method = "GET", - path = "/plugins/"..plugin.id, - }) - assert.res_status(404, res) - end) - end) - end) - - describe("Other", function() - local client - local consumer, api1, api2, basic_auth2, api3, rate_limiting_consumer - - before_each(function() - helpers.start_kong() - client = helpers.proxy_client() - api_client = helpers.admin_client() - - consumer = assert(helpers.dao.consumers:insert { - username = "consumer1" - }) - assert(helpers.dao.basicauth_credentials:insert { - username = "user123", - password = "pass123", - consumer_id = consumer.id - }) - - api1 = assert(helpers.dao.apis:insert { - request_host = "hooks1.com", - upstream_url = "http://mockbin.com" - }) - - api2 = assert(helpers.dao.apis:insert { - request_host = "hooks-consumer.com", - upstream_url = "http://mockbin.com" - }) - basic_auth2 = assert(helpers.dao.plugins:insert { - name = "basic-auth", - api_id = api2.id, - config = {} - }) - - api3 = assert(helpers.dao.apis:insert { - request_host = "hooks-plugins.com", - upstream_url = "http://mockbin.com" - }) - assert(helpers.dao.plugins:insert { - name = "basic-auth", - api_id = api3.id, - config = {} - }) - assert(helpers.dao.plugins:insert { - name = "rate-limiting", - api_id = api3.id, - config = { - minute = 10 - } - }) - rate_limiting_consumer = assert(helpers.dao.plugins:insert { - name = "rate-limiting", - api_id = api3.id, - consumer_id = consumer.id, - config = { - minute = 3 - } - }) - end) - after_each(function() - if client and api_client then - client:close() - api_client:close() - end - helpers.stop_kong() - end) - - describe("Plugin entity invalidation", function() - it("should invalidate a plugin when deleting", function() - -- Making a request to populate the cache - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks-consumer.com", - ["Authorization"] = "Basic dXNlcjEyMzpwYXNzMTIz" - } - }) - assert.res_status(200, res) - - -- Make sure the cache is populated - get_cache(cache.plugin_key("basic-auth", api2.id, nil)) - - -- Delete plugin - local res = assert(api_client:send { - method = "DELETE", - path = "/apis/"..api2.id.."/plugins/"..basic_auth2.id - }) - assert.res_status(204, res) - - -- Wait for cache to be invalidated - helpers.wait_for_invalidation(cache.plugin_key("basic-auth", api2.id, nil)) - - -- Consuming the API again without any authorization - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks-consumer.com" - } - }) - assert.res_status(200, res) - end) - - it("#only should invalidate a plugin when updating", function() - -- Consuming the API without any authorization - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks-consumer.com" - } - }) - assert.res_status(401, res) - - -- Making a request to populate the cache - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks-consumer.com", - ["Authorization"] = "Basic dXNlcjEyMzpwYXNzMTIz" - } - }) - assert.res_status(200, res) - - -- Make sure the cache is populated - get_cache(cache.plugin_key("rate-limiting", api3.id, consumer.id)) - - -- Update plugin - local res = assert(api_client:send { - method = "PATCH", - path = "/apis/"..api2.id.."/plugins/"..basic_auth2.id, - headers = { - ["Content-Type"] = "application/json" - }, - body = cjson.encode({ - enabled = false - }) - }) - assert.res_status(200, res) - - -- Wait for cache to be invalidated - helpers.wait_for_invalidation(cache.plugin_key("rate-limiting", api3.id, consumer.id)) - - -- Consuming the API again without any authorization - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks-consumer.com" - } - }) - assert.res_status(200, res) - end) - - it("should invalidate a consumer-specific plugin when deleting", function() - -- Making a request to populate the cache - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks-plugins.com", - ["Authorization"] = "Basic dXNlcjEyMzpwYXNzMTIz" - } - }) - assert.res_status(200, res) - assert.equal(3, tonumber(res.headers["x-ratelimit-limit-minute"])) - - -- Make sure the cache is populated - get_cache(cache.plugin_key("rate-limiting", api3.id, consumer.id)) - - -- Delete plugin - local res = assert(api_client:send { - method = "DELETE", - path = "/apis/"..api3.id.."/plugins/"..rate_limiting_consumer.id - }) - assert.res_status(204, res) - - -- Wait for cache to be invalidated - helpers.wait_for_invalidation(cache.plugin_key("rate-limiting", api3.id, consumer.id)) - - -- Consuming the API again - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks-plugins.com", - ["Authorization"] = "Basic dXNlcjEyMzpwYXNzMTIz" - } - }) - assert.res_status(200, res) - assert.equal(10, tonumber(res.headers["x-ratelimit-limit-minute"])) - end) - - it("#only should invalidate a consumer-specific plugin when updating", function() - -- Making a request to populate the cache - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks-plugins.com", - ["Authorization"] = "Basic dXNlcjEyMzpwYXNzMTIz" - } - }) - assert.res_status(200, res) - assert.equal(3, tonumber(res.headers["x-ratelimit-limit-minute"])) - - -- Make sure the cache is populated - get_cache(cache.plugin_key("basic-auth", api2.id, nil)) - - -- Update plugin - local res = assert(api_client:send { - method = "PATCH", - path = "/apis/"..api3.id.."/plugins/"..rate_limiting_consumer.id, - headers = { - ["Content-Type"] = "application/json" - }, - body = cjson.encode({ - enabled = false - }) - }) - assert.res_status(200, res) - - -- Wait for cache to be invalidated - helpers.wait_for_invalidation(cache.plugin_key("basic-auth", api2.id, nil)) - - -- Consuming the API again - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks-plugins.com", - ["Authorization"] = "Basic dXNlcjEyMzpwYXNzMTIz" - } - }) - assert.res_status(200, res) - assert.equal(10, tonumber(res.headers["x-ratelimit-limit-minute"])) - end) - end) - - describe("Consumer entity invalidation", function() - it("should invalidate a consumer when deleting", function() - -- Making a request to populate the cache - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks-consumer.com", - ["Authorization"] = "Basic dXNlcjEyMzpwYXNzMTIz" - } - }) - assert.res_status(200, res) - - -- Make sure the cache is populated - get_cache(cache.consumer_key(consumer.id)) - - -- Delete consumer - local res = assert(api_client:send { - method = "DELETE", - path = "/consumers/"..consumer.id - }) - assert.res_status(204, res) - - -- Wait for consumer be invalidated - helpers.wait_for_invalidation(cache.consumer_key(consumer.id)) - - -- Wait for Basic Auth credential to be invalidated - helpers.wait_for_invalidation(cache.basicauth_credential_key("user123")) - - -- Consuming the API again - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks-consumer.com", - ["Authorization"] = "Basic dXNlcjEyMzpwYXNzMTIz" - } - }) - assert.res_status(403, res) - end) - - it("should invalidate a consumer when updating", function() - -- Making a request to populate the cache - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks-consumer.com", - ["Authorization"] = "Basic dXNlcjEyMzpwYXNzMTIz" - } - }) - assert.res_status(200, res) - - -- Make sure the cache is populated - get_cache(cache.consumer_key(consumer.id)) - - -- Update consumer - local res = assert(api_client:send { - method = "PATCH", - path = "/consumers/"..consumer.id, - headers = { - ["Content-Type"] = "application/json" - }, - body = cjson.encode({ - username = "updated_consumer" - }) - }) - assert.res_status(200, res) - - -- Wait for consumer be invalidated - helpers.wait_for_invalidation(cache.consumer_key(consumer.id),3) - - -- Consuming the API again - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks-consumer.com", - ["Authorization"] = "Basic dXNlcjEyMzpwYXNzMTIz" - } - }) - assert.res_status(200, res) - - -- Making sure the cache is updated - local body = get_cache(cache.consumer_key(consumer.id)) - assert.equal("updated_consumer", body.username) - end) - end) - - describe("API entity invalidation", function() - it("should invalidate ALL_APIS_BY_DICT when adding a new API", function() - -- Making a request to populate ALL_APIS_BY_DICT - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks1.com" - } - }) - assert.res_status(200, res) - - -- Make sure the cache is populated - get_cache(cache.all_apis_by_dict_key()) - - -- Adding a new API - local res = assert(api_client:send { - method = "POST", - path = "/apis/", - headers = { - ["Content-Type"] = "application/json" - }, - body = cjson.encode({ - request_host = "dynamic-hooks.com", - upstream_url = "http://mockbin.org" - }) - }) - assert.res_status(201, res) - - -- Wait for consumer be invalidated - helpers.wait_for_invalidation(cache.all_apis_by_dict_key()) - - -- Consuming the API again - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks1.com" - } - }) - assert.res_status(200, res) - - -- Make sure the cache is populated - local body = get_cache(cache.all_apis_by_dict_key()) - assert.is_table(body.by_dns["hooks1.com"]) - assert.is_table(body.by_dns["dynamic-hooks.com"]) - end) - - it("should invalidate ALL_APIS_BY_DICT when updating an API", function() - -- Making a request to populate ALL_APIS_BY_DICT - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks1.com" - } - }) - assert.res_status(200, res) - - -- Make sure the cache is populated - local body = get_cache(cache.all_apis_by_dict_key()) - assert.equal("http://mockbin.com", body.by_dns["hooks1.com"].upstream_url) - - -- Update API - local res = assert(api_client:send { - method = "PATCH", - path = "/apis/"..api1.id, - headers = { - ["Content-Type"] = "application/json" - }, - body = cjson.encode({ - upstream_url = "http://mockbin.org" - }) - }) - assert.res_status(200, res) - - -- Wait for consumer be invalidated - helpers.wait_for_invalidation(cache.all_apis_by_dict_key()) - - -- Consuming the API again - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks1.com" - } - }) - assert.res_status(200, res) - - -- Make sure the cache is populated with updated value - local body = get_cache(cache.all_apis_by_dict_key()) - assert.equal("http://mockbin.org", body.by_dns["hooks1.com"].upstream_url) - assert.equal(3, pl_tablex.size(body.by_dns)) - end) - - it("should invalidate ALL_APIS_BY_DICT when deleting an API", function() - -- Making a request to populate ALL_APIS_BY_DICT - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks1.com" - } - }) - assert.res_status(200, res) - - -- Make sure the cache is populated - local body = get_cache(cache.all_apis_by_dict_key()) - assert.equal("http://mockbin.com", body.by_dns["hooks1.com"].upstream_url) - - -- Deleting the API - local res = assert(api_client:send { - method = "DELETE", - path = "/apis/"..api1.id - }) - assert.res_status(204, res) - - -- Wait for consumer be invalidated - helpers.wait_for_invalidation(cache.all_apis_by_dict_key()) - - -- Consuming the API again - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks1.com" - } - }) - assert.res_status(404, res) - - -- Make sure the cache is populated with zero APIs - local body = get_cache(cache.all_apis_by_dict_key()) - assert.equal(2, pl_tablex.size(body.by_dns)) - end) - end) - - describe("Upstreams entity", function() - local upstream - - before_each(function() - assert(helpers.dao.apis:insert { - request_host = "hooks2.com", - upstream_url = "http://mybalancer" - }) - upstream = assert(helpers.dao.upstreams:insert { - name = "mybalancer", - }) - assert(helpers.dao.targets:insert { - upstream_id = upstream.id, - target = "mockbin.com:80", - weight = 10, - }) - end) - it("invalidates the upstream-list when adding an upstream", function() - -- Making a request to populate the cache with the upstreams - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks2.com" - } - }) - assert.response(res).has.status(200) - -- validate that the cache is populated - get_cache(cache.upstreams_dict_key(upstream.id)) - -- add an upstream - local res = assert(api_client:send { - method = "POST", - path = "/upstreams", - headers = { - ["Content-Type"] = "application/json" - }, - body = { - name = "my2nd.upstream", - }, - }) - assert.response(res).has.status(201) - -- wait for invalidation of the cache - helpers.wait_for_invalidation(cache.upstreams_dict_key(upstream.id)) - end) - it("invalidates the upstream-list when updating an upstream", function() - -- Making a request to populate the cache with the upstreams - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks2.com" - } - }) - assert.response(res).has.status(200) - -- validate that the cache is populated - get_cache(cache.upstreams_dict_key(upstream.id)) - -- patch the upstream - local res = assert(api_client:send { - method = "PATCH", - path = "/upstreams/"..upstream.id, - headers = { - ["Content-Type"] = "application/json" - }, - body = { - slots = 10, - orderlist = { 1,2,3,4,5,6,7,8,9,10 } - }, - }) - assert.response(res).has.status(200) - -- wait for invalidation of the cache - helpers.wait_for_invalidation(cache.upstreams_dict_key(upstream.id)) - end) - it("invalidates the upstream-list when deleting an upstream", function() - -- Making a request to populate the cache with the upstreams - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks2.com" - } - }) - assert.response(res).has.status(200) - -- validate that the cache is populated - get_cache(cache.upstreams_dict_key(upstream.id)) - -- delete the upstream - local res = assert(api_client:send { - method = "DELETE", - path = "/upstreams/mybalancer", - }) - assert.response(res).has.status(204) - -- wait for invalidation of the cache - helpers.wait_for_invalidation(cache.upstreams_dict_key(upstream.id)) - end) - it("invalidates an upstream when updating an upstream", function() - -- Making a request to populate the cache with the upstream - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks2.com" - } - }) - assert.response(res).has.status(200) - -- validate that the cache is populated - get_cache(cache.upstream_key(upstream.id)) - -- patch the upstream - local res = assert(api_client:send { - method = "PATCH", - path = "/upstreams/"..upstream.id, - headers = { - ["Content-Type"] = "application/json" - }, - body = { - slots = 10, - orderlist = { 1,2,3,4,5,6,7,8,9,10 } - }, - }) - assert.response(res).has.status(200) - -- wait for invalidation of the cache - helpers.wait_for_invalidation(cache.upstream_key(upstream.id)) - end) - it("invalidates an upstream when deleting an upstream", function() - -- Making a request to populate the cache with the upstream - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks2.com" - } - }) - assert.response(res).has.status(200) - -- validate that the cache is populated - get_cache(cache.upstream_key(upstream.id)) - -- delete the upstream - local res = assert(api_client:send { - method = "DELETE", - path = "/upstreams/mybalancer", - }) - assert.response(res).has.status(204) - -- wait for invalidation of the cache - helpers.wait_for_invalidation(cache.upstream_key(upstream.id)) - end) - it("invalidates the target-history when updating an upstream", function() - -- Making a request to populate target history for upstream - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks2.com" - } - }) - assert.response(res).has.status(200) - -- validate that the cache is populated - get_cache(cache.targets_key(upstream.id)) - -- patch the upstream - local res = assert(api_client:send { - method = "PATCH", - path = "/upstreams/"..upstream.id, - headers = { - ["Content-Type"] = "application/json" - }, - body = { - slots = 10, - orderlist = { 1,2,3,4,5,6,7,8,9,10 } - }, - }) - assert.response(res).has.status(200) - -- wait for invalidation of the cache - helpers.wait_for_invalidation(cache.targets_key(upstream.id)) - end) - it("invalidates the target-history when deleting an upstream", function() - -- Making a request to populate target history for upstream - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks2.com" - } - }) - assert.response(res).has.status(200) - -- validate that the cache is populated - get_cache(cache.targets_key(upstream.id)) - -- delete the upstream - local res = assert(api_client:send { - method = "DELETE", - path = "/upstreams/mybalancer", - }) - assert.response(res).has.status(204) - -- wait for invalidation of the cache - helpers.wait_for_invalidation(cache.targets_key(upstream.id)) - end) - end) - - describe("Targets entity", function() - local upstream - - setup(function() - assert(helpers.dao.apis:insert { - request_host = "hooks2.com", - upstream_url = "http://mybalancer" - }) - upstream = assert(helpers.dao.upstreams:insert { - name = "mybalancer", - }) - assert(helpers.dao.targets:insert { - upstream_id = upstream.id, - target = "mockbin.com:80", - weight = 10, - }) - end) - it("invalidates the target-history when adding a target", function() - -- Making a request to populate target history for upstream - local res = assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks2.com" - } - }) - assert.response(res).has.status(200) - -- validate that the cache is populated - get_cache(cache.targets_key(upstream.id)) - -- Adding a new target - local res = assert(api_client:send { - method = "POST", - path = "/upstreams/mybalancer/targets", - headers = { - ["Content-Type"] = "application/json" - }, - body = { - target = "mockbin.com:80", - weight = 5, - } - }) - assert.response(res).has.status(201) - -- wait for invalidation of the cache - helpers.wait_for_invalidation(cache.targets_key(upstream.id)) - -- Making another request to re-populate target history - assert(client:send { - method = "GET", - path = "/status/200", - headers = { - ["Host"] = "hooks2.com" - } - }) - -- validate that the cache is populated - local body = get_cache(cache.targets_key(upstream.id)) - -- check contents - assert.equal(10, body[1].weight) -- initial weight value - assert.equal(5, body[2].weight) -- new weight value - end) - end) - - describe("Serf events", function() - local PID_FILE = "/tmp/serf_test.pid" - local LOG_FILE = "/tmp/serf_test.log" - - local function kill(pid_file, args) - local cmd = string.format([[kill %s `cat %s` >/dev/null 2>&1]], args or "-0", pid_file) - return os.execute(cmd) - end - - local function is_running(pid_path) - if not pl_path.exists(pid_path) then return nil end - local code = kill(pid_path, "-0") - return code == 0 - end - - local function start_serf() - local args = { - ["-node"] = "test_node", - ["-bind"] = "127.0.0.1:10000", - ["-profile"] = "lan", - ["-rpc-addr"] = "127.0.0.1:10001" - } - setmetatable(args, require "kong.tools.printable") - - local cmd = string.format("nohup %s agent %s > %s 2>&1 & echo $! > %s", - helpers.test_conf.serf_path, - tostring(args), - LOG_FILE, PID_FILE) - - -- start Serf agent - local ok = pl_utils.execute(cmd) - if not ok then return error("Cannot start Serf") end - - -- ensure started (just an improved version of previous Serf service) - local start_timeout = 5 - local tstart = ngx.time() - local texp, started = tstart + start_timeout - - repeat - ngx.sleep(0.2) - started = is_running(PID_FILE) - until started or ngx.time() >= texp - - if not started then - -- time to get latest error log from serf.log - local logs = pl_file.read(LOG_FILE) - local tlogs = pl_stringx.split(logs, "\n") - local err = string.gsub(tlogs[#tlogs-1], "==> ", "") - err = pl_stringx.strip(err) - error("could not start Serf:\n "..err) - end - - if not ok then error("Error starting serf") end - end - - local function stop_serf() - os.execute(string.format("kill `cat %s` >/dev/null 2>&1", PID_FILE)) - end - - it("should synchronize nodes on members events", function() - start_serf() - - -- Tell Kong to join the new Serf - local res = assert(api_client:send { - method = "POST", - path = "/cluster/", - headers = { - ["Content-Type"] = "application/json" - }, - body = cjson.encode({address = "127.0.0.1:10000"}) - }) - assert.res_status(200, res) - - -- Wait until the node joins - helpers.wait_until(function() - local res = assert(api_client:send { - method = "GET", - path = "/cluster/" - }) - local body = cjson.decode(assert.res_status(200, res)) - if #body.data == 2 then - local found - for _, v in ipairs(body.data) do - if v.address == "127.0.0.1:10000" then - found = true - assert.equal("test_node", v.name) - assert.equal("alive", v.status) - else - assert.is_string(v.name) - assert.equal("alive", v.status) - end - end - return found - end - end, 5) - - -- Killing serf - stop_serf() - - -- Wait until the node appears as failed - helpers.wait_until(function() - local res = assert(api_client:send { - method = "GET", - path = "/cluster/" - }) - local body = cjson.decode(assert.res_status(200, res)) - local found - if #body.data == 2 then - for _, v in ipairs(body.data) do - if v.address == "127.0.0.1:10000" then - if v.name == "test_node" and v.status == "failed" then - found = true - end - else - assert.is_string(v.name) - assert.equal("alive", v.status) - end - end - end - ngx.sleep(1) - return found - end, 60) - - finally(function() - stop_serf() - end) - end) - end) - end) -end) ---]==] \ No newline at end of file diff --git a/spec/02-integration/05-proxy/05-balancer_spec.lua b/spec/02-integration/05-proxy/05-balancer_spec.lua index 0f0800477ffc..6c2b8aeef968 100644 --- a/spec/02-integration/05-proxy/05-balancer_spec.lua +++ b/spec/02-integration/05-proxy/05-balancer_spec.lua @@ -79,10 +79,9 @@ dao_helpers.for_each_dao(function(kong_config) setup(function() config_db = helpers.test_conf.database helpers.test_conf.database = kong_config.database -assert.same(helpers.test_conf, kong_config) end) teardown(function() - helpers.test_conf.database = kong_config.database + helpers.test_conf.database = config_db config_db = nil end)