1171 lines
38 KiB
Lua
1171 lines
38 KiB
Lua
local helpers = require "spec.helpers"
|
|
local cjson = require "cjson"
|
|
local uuid = require "kong.tools.uuid"
|
|
local tablex = require "pl.tablex"
|
|
|
|
local function it_content_types(title, fn)
|
|
local test_form_encoded = fn("application/x-www-form-urlencoded")
|
|
local test_multipart = fn("multipart/form-data")
|
|
local test_json = fn("application/json")
|
|
|
|
it(title .. " with application/www-form-urlencoded", test_form_encoded)
|
|
it(title .. " with multipart/form-data", test_multipart)
|
|
it(title .. " with application/json", test_json)
|
|
end
|
|
|
|
local function client_send(req)
|
|
local client = helpers.admin_client()
|
|
local res = assert(client:send(req))
|
|
local status, body = res.status, res:read_body()
|
|
client:close()
|
|
return status, body
|
|
end
|
|
|
|
for _, strategy in helpers.each_strategy() do
|
|
|
|
describe("Admin API #" .. strategy, function()
|
|
local bp
|
|
local client
|
|
local weight_default, weight_min, weight_max = 100, 0, 65535
|
|
local default_port = 8000
|
|
|
|
lazy_setup(function()
|
|
local fixtures = {
|
|
dns_mock = helpers.dns_mock.new({
|
|
mocks_only = true, -- don't fallback to "real" DNS
|
|
})
|
|
}
|
|
fixtures.dns_mock:A {
|
|
name = "custom_localhost",
|
|
address = "127.0.0.1",
|
|
}
|
|
|
|
bp = helpers.get_db_utils(strategy, {
|
|
"upstreams",
|
|
"targets",
|
|
})
|
|
assert(helpers.start_kong({
|
|
database = strategy,
|
|
nginx_conf = "spec/fixtures/custom_nginx.template",
|
|
}, nil, nil, fixtures))
|
|
end)
|
|
|
|
lazy_teardown(function()
|
|
assert(helpers.stop_kong())
|
|
end)
|
|
|
|
before_each(function()
|
|
client = assert(helpers.admin_client())
|
|
end)
|
|
|
|
after_each(function()
|
|
if client then client:close() end
|
|
end)
|
|
|
|
describe("/targets", function()
|
|
it("returns a 404", function()
|
|
local res = assert(client:send {
|
|
method = "GET",
|
|
path = "/targets"
|
|
})
|
|
assert.response(res).has.status(404)
|
|
local json = assert.response(res).has.jsonbody()
|
|
assert.equal("Not found", json.message)
|
|
end)
|
|
end)
|
|
|
|
describe("/upstreams/{upstream}/targets/", function()
|
|
describe("POST", function()
|
|
it_content_types("creates a target with defaults", function(content_type)
|
|
return function()
|
|
local upstream = bp.upstreams:insert { slots = 10 }
|
|
local res = assert(client:post("/upstreams/" .. upstream.name .. "/targets/", {
|
|
body = {
|
|
target = "konghq.test",
|
|
},
|
|
headers = {["Content-Type"] = content_type}
|
|
}))
|
|
assert.response(res).has.status(201)
|
|
local json = assert.response(res).has.jsonbody()
|
|
assert.equal("konghq.test:" .. default_port, json.target)
|
|
assert.is_number(json.created_at)
|
|
assert.is_string(json.id)
|
|
assert.are.equal(weight_default, json.weight)
|
|
end
|
|
end)
|
|
it_content_types("creates a target without defaults", function(content_type)
|
|
return function()
|
|
local upstream = bp.upstreams:insert { slots = 10 }
|
|
local res = assert(client:post("/upstreams/" .. upstream.name .. "/targets/", {
|
|
body = {
|
|
target = "konghq.test:123",
|
|
weight = 99,
|
|
},
|
|
headers = {["Content-Type"] = content_type}
|
|
}))
|
|
assert.response(res).has.status(201)
|
|
local json = assert.response(res).has.jsonbody()
|
|
assert.equal("konghq.test:123", json.target)
|
|
assert.is_number(json.created_at)
|
|
assert.is_string(json.id)
|
|
assert.are.equal(99, json.weight)
|
|
end
|
|
end)
|
|
|
|
it_content_types("creates a target with weight = 0", function(content_type)
|
|
return function()
|
|
local upstream = bp.upstreams:insert { slots = 10 }
|
|
local res = assert(client:post("/upstreams/" .. upstream.name .. "/targets/", {
|
|
body = {
|
|
target = "zero.weight.test:8080",
|
|
weight = 0,
|
|
},
|
|
headers = {["Content-Type"] = content_type}
|
|
}))
|
|
assert.response(res).has.status(201)
|
|
local json = assert.response(res).has.jsonbody()
|
|
assert.equal("zero.weight.test:8080", json.target)
|
|
assert.is_number(json.created_at)
|
|
assert.is_string(json.id)
|
|
assert.are.equal(0, json.weight)
|
|
|
|
-- added for testing #7699
|
|
local res2 = assert(client:get("/upstreams/" .. upstream.name .. "/targets/zero.weight.test:8080"))
|
|
assert.response(res2).has.status(200)
|
|
local json2 = assert.response(res2).has.jsonbody()
|
|
assert.same(json, json2)
|
|
end
|
|
end)
|
|
|
|
describe("errors", function()
|
|
it("handles malformed JSON body", function()
|
|
local upstream = bp.upstreams:insert { slots = 10 }
|
|
local res = assert(client:post("/upstreams/" .. upstream.name .. "/targets/", {
|
|
body = '{"hello": "world"',
|
|
headers = {["Content-Type"] = "application/json"}
|
|
}))
|
|
local body = assert.response(res).has.status(400)
|
|
local json = cjson.decode(body)
|
|
assert.same({ message = "Cannot parse JSON body" }, json)
|
|
end)
|
|
it_content_types("handles invalid input", function(content_type)
|
|
return function()
|
|
local upstream = bp.upstreams:insert { slots = 10 }
|
|
-- Missing parameter
|
|
local res = assert(client:post("/upstreams/" .. upstream.name .. "/targets/", {
|
|
body = {
|
|
weight = weight_min,
|
|
},
|
|
headers = {["Content-Type"] = content_type}
|
|
}))
|
|
local body = assert.response(res).has.status(400)
|
|
local json = cjson.decode(body)
|
|
assert.equal("schema violation", json.name)
|
|
assert.same({ target = "required field missing" }, json.fields)
|
|
|
|
-- Invalid target parameter
|
|
res = assert(client:post("/upstreams/" .. upstream.name .. "/targets/", {
|
|
body = {
|
|
target = "some invalid host name",
|
|
},
|
|
headers = {["Content-Type"] = content_type}
|
|
}))
|
|
body = assert.response(res).has.status(400)
|
|
local json = cjson.decode(body)
|
|
assert.equal("schema violation", json.name)
|
|
assert.same({ target = "Invalid target; not a valid hostname or ip address" }, json.fields)
|
|
|
|
-- Invalid weight parameter
|
|
res = assert(client:send {
|
|
method = "POST",
|
|
path = "/upstreams/" .. upstream.name .. "/targets/",
|
|
body = {
|
|
target = "konghq.test",
|
|
weight = weight_max + 1,
|
|
},
|
|
headers = {["Content-Type"] = content_type}
|
|
})
|
|
body = assert.response(res).has.status(400)
|
|
local json = cjson.decode(body)
|
|
assert.equal("schema violation", json.name)
|
|
assert.same({ weight = "value should be between 0 and " .. weight_max }, json.fields)
|
|
end
|
|
end)
|
|
|
|
for _, method in ipairs({"PUT", "PATCH", "DELETE"}) do
|
|
it_content_types("returns 405 on " .. method, function(content_type)
|
|
return function()
|
|
local upstream = bp.upstreams:insert { slots = 10 }
|
|
local res = assert(client:send {
|
|
method = method,
|
|
path = "/upstreams/" .. upstream.name .. "/targets/",
|
|
body = {
|
|
target = "konghq.test",
|
|
},
|
|
headers = {["Content-Type"] = content_type}
|
|
})
|
|
assert.response(res).has.status(405)
|
|
end
|
|
end)
|
|
end
|
|
|
|
it_content_types("fails to create duplicated targets", function(content_type)
|
|
return function()
|
|
local upstream = bp.upstreams:insert { slots = 10 }
|
|
local res = assert(client:post("/upstreams/" .. upstream.name .. "/targets/", {
|
|
body = {
|
|
target = "single-target.test:8080",
|
|
weight = 1,
|
|
},
|
|
headers = {["Content-Type"] = content_type}
|
|
}))
|
|
assert.response(res).has.status(201)
|
|
local json = assert.response(res).has.jsonbody()
|
|
assert.equal("single-target.test:8080", json.target)
|
|
assert.is_number(json.created_at)
|
|
assert.is_string(json.id)
|
|
assert.are.equal(1, json.weight)
|
|
|
|
local res = assert(client:post("/upstreams/" .. upstream.name .. "/targets/", {
|
|
body = {
|
|
target = "single-target.test:8080",
|
|
weight = 100,
|
|
},
|
|
headers = {["Content-Type"] = content_type}
|
|
}))
|
|
assert.response(res).has.status(409)
|
|
end
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
describe("GET", function()
|
|
local apis = {}
|
|
local api_map
|
|
|
|
local upstream
|
|
|
|
local function target_list_to_map(list)
|
|
local map = {}
|
|
for _, t in ipairs(list) do
|
|
map[t.target] = t
|
|
if t.tags == ngx.null then
|
|
t.tags = nil
|
|
end
|
|
end
|
|
return map
|
|
end
|
|
|
|
before_each(function()
|
|
upstream = bp.upstreams:insert {}
|
|
|
|
apis[1] = bp.targets:insert {
|
|
target = "api-1:80",
|
|
weight = 10,
|
|
upstream = { id = upstream.id },
|
|
}
|
|
apis[2] = bp.targets:insert {
|
|
target = "api-2:80",
|
|
weight = 0,
|
|
upstream = { id = upstream.id },
|
|
}
|
|
apis[3] = bp.targets:insert {
|
|
target = "api-3:80",
|
|
weight = 50,
|
|
upstream = { id = upstream.id },
|
|
}
|
|
apis[4] = bp.targets:insert {
|
|
target = "api-4:80",
|
|
weight = 10,
|
|
upstream = { id = upstream.id },
|
|
}
|
|
|
|
api_map = target_list_to_map(apis)
|
|
end)
|
|
|
|
for _, append in ipairs({ "", "/" }) do
|
|
it("shows all targets with " .. (append == "" and "no" or "") .. " ending slash", function()
|
|
local res = assert(client:send {
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.name .. "/targets" .. append,
|
|
})
|
|
assert.response(res).has.status(200)
|
|
local json = assert.response(res).has.jsonbody()
|
|
|
|
-- we got four active targets for this upstream
|
|
assert.equal(4, #json.data)
|
|
|
|
assert.same(api_map, target_list_to_map(json.data))
|
|
end)
|
|
end
|
|
|
|
describe("empty results", function()
|
|
it("data property is an empty array", function()
|
|
local empty_upstream = bp.upstreams:insert {}
|
|
local res = assert(client:send {
|
|
method = "GET",
|
|
path = "/upstreams/" .. empty_upstream.name .. "/targets",
|
|
})
|
|
local body = assert.response(res).has.status(200)
|
|
assert.match('"data":%[%]', body)
|
|
end)
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
describe("/upstreams/{upstream}/health/", function()
|
|
|
|
describe("GET", function()
|
|
local upstream
|
|
local node_id
|
|
|
|
local function add_targets(target_fmt)
|
|
local targets = {}
|
|
local weights = { 10, 10, 10, 10 }
|
|
|
|
for i = 1, #weights do
|
|
local status, body = client_send({
|
|
method = "POST",
|
|
path = "/upstreams/" .. upstream.name .. "/targets",
|
|
headers = {
|
|
["Content-Type"] = "application/json",
|
|
},
|
|
body = {
|
|
target = string.format(target_fmt, i),
|
|
weight = weights[i],
|
|
}
|
|
})
|
|
assert.same(201, status)
|
|
targets[i] = assert(cjson.decode(body))
|
|
end
|
|
return targets
|
|
end
|
|
|
|
-- Performs tests similar to /upstreams/:upstream_id/targets,
|
|
-- and checks for the "health" field of each target.
|
|
-- @param targets the array of target data produced by add_targets
|
|
-- @param n the expected number of targets in the response
|
|
-- It is different from #targets because add_targets adds
|
|
-- zero-weight targets as well.
|
|
-- @param health the expected "health" response for all targets
|
|
local function check_health_endpoint(targets, n, health)
|
|
for _, append in ipairs({ "", "/" }) do
|
|
local status, body = client_send({
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.name .. "/health" .. append,
|
|
})
|
|
assert.same(200, status)
|
|
local res = assert(cjson.decode(body))
|
|
|
|
assert.same(node_id, res.node_id)
|
|
assert.equal(n, #res.data)
|
|
|
|
-- when multiple active targets are present, we only see the last one
|
|
assert.equal(targets[4].id, res.data[1].id)
|
|
|
|
-- validate the remaining returned targets
|
|
-- note the backwards order, because we walked the targets backwards
|
|
assert.equal(targets[3].target, res.data[2].target)
|
|
assert.equal(targets[2].target, res.data[3].target)
|
|
for i = 1, n do
|
|
if res.data[i].data ~= nil and res.data[i].data.addresses ~= nil then
|
|
for j = 1, #res.data[i].data.addresses do
|
|
assert.equal(health, res.data[i].data.addresses[j].health)
|
|
end
|
|
assert.equal(health, res.data[i].health)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
lazy_setup(function()
|
|
local status, body = client_send({
|
|
method = "GET",
|
|
path = "/",
|
|
})
|
|
assert.same(200, status)
|
|
local res = assert(cjson.decode(body))
|
|
assert.string(res.node_id)
|
|
node_id = res.node_id
|
|
end)
|
|
|
|
before_each(function()
|
|
local any_upstream = bp.upstreams:insert {}
|
|
local status, body = client_send({
|
|
method = "POST",
|
|
path = "/upstreams",
|
|
headers = {
|
|
["Content-Type"] = "application/json",
|
|
},
|
|
body = {
|
|
name = any_upstream.name .. "-health",
|
|
}
|
|
})
|
|
assert.same(201, status)
|
|
upstream = assert(cjson.decode(body))
|
|
end)
|
|
|
|
describe("with healthchecks off", function()
|
|
it("returns HEALTHCHECKS_OFF for targets that resolve", function()
|
|
add_targets("127.0.0.1:8%d")
|
|
local targets = add_targets("custom_localhost:8%d")
|
|
check_health_endpoint(targets, 8, "HEALTHCHECKS_OFF")
|
|
end)
|
|
|
|
it("returns DNS_ERROR if DNS cannot be resolved", function()
|
|
local targets = add_targets("bad-health-target-%d:80")
|
|
|
|
check_health_endpoint(targets, 4, "DNS_ERROR")
|
|
end)
|
|
end)
|
|
|
|
describe("with healthchecks on", function()
|
|
local checked
|
|
before_each(function()
|
|
if checked == nil then
|
|
ngx.sleep(1)
|
|
end
|
|
local status, body = client_send({
|
|
method = "PATCH",
|
|
path = "/upstreams/" .. upstream.name,
|
|
headers = {
|
|
["Content-Type"] = "application/json",
|
|
},
|
|
body = {
|
|
healthchecks = {
|
|
passive = {
|
|
healthy = {
|
|
successes = 1,
|
|
},
|
|
unhealthy = {
|
|
tcp_failures = 1,
|
|
http_failures = 1,
|
|
timeouts = 1,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
})
|
|
assert.same(200, status)
|
|
if checked == nil then
|
|
local json = assert(cjson.decode(body))
|
|
assert.truthy(upstream.updated_at < json.updated_at)
|
|
checked = true
|
|
end
|
|
end)
|
|
|
|
it("returns DNS_ERROR if DNS cannot be resolved", function()
|
|
|
|
local targets = add_targets("bad-target-%d:80")
|
|
|
|
check_health_endpoint(targets, 4, "DNS_ERROR")
|
|
|
|
end)
|
|
|
|
it("returns HEALTHY if failure not detected", function()
|
|
|
|
local targets = add_targets("custom_localhost:222%d")
|
|
|
|
check_health_endpoint(targets, 4, "HEALTHY")
|
|
|
|
end)
|
|
|
|
it("returns UNHEALTHY if failure detected", function()
|
|
|
|
local targets = add_targets("custom_localhost:222%d")
|
|
|
|
local status = client_send({
|
|
method = "PUT",
|
|
path = "/upstreams/" .. upstream.name,
|
|
headers = {
|
|
["Content-Type"] = "application/json",
|
|
},
|
|
body = {
|
|
healthchecks = {
|
|
active = {
|
|
healthy = {
|
|
interval = 1,
|
|
},
|
|
unhealthy = {
|
|
interval = 1,
|
|
tcp_failures = 1,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
})
|
|
assert.same(200, status)
|
|
|
|
helpers.pwait_until(function()
|
|
check_health_endpoint(targets, 4, "UNHEALTHY")
|
|
end, 15)
|
|
|
|
end)
|
|
|
|
it("returns HEALTHCHECKS_OFF for target with weight 0", function ()
|
|
local status, _ = client_send({
|
|
method = "POST",
|
|
path = "/upstreams/" .. upstream.name .. "/targets",
|
|
headers = {
|
|
["Content-Type"] = "application/json",
|
|
},
|
|
body = {
|
|
target = "custom_localhost:2221",
|
|
weight = 0,
|
|
}
|
|
})
|
|
assert.same(201, status)
|
|
|
|
helpers.pwait_until(function ()
|
|
local status, body = client_send({
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.name .. "/health",
|
|
})
|
|
assert.same(200, status)
|
|
local res = assert(cjson.decode(body))
|
|
local function check_health_addresses(addresses, health)
|
|
for i=1, #addresses do
|
|
assert.same(health, addresses[i].health)
|
|
end
|
|
end
|
|
assert.equal(1, #res.data)
|
|
check_health_addresses(res.data[1].data.addresses, "HEALTHCHECKS_OFF")
|
|
end, 15)
|
|
|
|
end)
|
|
|
|
local function check_health_addresses(addresses, health)
|
|
for i=1, #addresses do
|
|
assert.same(health, addresses[i].health)
|
|
end
|
|
end
|
|
|
|
it("returns HEALTHCHECKS_OFF for target with weight 0", function ()
|
|
local status, _ = client_send({
|
|
method = "POST",
|
|
path = "/upstreams/" .. upstream.name .. "/targets",
|
|
headers = {
|
|
["Content-Type"] = "application/json",
|
|
},
|
|
body = {
|
|
target = "custom_localhost:2221",
|
|
weight = 0,
|
|
}
|
|
})
|
|
assert.same(201, status)
|
|
|
|
-- Give time for weight modification to kick in
|
|
ngx.sleep(0.3)
|
|
|
|
local status, body = client_send({
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.name .. "/health",
|
|
})
|
|
assert.same(200, status)
|
|
local res = assert(cjson.decode(body))
|
|
assert.equal(1, #res.data)
|
|
check_health_addresses(res.data[1].data.addresses, "HEALTHCHECKS_OFF")
|
|
end)
|
|
|
|
it("return HEALTHY if weight of target turns from zero to non-zero", function ()
|
|
local status, _ = client_send({
|
|
method = "POST",
|
|
path = "/upstreams/" .. upstream.name .. "/targets",
|
|
headers = {
|
|
["Content-Type"] = "application/json",
|
|
},
|
|
body = {
|
|
target = "custom_localhost:2221",
|
|
weight = 0,
|
|
}
|
|
})
|
|
assert.same(201, status)
|
|
|
|
-- Give time for weight modification to kick in
|
|
ngx.sleep(0.3)
|
|
|
|
local status, body = client_send({
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.name .. "/health",
|
|
})
|
|
assert.same(200, status)
|
|
local res = assert(cjson.decode(body))
|
|
assert.equal(1, #res.data)
|
|
check_health_addresses(res.data[1].data.addresses, "HEALTHCHECKS_OFF")
|
|
|
|
local status = client_send({
|
|
method = "PATCH",
|
|
path = "/upstreams/" .. upstream.name .. "/targets/custom_localhost:2221",
|
|
headers = {
|
|
["Content-Type"] = "application/json",
|
|
},
|
|
body = {
|
|
-- target = "custom_localhost:2221",
|
|
weight = 10,
|
|
},
|
|
})
|
|
assert.same(200, status)
|
|
|
|
-- Give time for weight modification to kick in
|
|
ngx.sleep(0.3)
|
|
|
|
local status, body = client_send({
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.name .. "/health",
|
|
})
|
|
assert.same(200, status)
|
|
local res = assert(cjson.decode(body))
|
|
assert.equal(1, #res.data)
|
|
check_health_addresses(res.data[1].data.addresses, "HEALTHY")
|
|
end)
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
describe("/upstreams/{upstream}/targets/all/", function()
|
|
describe("GET", function()
|
|
local upstream
|
|
before_each(function()
|
|
upstream = bp.upstreams:insert {}
|
|
for i = 1, 10 do
|
|
bp.targets:insert {
|
|
target = "api-" .. i .. ":80",
|
|
weight = 100,
|
|
upstream = { id = upstream.id },
|
|
}
|
|
end
|
|
end)
|
|
|
|
it("retrieves the first page", function()
|
|
local res = assert(client:send {
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.name .. "/targets/all",
|
|
})
|
|
assert.response(res).has.status(200)
|
|
local json = assert.response(res).has.jsonbody()
|
|
assert.equal(10, #json.data)
|
|
end)
|
|
it("offset is a string", function()
|
|
local res = assert(client:send {
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.name .. "/targets/all",
|
|
query = {size = 3},
|
|
})
|
|
assert.response(res).has.status(200)
|
|
local json = assert.response(res).has.jsonbody()
|
|
assert.is_string(json.offset)
|
|
end)
|
|
it("next url ends with /targets/all", function()
|
|
local res = assert(client:send {
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.name .. "/targets/all",
|
|
query = {size = 3},
|
|
})
|
|
assert.response(res).has.status(200)
|
|
local json = assert.response(res).has.jsonbody()
|
|
assert.equals("/upstreams/" .. upstream.name .. "/targets/all?offset=" .. ngx.escape_uri(json.offset), json.next)
|
|
end)
|
|
it("paginates a set", function()
|
|
local pages = {}
|
|
local offset
|
|
|
|
for i = 1, 4 do
|
|
local res = assert(client:send {
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.name .. "/targets/all",
|
|
query = {size = 3, offset = offset}
|
|
})
|
|
assert.response(res).has.status(200)
|
|
local json = assert.response(res).has.jsonbody()
|
|
|
|
if i < 4 then
|
|
assert.equal(3, #json.data)
|
|
else
|
|
assert.equal(1, #json.data)
|
|
end
|
|
|
|
if i > 1 then
|
|
-- check all pages are different
|
|
assert.not_same(pages[i-1], json)
|
|
end
|
|
|
|
offset = json.offset
|
|
pages[i] = json
|
|
end
|
|
end)
|
|
it("ignores filters", function()
|
|
local res = assert(client:send {
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.name .. "/targets/all",
|
|
query = {foo = "bar"},
|
|
})
|
|
assert.response(res).has.status(200)
|
|
end)
|
|
it("ignores an invalid body", function()
|
|
local res = assert(client:send {
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.name .. "/targets/all",
|
|
body = "this fails if decoded as json",
|
|
headers = {
|
|
["Content-Type"] = "application/json",
|
|
}
|
|
})
|
|
assert.response(res).has.status(200)
|
|
end)
|
|
|
|
describe("empty results", function()
|
|
it("data property is an empty array", function()
|
|
local empty_upstream = bp.upstreams:insert {}
|
|
local res = assert(client:send {
|
|
method = "GET",
|
|
path = "/upstreams/" .. empty_upstream.name .. "/targets/all",
|
|
})
|
|
local body = assert.response(res).has.status(200)
|
|
local json = cjson.decode(body)
|
|
assert.same({
|
|
data = {},
|
|
next = ngx.null,
|
|
}, json)
|
|
-- ensure JSON representation is correct
|
|
assert.match('"data":%[%]', body)
|
|
end)
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
describe("/upstreams/{upstream}/targets/{target}/(un)healthy", function()
|
|
|
|
local localhosts = {
|
|
ipv4 = "127.0.0.1",
|
|
ipv6 = "[0000:0000:0000:0000:0000:0000:0000:0001]",
|
|
hostname = "localhost",
|
|
}
|
|
for mode, localhost in pairs(localhosts) do
|
|
|
|
describe("POST #" .. mode, function()
|
|
local upstream
|
|
local target_path
|
|
local target
|
|
local wrong_target
|
|
|
|
lazy_setup(function()
|
|
local my_target_name = localhost .. ":8192"
|
|
|
|
wrong_target = bp.targets:insert {
|
|
target = my_target_name,
|
|
weight = 10
|
|
}
|
|
|
|
upstream = bp.upstreams:insert {}
|
|
local status, body = assert(client_send({
|
|
method = "PATCH",
|
|
path = "/upstreams/" .. upstream.id,
|
|
headers = {["Content-Type"] = "application/json"},
|
|
body = {
|
|
healthchecks = {
|
|
passive = {
|
|
healthy = {
|
|
successes = 1,
|
|
},
|
|
unhealthy = {
|
|
tcp_failures = 1,
|
|
http_failures = 1,
|
|
timeouts = 1,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}))
|
|
assert.same(200, status, body)
|
|
local json = assert(cjson.decode(body))
|
|
|
|
status, body = assert(client_send({
|
|
method = "POST",
|
|
path = "/upstreams/" .. upstream.id .. "/targets",
|
|
headers = {["Content-Type"] = "application/json"},
|
|
body = {
|
|
target = my_target_name,
|
|
weight = 10,
|
|
upstream = { id = json.id },
|
|
}
|
|
}))
|
|
assert.same(201, status)
|
|
target = assert(cjson.decode(body))
|
|
assert.same(my_target_name, target.target)
|
|
|
|
target_path = "/upstreams/" .. upstream.id .. "/targets/" .. target.target
|
|
end)
|
|
|
|
it("checks every combination of valid and invalid upstream and target", function()
|
|
for i, u in ipairs({ uuid.uuid(), "invalid", upstream.name, upstream.id }) do
|
|
for j, t in ipairs({ uuid.uuid(), "invalid:1234", wrong_target.id, target.target, target.id }) do
|
|
for _, e in ipairs({ "healthy", "unhealthy" }) do
|
|
local expected = (i >= 3 and j >= 4) and 204 or 404
|
|
local path = "/upstreams/" .. u .. "/targets/" .. t .. "/" .. e
|
|
local status = assert(client_send {
|
|
method = "PUT",
|
|
path = "/upstreams/" .. u .. "/targets/" .. t .. "/" .. e
|
|
})
|
|
assert.same(expected, status, "bad status for path " .. path)
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
it("flips the target status from UNHEALTHY to HEALTHY", function()
|
|
local status, body, json
|
|
|
|
status, body = assert(client_send {
|
|
method = "PUT",
|
|
path = target_path .. "/unhealthy"
|
|
})
|
|
assert.same(204, status, body)
|
|
|
|
helpers.pwait_until(function()
|
|
status, body = assert(client_send {
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.id .. "/health"
|
|
})
|
|
|
|
assert.same(200, status)
|
|
json = assert(cjson.decode(body))
|
|
assert.same(target.target, json.data[1].target)
|
|
assert.same("UNHEALTHY", json.data[1].health)
|
|
end, 15)
|
|
|
|
status = assert(client_send {
|
|
method = "PUT",
|
|
path = target_path .. "/healthy"
|
|
})
|
|
assert.same(204, status)
|
|
|
|
helpers.pwait_until(function()
|
|
status, body = assert(client_send {
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.id .. "/health"
|
|
})
|
|
|
|
assert.same(200, status)
|
|
json = assert(cjson.decode(body))
|
|
assert.same(target.target, json.data[1].target)
|
|
assert.same("HEALTHY", json.data[1].health)
|
|
end, 15)
|
|
|
|
end)
|
|
|
|
it("flips the target status from HEALTHY to UNHEALTHY", function()
|
|
local status, body, json
|
|
|
|
status = assert(client_send {
|
|
method = "PUT",
|
|
path = target_path .. "/healthy"
|
|
})
|
|
assert.same(204, status)
|
|
|
|
helpers.pwait_until(function ()
|
|
status, body = assert(client_send {
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.id .. "/health"
|
|
})
|
|
|
|
assert.same(200, status)
|
|
json = assert(cjson.decode(body))
|
|
assert.same(target.target, json.data[1].target)
|
|
assert.same("HEALTHY", json.data[1].health)
|
|
end, 15)
|
|
|
|
status = assert(client_send {
|
|
method = "PUT",
|
|
path = target_path .. "/unhealthy"
|
|
})
|
|
assert.same(204, status)
|
|
|
|
helpers.pwait_until(function ()
|
|
status, body = assert(client_send {
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.id .. "/health"
|
|
})
|
|
|
|
assert.same(200, status)
|
|
json = assert(cjson.decode(body))
|
|
assert.same(target.target, json.data[1].target)
|
|
assert.same("UNHEALTHY", json.data[1].health)
|
|
end, 15)
|
|
|
|
end)
|
|
end)
|
|
end
|
|
end)
|
|
|
|
describe("/upstreams/{upstream}/targets/{target}", function()
|
|
describe("GET", function()
|
|
local target
|
|
local upstream
|
|
|
|
before_each(function()
|
|
upstream = bp.upstreams:insert {}
|
|
|
|
bp.targets:insert {
|
|
target = "api-1:80",
|
|
weight = 10,
|
|
upstream = { id = upstream.id },
|
|
}
|
|
|
|
target = bp.targets:insert {
|
|
target = "api-2:80",
|
|
weight = 10,
|
|
upstream = { id = upstream.id },
|
|
}
|
|
end)
|
|
|
|
it("returns target entity", function()
|
|
local res = client:get("/upstreams/" .. upstream.name .. "/targets/" .. target.target)
|
|
assert.response(res).has.status(200)
|
|
local json = assert.response(res).has.jsonbody()
|
|
json.tags = nil
|
|
json.updated_at = nil
|
|
target.updated_at = nil
|
|
assert.same(target, json)
|
|
end)
|
|
end)
|
|
|
|
describe("PATCH", function()
|
|
local target
|
|
local upstream
|
|
|
|
before_each(function()
|
|
upstream = bp.upstreams:insert {}
|
|
|
|
bp.targets:insert {
|
|
target = "api-1:80",
|
|
weight = 10,
|
|
upstream = { id = upstream.id },
|
|
}
|
|
|
|
-- predefine the target to mock delete
|
|
target = bp.targets:insert {
|
|
target = "api-2:80",
|
|
weight = 10,
|
|
upstream = { id = upstream.id },
|
|
}
|
|
end)
|
|
|
|
it("is allowed and works", function()
|
|
local admin_client = assert(helpers.admin_client())
|
|
local res
|
|
assert
|
|
.with_timeout(10)
|
|
.ignore_exceptions(true)
|
|
.eventually(function()
|
|
res = admin_client:patch("/upstreams/" .. upstream.name .. "/targets/" .. target.target, {
|
|
body = {
|
|
weight = 659,
|
|
},
|
|
headers = { ["Content-Type"] = "application/json" }
|
|
})
|
|
assert.response(res).has.status(200)
|
|
end)
|
|
.has_no_error()
|
|
local json = assert.response(res).has.jsonbody()
|
|
assert.is_string(json.id)
|
|
assert.are.equal(target.target, json.target)
|
|
assert.are.equal(659, json.weight)
|
|
assert.truthy(target.updated_at < json.updated_at)
|
|
admin_client:close()
|
|
|
|
local res = assert(client:send {
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.name .. "/targets/" .. target.target,
|
|
})
|
|
assert.response(res).has.status(200)
|
|
json = assert.response(res).has.jsonbody()
|
|
assert.is_string(json.id)
|
|
assert.are.equal(659, json.weight)
|
|
|
|
end)
|
|
end)
|
|
|
|
describe("PUT", function()
|
|
local target
|
|
local upstream
|
|
|
|
before_each(function()
|
|
upstream = bp.upstreams:insert {}
|
|
|
|
bp.targets:insert {
|
|
target = "api-1:80",
|
|
weight = 10,
|
|
upstream = { id = upstream.id },
|
|
}
|
|
|
|
target = bp.targets:insert {
|
|
target = "api-2:80",
|
|
weight = 10,
|
|
upstream = { id = upstream.id },
|
|
}
|
|
end)
|
|
|
|
it("updates target (by id)", function()
|
|
-- update the target port
|
|
target.target = target.target .. "1"
|
|
local res = client:put("/upstreams/" .. upstream.name .. "/targets/" .. target.id, {
|
|
body = {
|
|
target = target.target
|
|
},
|
|
headers = { ["Content-Type"] = "application/json" }
|
|
})
|
|
|
|
assert.response(res).has.status(200)
|
|
res = client:get("/upstreams/" .. upstream.name .. "/targets/" .. target.id)
|
|
assert.response(res).has.status(200)
|
|
local json = assert.response(res).has.jsonbody()
|
|
json.tags = nil
|
|
json.updated_at = nil
|
|
target.updated_at = nil
|
|
assert.same(target, json)
|
|
end)
|
|
|
|
it("updates target (by target)", function()
|
|
local res = client:put("/upstreams/" .. upstream.name .. "/targets/" .. target.target, {
|
|
body = {
|
|
-- update the target port
|
|
target = target.target .. "1"
|
|
},
|
|
headers = { ["Content-Type"] = "application/json" }
|
|
})
|
|
assert.response(res).has.status(200)
|
|
local tgt = assert.response(res).has.jsonbody()
|
|
|
|
-- the previous one should not exist now
|
|
res = client:get("/upstreams/" .. upstream.name .. "/targets/" .. target.target)
|
|
assert.response(res).has.status(404)
|
|
|
|
-- now check the updated one
|
|
target.target = target.target .. "1"
|
|
res = client:get("/upstreams/" .. upstream.name .. "/targets/" .. target.target)
|
|
assert.response(res).has.status(200)
|
|
local json = assert.response(res).has.jsonbody()
|
|
assert.same(tgt, json)
|
|
end)
|
|
end)
|
|
|
|
describe("DELETE", function()
|
|
local target
|
|
|
|
local upstream
|
|
before_each(function()
|
|
upstream = bp.upstreams:insert {}
|
|
|
|
bp.targets:insert {
|
|
target = "api-1:80",
|
|
weight = 10,
|
|
upstream = { id = upstream.id },
|
|
}
|
|
|
|
-- predefine the target to mock delete
|
|
target = bp.targets:insert {
|
|
target = "api-2:80",
|
|
weight = 10,
|
|
upstream = { id = upstream.id },
|
|
}
|
|
end)
|
|
|
|
it("method DELETE actually deletes targets (by target)", function()
|
|
local targets = assert(client:send {
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.name .. "/targets/all",
|
|
})
|
|
assert.response(targets).has.status(200)
|
|
local json = assert.response(targets).has.jsonbody()
|
|
assert.equal(2, #json.data)
|
|
|
|
local res = assert(client:send {
|
|
method = "DELETE",
|
|
path = "/upstreams/" .. upstream.name .. "/targets/" .. target.target
|
|
})
|
|
assert.response(res).has.status(204)
|
|
|
|
local targets = assert(client:send {
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.name .. "/targets/all",
|
|
})
|
|
assert.response(targets).has.status(200)
|
|
local json = assert.response(targets).has.jsonbody()
|
|
assert.equal(1, #json.data)
|
|
|
|
local active = assert(client:send {
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.name .. "/targets",
|
|
})
|
|
assert.response(active).has.status(200)
|
|
json = assert.response(active).has.jsonbody()
|
|
assert.equal(1, #json.data)
|
|
assert.equal("api-1:80", json.data[1].target)
|
|
end)
|
|
|
|
it("method DELETE actually deletes targets (by id)", function()
|
|
local targets = assert(client:send {
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.name .. "/targets/all",
|
|
})
|
|
assert.response(targets).has.status(200)
|
|
local json = assert.response(targets).has.jsonbody()
|
|
assert.equal(2, #json.data)
|
|
|
|
local res = assert(client:send {
|
|
method = "DELETE",
|
|
path = "/upstreams/" .. upstream.name .. "/targets/" .. target.id
|
|
})
|
|
assert.response(res).has.status(204)
|
|
|
|
local targets = assert(client:send {
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.name .. "/targets/all",
|
|
})
|
|
assert.response(targets).has.status(200)
|
|
local json = assert.response(targets).has.jsonbody()
|
|
assert.equal(1, #json.data)
|
|
|
|
local active = assert(client:send {
|
|
method = "GET",
|
|
path = "/upstreams/" .. upstream.name .. "/targets",
|
|
})
|
|
assert.response(active).has.status(200)
|
|
json = assert.response(active).has.jsonbody()
|
|
assert.equal(1, #json.data)
|
|
assert.equal("api-1:80", json.data[1].target)
|
|
end)
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
|
|
describe("/upstreams/{upstream}/targets/{target}/(un)healthy not available in hybrid mode", function()
|
|
lazy_setup(function()
|
|
assert(helpers.start_kong({
|
|
role = "control_plane",
|
|
cluster_cert = "spec/fixtures/kong_clustering.crt",
|
|
cluster_cert_key = "spec/fixtures/kong_clustering.key",
|
|
database = strategy,
|
|
}))
|
|
end)
|
|
|
|
lazy_teardown(function()
|
|
assert(helpers.stop_kong())
|
|
end)
|
|
|
|
it("healthcheck endpoints not included in /endpoints", function()
|
|
local admin_client = assert(helpers.admin_client())
|
|
|
|
local res = admin_client:get("/endpoints")
|
|
local body = assert.res_status(200, res)
|
|
local json = cjson.decode(body)
|
|
assert.is_nil(tablex.find(json.data, '/upstreams/{upstreams}/targets/{targets}/healthy'))
|
|
assert.is_nil(tablex.find(json.data, '/upstreams/{upstreams}/targets/{targets}/unhealthy'))
|
|
assert.is_nil(tablex.find(json.data, '/upstreams/{upstreams}/targets/{targets}/{address}/healthy'))
|
|
assert.is_nil(tablex.find(json.data, '/upstreams/{upstreams}/targets/{targets}/{address}/unhealthy'))
|
|
end)
|
|
end)
|
|
|
|
end
|