693 lines
22 KiB
Lua
693 lines
22 KiB
Lua
------------------------------------------------------------------
|
|
-- Collection of utilities to help testing Kong features and plugins.
|
|
--
|
|
-- @copyright Copyright 2016-2022 Kong Inc. All rights reserved.
|
|
-- @license [Apache 2.0](https://opensource.org/licenses/Apache-2.0)
|
|
-- @module spec.helpers
|
|
|
|
|
|
local cjson = require("cjson.safe")
|
|
local lfs = require("lfs")
|
|
local pl_file = require("pl.file")
|
|
local luassert = require("luassert.assert")
|
|
local https_server = require("spec.fixtures.https_server")
|
|
|
|
|
|
local CONSTANTS = require("spec.internal.constants")
|
|
local shell = require("spec.internal.shell")
|
|
local asserts = require("spec.internal.asserts") -- luacheck: ignore
|
|
local client = require("spec.internal.client")
|
|
|
|
|
|
local get_available_port
|
|
do
|
|
local USED_PORTS = {}
|
|
|
|
function get_available_port()
|
|
for _i = 1, 10 do
|
|
local port = math.random(10000, 30000)
|
|
|
|
if not USED_PORTS[port] then
|
|
USED_PORTS[port] = true
|
|
|
|
local ok = shell.run("netstat -lnt | grep \":" .. port .. "\" > /dev/null", nil, 0)
|
|
|
|
if not ok then
|
|
-- return code of 1 means `grep` did not found the listening port
|
|
return port
|
|
|
|
else
|
|
print("Port " .. port .. " is occupied, trying another one")
|
|
end
|
|
end
|
|
end
|
|
|
|
error("Could not find an available port after 10 tries")
|
|
end
|
|
end
|
|
|
|
|
|
--------------------
|
|
-- Custom assertions
|
|
--
|
|
-- @section assertions
|
|
|
|
require("spec.helpers.wait")
|
|
|
|
--- Waits until a specific condition is met.
|
|
-- The check function will repeatedly be called (with a fixed interval), until
|
|
-- the condition is met. Throws an error on timeout.
|
|
--
|
|
-- NOTE: this is a regular Lua function, not a Luassert assertion.
|
|
-- @function wait_until
|
|
-- @param f check function that should return `truthy` when the condition has
|
|
-- been met
|
|
-- @param timeout (optional) maximum time to wait after which an error is
|
|
-- thrown, defaults to 5.
|
|
-- @param step (optional) interval between checks, defaults to 0.05.
|
|
-- @return nothing. It returns when the condition is met, or throws an error
|
|
-- when it times out.
|
|
-- @usage
|
|
-- -- wait 10 seconds for a file "myfilename" to appear
|
|
-- helpers.wait_until(function() return file_exist("myfilename") end, 10)
|
|
local function wait_until(f, timeout, step)
|
|
if CONSTANTS.TEST_COVERAGE_MODE == "true" then
|
|
timeout = CONSTANTS.TEST_COVERAGE_TIMEOUT
|
|
end
|
|
|
|
luassert.wait_until({
|
|
condition = "truthy",
|
|
fn = f,
|
|
timeout = timeout,
|
|
step = step,
|
|
})
|
|
end
|
|
|
|
|
|
--- Waits until no Lua error occurred
|
|
-- The check function will repeatedly be called (with a fixed interval), until
|
|
-- there is no Lua error occurred
|
|
--
|
|
-- NOTE: this is a regular Lua function, not a Luassert assertion.
|
|
-- @function pwait_until
|
|
-- @param f check function
|
|
-- @param timeout (optional) maximum time to wait after which an error is
|
|
-- thrown, defaults to 5.
|
|
-- @param step (optional) interval between checks, defaults to 0.05.
|
|
-- @return nothing. It returns when the condition is met, or throws an error
|
|
-- when it times out.
|
|
local function pwait_until(f, timeout, step)
|
|
if CONSTANTS.TEST_COVERAGE_MODE == "true" then
|
|
timeout = CONSTANTS.TEST_COVERAGE_TIMEOUT
|
|
end
|
|
|
|
luassert.wait_until({
|
|
condition = "no_error",
|
|
fn = f,
|
|
timeout = timeout,
|
|
step = step,
|
|
})
|
|
end
|
|
|
|
|
|
--- Wait for some timers, throws an error on timeout.
|
|
--
|
|
-- NOTE: this is a regular Lua function, not a Luassert assertion.
|
|
-- @function wait_timer
|
|
-- @tparam string timer_name_pattern the call will apply to all timers matching this string
|
|
-- @tparam boolean plain if truthy, the `timer_name_pattern` will be matched plain, so without pattern matching
|
|
-- @tparam string mode one of: "all-finish", "all-running", "any-finish", "any-running", or "worker-wide-all-finish"
|
|
--
|
|
-- any-finish: At least one of the timers that were matched finished
|
|
--
|
|
-- all-finish: All timers that were matched finished
|
|
--
|
|
-- any-running: At least one of the timers that were matched is running
|
|
--
|
|
-- all-running: All timers that were matched are running
|
|
--
|
|
-- worker-wide-all-finish: All the timers in the worker that were matched finished
|
|
-- @tparam number timeout maximum time to wait (optional, default: 2)
|
|
-- @tparam number admin_client_timeout, to override the default timeout setting (optional)
|
|
-- @tparam number forced_admin_port to override the default port of admin API (optional)
|
|
-- @usage helpers.wait_timer("rate-limiting", true, "all-finish", 10)
|
|
local function wait_timer(timer_name_pattern, plain,
|
|
mode, timeout,
|
|
admin_client_timeout, forced_admin_port)
|
|
if not timeout then
|
|
timeout = 2
|
|
end
|
|
|
|
local _admin_client
|
|
|
|
local all_running_each_worker = nil
|
|
local all_finish_each_worker = nil
|
|
local any_running_each_worker = nil
|
|
local any_finish_each_worker = nil
|
|
|
|
wait_until(function ()
|
|
if _admin_client then
|
|
_admin_client:close()
|
|
end
|
|
|
|
_admin_client = client.admin_client(admin_client_timeout, forced_admin_port)
|
|
local res = assert(_admin_client:get("/timers"))
|
|
local body = luassert.res_status(200, res)
|
|
local json = assert(cjson.decode(body))
|
|
local worker_id = json.worker.id
|
|
local worker_count = json.worker.count
|
|
|
|
if not all_running_each_worker then
|
|
all_running_each_worker = {}
|
|
all_finish_each_worker = {}
|
|
any_running_each_worker = {}
|
|
any_finish_each_worker = {}
|
|
|
|
for i = 0, worker_count - 1 do
|
|
all_running_each_worker[i] = false
|
|
all_finish_each_worker[i] = false
|
|
any_running_each_worker[i] = false
|
|
any_finish_each_worker[i] = false
|
|
end
|
|
end
|
|
|
|
local is_matched = false
|
|
|
|
for timer_name, timer in pairs(json.stats.timers) do
|
|
if string.find(timer_name, timer_name_pattern, 1, plain) then
|
|
is_matched = true
|
|
|
|
all_finish_each_worker[worker_id] = false
|
|
|
|
if timer.is_running then
|
|
all_running_each_worker[worker_id] = true
|
|
any_running_each_worker[worker_id] = true
|
|
goto continue
|
|
end
|
|
|
|
all_running_each_worker[worker_id] = false
|
|
|
|
goto continue
|
|
end
|
|
|
|
::continue::
|
|
end
|
|
|
|
if not is_matched then
|
|
any_finish_each_worker[worker_id] = true
|
|
all_finish_each_worker[worker_id] = true
|
|
end
|
|
|
|
local all_running = false
|
|
|
|
local all_finish = false
|
|
local all_finish_worker_wide = true
|
|
|
|
local any_running = false
|
|
local any_finish = false
|
|
|
|
for _, v in pairs(all_running_each_worker) do
|
|
all_running = all_running or v
|
|
end
|
|
|
|
for _, v in pairs(all_finish_each_worker) do
|
|
all_finish = all_finish or v
|
|
all_finish_worker_wide = all_finish_worker_wide and v
|
|
end
|
|
|
|
for _, v in pairs(any_running_each_worker) do
|
|
any_running = any_running or v
|
|
end
|
|
|
|
for _, v in pairs(any_finish_each_worker) do
|
|
any_finish = any_finish or v
|
|
end
|
|
|
|
if mode == "all-running" then
|
|
return all_running
|
|
end
|
|
|
|
if mode == "all-finish" then
|
|
return all_finish
|
|
end
|
|
|
|
if mode == "worker-wide-all-finish" then
|
|
return all_finish_worker_wide
|
|
end
|
|
|
|
if mode == "any-finish" then
|
|
return any_finish
|
|
end
|
|
|
|
if mode == "any-running" then
|
|
return any_running
|
|
end
|
|
|
|
error("unexpected error")
|
|
end, timeout)
|
|
end
|
|
|
|
|
|
--- Waits for invalidation of a cached key by polling the mgt-api
|
|
-- and waiting for a 404 response. Throws an error on timeout.
|
|
--
|
|
-- NOTE: this is a regular Lua function, not a Luassert assertion.
|
|
-- @function wait_for_invalidation
|
|
-- @param key (string) the cache-key to check
|
|
-- @param timeout (optional) in seconds (for default see `wait_until`).
|
|
-- @return nothing. It returns when the key is invalidated, or throws an error
|
|
-- when it times out.
|
|
-- @usage
|
|
-- local cache_key = "abc123"
|
|
-- helpers.wait_for_invalidation(cache_key, 10)
|
|
local function wait_for_invalidation(key, timeout)
|
|
-- TODO: this code is duplicated all over the codebase,
|
|
-- search codebase for "/cache/" endpoint
|
|
local api_client = client.admin_client()
|
|
wait_until(function()
|
|
local res = api_client:get("/cache/" .. key)
|
|
res:read_body()
|
|
return res.status == 404
|
|
end, timeout)
|
|
end
|
|
|
|
|
|
--- Wait for all targets, upstreams, services, and routes update
|
|
--
|
|
-- NOTE: this function is not available for DBless-mode
|
|
-- @function wait_for_all_config_update
|
|
-- @tparam[opt] table opts a table contains params
|
|
-- @tparam[opt=30] number opts.timeout maximum seconds to wait, defatuls is 30
|
|
-- @tparam[opt] number opts.admin_client_timeout to override the default timeout setting
|
|
-- @tparam[opt] number opts.forced_admin_port to override the default Admin API port
|
|
-- @tparam[opt] bollean opts.stream_enabled to enable stream module
|
|
-- @tparam[opt] number opts.proxy_client_timeout to override the default timeout setting
|
|
-- @tparam[opt] number opts.forced_proxy_port to override the default proxy port
|
|
-- @tparam[opt] number opts.stream_port to set the stream port
|
|
-- @tparam[opt] string opts.stream_ip to set the stream ip
|
|
-- @tparam[opt=false] boolean opts.override_global_rate_limiting_plugin to override the global rate-limiting plugin in waiting
|
|
-- @tparam[opt=false] boolean opts.override_global_key_auth_plugin to override the global key-auth plugin in waiting
|
|
local function wait_for_all_config_update(opts)
|
|
opts = opts or {}
|
|
if CONSTANTS.TEST_COVERAGE_MODE == "true" then
|
|
opts.timeout = CONSTANTS.TEST_COVERAGE_TIMEOUT
|
|
end
|
|
local timeout = opts.timeout or 30
|
|
local admin_client_timeout = opts.admin_client_timeout
|
|
local forced_admin_port = opts.forced_admin_port
|
|
local proxy_client_timeout = opts.proxy_client_timeout
|
|
local forced_proxy_port = opts.forced_proxy_port
|
|
local stream_port = opts.stream_port
|
|
local stream_ip = opts.stream_ip
|
|
local stream_enabled = opts.stream_enabled or false
|
|
local override_rl = opts.override_global_rate_limiting_plugin or false
|
|
local override_auth = opts.override_global_key_auth_plugin or false
|
|
local headers = opts.override_default_headers or { ["Content-Type"] = "application/json" }
|
|
local disable_ipv6 = opts.disable_ipv6 or false
|
|
|
|
local function call_admin_api(method, path, body, expected_status, headers)
|
|
local client = client.admin_client(admin_client_timeout, forced_admin_port)
|
|
|
|
local res
|
|
|
|
if string.upper(method) == "POST" then
|
|
res = client:post(path, {
|
|
headers = headers,
|
|
body = body,
|
|
})
|
|
|
|
elseif string.upper(method) == "DELETE" then
|
|
res = client:delete(path, {
|
|
headers = headers
|
|
})
|
|
end
|
|
|
|
local ok, json_or_nil_or_err = pcall(function ()
|
|
assert(res.status == expected_status, "unexpected response code: " .. res.status)
|
|
|
|
if string.upper(method) == "DELETE" then
|
|
return
|
|
end
|
|
|
|
local json = cjson.decode((res:read_body()))
|
|
assert(json ~= nil, "unexpected response body")
|
|
return json
|
|
end)
|
|
|
|
client:close()
|
|
|
|
assert(ok, json_or_nil_or_err)
|
|
|
|
return json_or_nil_or_err
|
|
end
|
|
|
|
local upstream_id, target_id, service_id, route_id
|
|
local stream_upstream_id, stream_target_id, stream_service_id, stream_route_id
|
|
local consumer_id, rl_plugin_id, key_auth_plugin_id, credential_id
|
|
local upstream_name = "really.really.really.really.really.really.really.mocking.upstream.test"
|
|
local service_name = "really-really-really-really-really-really-really-mocking-service"
|
|
local stream_upstream_name = "stream-really.really.really.really.really.really.really.mocking.upstream.test"
|
|
local stream_service_name = "stream-really-really-really-really-really-really-really-mocking-service"
|
|
local route_path = "/really-really-really-really-really-really-really-mocking-route"
|
|
local key_header_name = "really-really-really-really-really-really-really-mocking-key"
|
|
local consumer_name = "really-really-really-really-really-really-really-mocking-consumer"
|
|
local test_credentials = "really-really-really-really-really-really-really-mocking-credentials"
|
|
|
|
local host = "localhost"
|
|
local port = get_available_port()
|
|
|
|
local server = https_server.new(port, host, "http", nil, 1, nil, disable_ipv6)
|
|
|
|
server:start()
|
|
|
|
-- create mocking upstream
|
|
local res = assert(call_admin_api("POST",
|
|
"/upstreams",
|
|
{ name = upstream_name },
|
|
201, headers))
|
|
upstream_id = res.id
|
|
|
|
-- create mocking target to mocking upstream
|
|
res = assert(call_admin_api("POST",
|
|
string.format("/upstreams/%s/targets", upstream_id),
|
|
{ target = host .. ":" .. port },
|
|
201, headers))
|
|
target_id = res.id
|
|
|
|
-- create mocking service to mocking upstream
|
|
res = assert(call_admin_api("POST",
|
|
"/services",
|
|
{ name = service_name, url = "http://" .. upstream_name .. "/always_200" },
|
|
201, headers))
|
|
service_id = res.id
|
|
|
|
-- create mocking route to mocking service
|
|
res = assert(call_admin_api("POST",
|
|
string.format("/services/%s/routes", service_id),
|
|
{ paths = { route_path }, strip_path = true, path_handling = "v0",},
|
|
201, headers))
|
|
route_id = res.id
|
|
|
|
if override_rl then
|
|
-- create rate-limiting plugin to mocking mocking service
|
|
res = assert(call_admin_api("POST",
|
|
string.format("/services/%s/plugins", service_id),
|
|
{ name = "rate-limiting", config = { minute = 999999, policy = "local" } },
|
|
201, headers))
|
|
rl_plugin_id = res.id
|
|
end
|
|
|
|
if override_auth then
|
|
-- create key-auth plugin to mocking mocking service
|
|
res = assert(call_admin_api("POST",
|
|
string.format("/services/%s/plugins", service_id),
|
|
{ name = "key-auth", config = { key_names = { key_header_name } } },
|
|
201, headers))
|
|
key_auth_plugin_id = res.id
|
|
|
|
-- create consumer
|
|
res = assert(call_admin_api("POST",
|
|
"/consumers",
|
|
{ username = consumer_name },
|
|
201, headers))
|
|
consumer_id = res.id
|
|
|
|
-- create credential to key-auth plugin
|
|
res = assert(call_admin_api("POST",
|
|
string.format("/consumers/%s/key-auth", consumer_id),
|
|
{ key = test_credentials },
|
|
201, headers))
|
|
credential_id = res.id
|
|
end
|
|
|
|
if stream_enabled then
|
|
-- create mocking upstream
|
|
local res = assert(call_admin_api("POST",
|
|
"/upstreams",
|
|
{ name = stream_upstream_name },
|
|
201, headers))
|
|
stream_upstream_id = res.id
|
|
|
|
-- create mocking target to mocking upstream
|
|
res = assert(call_admin_api("POST",
|
|
string.format("/upstreams/%s/targets", stream_upstream_id),
|
|
{ target = host .. ":" .. port },
|
|
201, headers))
|
|
stream_target_id = res.id
|
|
|
|
-- create mocking service to mocking upstream
|
|
res = assert(call_admin_api("POST",
|
|
"/services",
|
|
{ name = stream_service_name, url = "tcp://" .. stream_upstream_name },
|
|
201, headers))
|
|
stream_service_id = res.id
|
|
|
|
-- create mocking route to mocking service
|
|
res = assert(call_admin_api("POST",
|
|
string.format("/services/%s/routes", stream_service_id),
|
|
{ destinations = { { port = stream_port }, }, protocols = { "tcp" },},
|
|
201, headers))
|
|
stream_route_id = res.id
|
|
end
|
|
|
|
local ok, err = pcall(function ()
|
|
-- wait for mocking route ready
|
|
pwait_until(function ()
|
|
local proxy = client.proxy_client(proxy_client_timeout, forced_proxy_port)
|
|
|
|
if override_auth then
|
|
res = proxy:get(route_path, { headers = { [key_header_name] = test_credentials } })
|
|
|
|
else
|
|
res = proxy:get(route_path)
|
|
end
|
|
|
|
local ok, err = pcall(assert, res.status == 200)
|
|
proxy:close()
|
|
assert(ok, err)
|
|
end, timeout / 2)
|
|
|
|
if stream_enabled then
|
|
pwait_until(function ()
|
|
local proxy = client.proxy_client(proxy_client_timeout, stream_port, stream_ip)
|
|
|
|
res = proxy:get("/always_200")
|
|
local ok, err = pcall(assert, res.status == 200)
|
|
proxy:close()
|
|
assert(ok, err)
|
|
end, timeout)
|
|
end
|
|
end)
|
|
if not ok then
|
|
server:shutdown()
|
|
error(err)
|
|
end
|
|
|
|
-- delete mocking configurations
|
|
if override_auth then
|
|
call_admin_api("DELETE", string.format("/consumers/%s/key-auth/%s", consumer_id, credential_id), nil, 204, headers)
|
|
call_admin_api("DELETE", string.format("/consumers/%s", consumer_id), nil, 204, headers)
|
|
call_admin_api("DELETE", "/plugins/" .. key_auth_plugin_id, nil, 204, headers)
|
|
end
|
|
|
|
if override_rl then
|
|
call_admin_api("DELETE", "/plugins/" .. rl_plugin_id, nil, 204, headers)
|
|
end
|
|
|
|
call_admin_api("DELETE", "/routes/" .. route_id, nil, 204, headers)
|
|
call_admin_api("DELETE", "/services/" .. service_id, nil, 204, headers)
|
|
call_admin_api("DELETE", string.format("/upstreams/%s/targets/%s", upstream_id, target_id), nil, 204, headers)
|
|
call_admin_api("DELETE", "/upstreams/" .. upstream_id, nil, 204, headers)
|
|
|
|
if stream_enabled then
|
|
call_admin_api("DELETE", "/routes/" .. stream_route_id, nil, 204, headers)
|
|
call_admin_api("DELETE", "/services/" .. stream_service_id, nil, 204, headers)
|
|
call_admin_api("DELETE", string.format("/upstreams/%s/targets/%s", stream_upstream_id, stream_target_id), nil, 204, headers)
|
|
call_admin_api("DELETE", "/upstreams/" .. stream_upstream_id, nil, 204, headers)
|
|
end
|
|
|
|
ok, err = pcall(function ()
|
|
-- wait for mocking configurations to be deleted
|
|
pwait_until(function ()
|
|
local proxy = client.proxy_client(proxy_client_timeout, forced_proxy_port)
|
|
res = proxy:get(route_path)
|
|
local ok, err = pcall(assert, res.status == 404)
|
|
proxy:close()
|
|
assert(ok, err)
|
|
end, timeout / 2)
|
|
end)
|
|
|
|
server:shutdown()
|
|
|
|
if not ok then
|
|
error(err)
|
|
end
|
|
|
|
end
|
|
|
|
|
|
--- Waits for a file to meet a certain condition
|
|
-- The check function will repeatedly be called (with a fixed interval), until
|
|
-- there is no Lua error occurred
|
|
--
|
|
-- NOTE: this is a regular Lua function, not a Luassert assertion.
|
|
-- @function wait_for_file
|
|
-- @tparam string mode one of:
|
|
--
|
|
-- "file", "directory", "link", "socket", "named pipe", "char device", "block device", "other"
|
|
--
|
|
-- @tparam string path the file path
|
|
-- @tparam[opt=10] number timeout maximum seconds to wait
|
|
local function wait_for_file(mode, path, timeout)
|
|
pwait_until(function()
|
|
local result, err = lfs.attributes(path, "mode")
|
|
local msg = string.format("failed to wait for the mode (%s) of '%s': %s",
|
|
mode, path, tostring(err))
|
|
assert(result == mode, msg)
|
|
end, timeout or 10)
|
|
end
|
|
|
|
|
|
local wait_for_file_contents
|
|
do
|
|
--- Wait until a file exists and is non-empty.
|
|
--
|
|
-- If, after the timeout is reached, the file does not exist, is not
|
|
-- readable, or is empty, an assertion error will be raised.
|
|
--
|
|
-- @function wait_for_file_contents
|
|
-- @param fname the filename to wait for
|
|
-- @param timeout (optional) maximum time to wait after which an error is
|
|
-- thrown, defaults to 10.
|
|
-- @return contents the file contents, as a string
|
|
function wait_for_file_contents(fname, timeout)
|
|
assert(type(fname) == "string",
|
|
"filename must be a string")
|
|
|
|
timeout = timeout or 10
|
|
assert(type(timeout) == "number" and timeout >= 0,
|
|
"timeout must be nil or a number >= 0")
|
|
|
|
local data = pl_file.read(fname)
|
|
if data and #data > 0 then
|
|
return data
|
|
end
|
|
|
|
pcall(wait_until, function()
|
|
data = pl_file.read(fname)
|
|
return data and #data > 0
|
|
end, timeout)
|
|
|
|
assert(data, "file (" .. fname .. ") does not exist or is not readable"
|
|
.. " after " .. tostring(timeout) .. " seconds")
|
|
|
|
assert(#data > 0, "file (" .. fname .. ") exists but is empty after " ..
|
|
tostring(timeout) .. " seconds")
|
|
|
|
return data
|
|
end
|
|
end
|
|
|
|
|
|
local function wait_until_no_common_workers(workers, expected_total)
|
|
wait_until(function()
|
|
local pok, admin_client = pcall(client.admin_client)
|
|
if not pok then
|
|
return false
|
|
end
|
|
local res = assert(admin_client:send {
|
|
method = "GET",
|
|
path = "/",
|
|
})
|
|
luassert.res_status(200, res)
|
|
local json = cjson.decode(luassert.res_status(200, res))
|
|
admin_client:close()
|
|
|
|
local new_workers = json.pids.workers
|
|
local total = 0
|
|
local common = 0
|
|
if new_workers then
|
|
for _, v in ipairs(new_workers) do
|
|
total = total + 1
|
|
for _, v_old in ipairs(workers) do
|
|
if v == v_old then
|
|
common = common + 1
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return common == 0 and total == (expected_total or total)
|
|
end, 30)
|
|
end
|
|
|
|
|
|
local function get_kong_workers(expected_total)
|
|
local workers
|
|
|
|
wait_until(function()
|
|
local pok, admin_client = pcall(client.admin_client)
|
|
if not pok then
|
|
return false
|
|
end
|
|
local res = admin_client:send {
|
|
method = "GET",
|
|
path = "/",
|
|
}
|
|
if not res or res.status ~= 200 then
|
|
return false
|
|
end
|
|
local body = luassert.res_status(200, res)
|
|
local json = cjson.decode(body)
|
|
|
|
admin_client:close()
|
|
|
|
workers = {}
|
|
|
|
for _, item in ipairs(json.pids.workers) do
|
|
if item ~= ngx.null then
|
|
table.insert(workers, item)
|
|
end
|
|
end
|
|
|
|
if expected_total and #workers ~= expected_total then
|
|
return nil, ("expected %s worker pids, got %s"):format(expected_total,
|
|
#workers)
|
|
|
|
elseif #workers == 0 then
|
|
return nil, "GET / returned no worker pids"
|
|
end
|
|
|
|
return true
|
|
end, 10)
|
|
return workers
|
|
end
|
|
|
|
|
|
--- Reload Kong and wait all workers are restarted.
|
|
local function reload_kong(...)
|
|
local workers = get_kong_workers()
|
|
local ok, err = shell.kong_exec(...)
|
|
if ok then
|
|
wait_until_no_common_workers(workers, 1)
|
|
end
|
|
return ok, err
|
|
end
|
|
|
|
|
|
return {
|
|
get_available_port = get_available_port,
|
|
|
|
wait_until = wait_until,
|
|
pwait_until = pwait_until,
|
|
wait_timer = wait_timer,
|
|
wait_for_invalidation = wait_for_invalidation,
|
|
wait_for_all_config_update = wait_for_all_config_update,
|
|
wait_for_file = wait_for_file,
|
|
wait_for_file_contents = wait_for_file_contents,
|
|
wait_until_no_common_workers = wait_until_no_common_workers,
|
|
|
|
get_kong_workers = get_kong_workers,
|
|
reload_kong = reload_kong,
|
|
}
|