kong/spec/01-unit/09-balancer/03-consistent_hashing_spec.lua (969 lines of code) (raw):
assert:set_parameter("TableFormatLevel", 5) -- when displaying tables, set a bigger default depth
------------------------
-- START TEST HELPERS --
------------------------
local client
local targets, balancers
local dns_utils = require "kong.resty.dns.utils"
local mocker = require "spec.fixtures.mocker"
local utils = require "kong.tools.utils"
local ws_id = utils.uuid()
local helpers = require "spec.helpers.dns"
local gettime = helpers.gettime
local sleep = helpers.sleep
local dnsSRV = function(...) return helpers.dnsSRV(client, ...) end
local dnsA = function(...) return helpers.dnsA(client, ...) end
local dnsAAAA = function(...) return helpers.dnsAAAA(client, ...) end
local unset_register = {}
local function setup_block(consistency)
local cache_table = {}
local function mock_cache()
return {
safe_set = function(self, k, v)
cache_table[k] = v
return true
end,
get = function(self, k, _, fn, arg)
if cache_table[k] == nil then
cache_table[k] = fn(arg)
end
return cache_table[k]
end,
}
end
local function register_unsettter(f)
table.insert(unset_register, f)
end
mocker.setup(register_unsettter, {
kong = {
configuration = {
worker_consistency = consistency,
worker_state_update_frequency = 0.1,
},
core_cache = mock_cache(cache_table),
},
ngx = {
ctx = {
workspace = ws_id,
}
}
})
end
local function unsetup_block()
for _, f in ipairs(unset_register) do
f()
end
end
local function add_target(b, name, port, weight)
-- adding again changes weight
for _, prev_target in ipairs(b.targets) do
if prev_target.name == name and prev_target.port == port then
local entry = {port = port}
for _, addr in ipairs(prev_target.addresses) do
entry.address = addr.ip
b:changeWeight(prev_target, entry, weight)
end
prev_target.weight = weight
return prev_target
end
end
if type(name) == "table" then
local entry = name
name = entry.name or entry[1]
port = entry.port or entry[2]
weight = entry.weight or entry[3]
end
local target = {
upstream = b.upstream_id,
balancer = b,
name = name,
nameType = dns_utils.hostnameType(name),
addresses = {},
port = port or 80,
weight = weight or 100,
totalWeight = 0,
unavailableWeight = 0,
}
table.insert(b.targets, target)
targets.resolve_targets(b.targets)
return target
end
local upstream_index = 0
local function new_balancer(opts)
upstream_index = upstream_index + 1
local upname="upstream_" .. upstream_index
local hc_defaults = {
active = {
timeout = 1,
concurrency = 10,
http_path = "/",
healthy = {
interval = 0, -- 0 = probing disabled by default
http_statuses = { 200, 302 },
successes = 0, -- 0 = disabled by default
},
unhealthy = {
interval = 0, -- 0 = probing disabled by default
http_statuses = { 429, 404,
500, 501, 502, 503, 504, 505 },
tcp_failures = 0, -- 0 = disabled by default
timeouts = 0, -- 0 = disabled by default
http_failures = 0, -- 0 = disabled by default
},
},
passive = {
healthy = {
http_statuses = { 200, 201, 202, 203, 204, 205, 206, 207, 208, 226,
300, 301, 302, 303, 304, 305, 306, 307, 308 },
successes = 0,
},
unhealthy = {
http_statuses = { 429, 500, 503 },
tcp_failures = 0, -- 0 = circuit-breaker disabled by default
timeouts = 0, -- 0 = circuit-breaker disabled by default
http_failures = 0, -- 0 = circuit-breaker disabled by default
},
},
}
local my_upstream = { id=upname, name=upname, ws_id=ws_id, slots=opts.wheelSize or 10, healthchecks=hc_defaults, algorithm="consistent-hashing" }
local b = (balancers.create_balancer(my_upstream, true))
for k, v in pairs{
wheelSize = opts.wheelSize,
requeryInterval = opts.requery,
ttl0Interval = opts.ttl0,
} do
b[k] = v
end
if opts.callback then
b:setCallback(opts.callback)
end
for _, target in ipairs(opts.hosts or {}) do
add_target(b, target)
end
return b
end
-- creates a hash table with "address:port" keys and as value the number of indices
local function count_indices(b)
local r = {}
local continuum = b.algorithm.continuum
for _, address in pairs(continuum) do
local key = tostring(address.ip)
if key:find(":",1,true) then
--print("available: ", address.available)
key = "["..key.."]:"..address.port
else
key = key..":"..address.port
end
r[key] = (r[key] or 0) + 1
end
return r
end
-- copies the wheel to a list with ip, port and hostname in the field values.
-- can be used for before/after comparison
local copyWheel = function(b)
local copy = {}
local continuum = b.algorithm.continuum
for i, address in pairs(continuum) do
copy[i] = i.." - "..address.ip.." @ "..address.port.." ("..address.target.name..")"
end
return copy
end
----------------------
-- END TEST HELPERS --
----------------------
describe("[consistent_hashing]", function()
local snapshot
setup(function()
_G.package.loaded["kong.resty.dns.client"] = nil -- make sure module is reloaded
_G.package.loaded["kong.runloop.balancer.targets"] = nil -- make sure module is reloaded
client = require "kong.resty.dns.client"
targets = require "kong.runloop.balancer.targets"
balancers = require "kong.runloop.balancer.balancers"
local healthcheckers = require "kong.runloop.balancer.healthcheckers"
healthcheckers.init()
balancers.init()
local kong = {}
_G.kong = kong
kong.worker_events = require "resty.worker.events"
kong.worker_events.configure({
shm = "kong_process_events", -- defined by "lua_shared_dict"
timeout = 5, -- life time of event data in shm
interval = 1, -- poll interval (seconds)
wait_interval = 0.010, -- wait before retry fetching event data
wait_max = 0.5, -- max wait time before discarding event
})
local function empty_each()
return function() end
end
kong.db = {
targets = {
each = empty_each,
select_by_upstream_raw = function()
return {}
end
},
upstreams = {
each = empty_each,
select = function() end,
},
}
kong.core_cache = {
_cache = {},
get = function(self, key, _, loader, arg)
local v = self._cache[key]
if v == nil then
v = loader(arg)
self._cache[key] = v
end
return v
end,
invalidate_local = function(self, key)
self._cache[key] = nil
end
}
end)
before_each(function()
setup_block()
assert(client.init {
hosts = {},
resolvConf = {
"nameserver 8.8.8.8"
},
})
snapshot = assert:snapshot()
end)
after_each(function()
snapshot:revert() -- undo any spying/stubbing etc.
unsetup_block()
collectgarbage()
collectgarbage()
end)
describe("getting targets", function()
it("gets an IP address and port number; consistent hashing", function()
dnsA({
{ name = "mashape.com", address = "1.2.3.4" },
})
dnsA({
{ name = "getkong.org", address = "5.6.7.8" },
})
local b = new_balancer({
hosts = {
{name = "mashape.com", port = 123, weight = 10},
{name = "getkong.org", port = 321, weight = 5},
},
dns = client,
wheelSize = (1000),
})
-- run down the wheel, hitting all indices once
local res = {}
for n = 1, 1500 do
local addr, port, host = b:getPeer(false, nil, tostring(n))
res[addr..":"..port] = (res[addr..":"..port] or 0) + 1
res[host..":"..port] = (res[host..":"..port] or 0) + 1
end
-- weight distribution may vary up to 10% when using ketama algorithm
assert.is_true(res["1.2.3.4:123"] > 900)
assert.is_true(res["1.2.3.4:123"] < 1100)
assert.is_true(res["5.6.7.8:321"] > 450)
assert.is_true(res["5.6.7.8:321"] < 550)
-- hit one index 15 times
res = {}
local hash = tostring(6) -- just pick one
for _ = 1, 15 do
local addr, port, host = b:getPeer(false, nil, hash)
res[addr..":"..port] = (res[addr..":"..port] or 0) + 1
res[host..":"..port] = (res[host..":"..port] or 0) + 1
end
assert(15 == res["1.2.3.4:123"] or nil == res["1.2.3.4:123"], "mismatch")
assert(15 == res["mashape.com:123"] or nil == res["mashape.com:123"], "mismatch")
assert(15 == res["5.6.7.8:321"] or nil == res["5.6.7.8:321"], "mismatch")
assert(15 == res["getkong.org:321"] or nil == res["getkong.org:321"], "mismatch")
end)
it("evaluate the change in the continuum", function()
local res1 = {}
local res2 = {}
local res3 = {}
local b = new_balancer({
hosts = {
{name = "10.0.0.1", port = 1, weight = 100},
{name = "10.0.0.2", port = 2, weight = 100},
{name = "10.0.0.3", port = 3, weight = 100},
{name = "10.0.0.4", port = 4, weight = 100},
{name = "10.0.0.5", port = 5, weight = 100},
},
dns = client,
wheelSize = 5000,
})
for n = 1, 10000 do
local addr, port = b:getPeer(false, nil, n)
res1[n] = { ip = addr, port = port }
end
add_target(b, "10.0.0.6", 6, 100)
for n = 1, 10000 do
local addr, port = b:getPeer(false, nil, n)
res2[n] = { ip = addr, port = port }
end
local dif = 0
for n = 1, 10000 do
if res1[n].ip ~= res2[n].ip or res1[n].port ~= res2[n].port then
dif = dif + 1
end
end
-- increasing the number of addresses from 5 to 6 should change 49% of
-- targets if we were using a simple distribution, like an array.
-- anyway, we should be below than 20%.
assert((dif/100) < 49, "it should be better than a simple distribution")
assert((dif/100) < 20, "it is still to much change ")
add_target(b, "10.0.0.7", 7, 100)
add_target(b, "10.0.0.8", 8, 100)
for n = 1, 10000 do
local addr, port = b:getPeer(false, nil, n)
res3[n] = { ip = addr, port = port }
end
dif = 0
local dif2 = 0
for n = 1, 10000 do
if res1[n].ip ~= res3[n].ip or res1[n].port ~= res3[n].port then
dif = dif + 1
end
if res2[n].ip ~= res3[n].ip or res2[n].port ~= res3[n].port then
dif2 = dif2 + 1
end
end
-- increasing the number of addresses from 5 to 8 should change 83% of
-- targets, and from 6 to 8, 76%, if we were using a simple distribution,
-- like an array.
-- either way, we should be below than 40% and 25%.
assert((dif/100) < 83, "it should be better than a simple distribution")
assert((dif/100) < 40, "it is still to much change ")
assert((dif2/100) < 76, "it should be better than a simple distribution")
assert((dif2/100) < 25, "it is still to much change ")
end)
it("gets an IP address and port number; consistent hashing skips unhealthy addresses", function()
dnsA({
{ name = "mashape.com", address = "1.2.3.4" },
})
dnsA({
{ name = "getkong.org", address = "5.6.7.8" },
})
local b = new_balancer({
hosts = {
{name = "mashape.com", port = 123, weight = 100},
{name = "getkong.org", port = 321, weight = 50},
},
dns = client,
wheelSize = 1000,
})
-- mark node down
assert(b:setAddressStatus(b:findAddress("1.2.3.4", 123, "mashape.com"), false))
-- do a few requests
local res = {}
for n = 1, 160 do
local addr, port, host = b:getPeer(false, nil, n)
res[addr..":"..port] = (res[addr..":"..port] or 0) + 1
res[host..":"..port] = (res[host..":"..port] or 0) + 1
end
assert.equal(nil, res["1.2.3.4:123"]) -- address got no hits, key never gets initialized
assert.equal(nil, res["mashape.com:123"]) -- host got no hits, key never gets initialized
assert.equal(160, res["5.6.7.8:321"])
assert.equal(160, res["getkong.org:321"])
end)
it("does not hit the resolver when 'cache_only' is set", function()
local record = dnsA({
{ name = "mashape.com", address = "1.2.3.4" },
})
local b = new_balancer({
hosts = { { name = "mashape.com", port = 80, weight = 5 } },
dns = client,
wheelSize = 10,
})
record.expire = gettime() - 1 -- expire current dns cache record
dnsA({ -- create a new record
{ name = "mashape.com", address = "5.6.7.8" },
})
-- create a spy to check whether dns was queried
spy.on(client, "resolve")
local hash = "a value to hash"
local cache_only = true
local ip, port, host = b:getPeer(cache_only, nil, hash)
assert.spy(client.resolve).Not.called_with("mashape.com",nil, nil)
assert.equal("1.2.3.4", ip) -- initial un-updated ip address
assert.equal(80, port)
assert.equal("mashape.com", host)
end)
end)
describe("setting status triggers address-callback", function()
it("for IP addresses", function()
local count_add = 0
local count_remove = 0
local b
b = new_balancer({
hosts = {}, -- no hosts, so balancer is empty
dns = client,
wheelSize = 10,
callback = function(balancer, action, address, ip, port, hostname)
assert.equal(b, balancer)
if action == "added" then
count_add = count_add + 1
elseif action == "removed" then
count_remove = count_remove + 1
elseif action == "health" then --luacheck: ignore
-- nothing to do
else
error("unknown action received: "..tostring(action))
end
if action ~= "health" then
assert.equals("12.34.56.78", ip)
assert.equals(123, port)
assert.equals("12.34.56.78", hostname)
end
end
})
add_target(b, "12.34.56.78", 123, 100)
ngx.sleep(0)
assert.equal(1, count_add)
assert.equal(0, count_remove)
--b:removeHost("12.34.56.78", 123)
b.targets[1].addresses[1].disabled = true
b:deleteDisabledAddresses(b.targets[1])
ngx.sleep(0)
assert.equal(1, count_add)
assert.equal(1, count_remove)
end)
it("for 1 level dns", function()
local count_add = 0
local count_remove = 0
local b
b = new_balancer({
hosts = {}, -- no hosts, so balancer is empty
dns = client,
wheelSize = 10,
callback = function(balancer, action, address, ip, port, hostname)
assert.equal(b, balancer)
if action == "added" then
count_add = count_add + 1
elseif action == "removed" then
count_remove = count_remove + 1
elseif action == "health" then --luacheck: ignore
-- nothing to do
else
error("unknown action received: "..tostring(action))
end
if action ~= "health" then
assert.equals("12.34.56.78", ip)
assert.equals(123, port)
assert.equals("mashape.com", hostname)
end
end
})
dnsA({
{ name = "mashape.com", address = "12.34.56.78" },
{ name = "mashape.com", address = "12.34.56.78" },
})
add_target(b, "mashape.com", 123, 100)
ngx.sleep(0)
assert.equal(2, count_add)
assert.equal(0, count_remove)
b.targets[1].addresses[1].disabled = true
b.targets[1].addresses[2].disabled = true
b:deleteDisabledAddresses(b.targets[1])
ngx.sleep(0)
assert.equal(2, count_add)
assert.equal(2, count_remove)
end)
it("for 2+ level dns", function()
local count_add = 0
local count_remove = 0
local b
b = new_balancer({
hosts = {}, -- no hosts, so balancer is empty
dns = client,
wheelSize = 10,
callback = function(balancer, action, address, ip, port, hostname)
assert.equal(b, balancer)
if action == "added" then
count_add = count_add + 1
elseif action == "removed" then
count_remove = count_remove + 1
elseif action == "health" then --luacheck: ignore
-- nothing to do
else
error("unknown action received: "..tostring(action))
end
if action ~= "health" then
assert(ip == "mashape1.com" or ip == "mashape2.com")
assert(port == 8001 or port == 8002)
assert.equals("mashape.com", hostname)
end
end
})
dnsA({
{ name = "mashape1.com", address = "12.34.56.1" },
})
dnsA({
{ name = "mashape2.com", address = "12.34.56.2" },
})
dnsSRV({
{ name = "mashape.com", target = "mashape1.com", port = 8001, weight = 5 },
{ name = "mashape.com", target = "mashape2.com", port = 8002, weight = 5 },
})
add_target(b, "mashape.com", 123, 100)
ngx.sleep(0)
assert.equal(2, count_add)
assert.equal(0, count_remove)
--b:removeHost("mashape.com", 123)
b.targets[1].addresses[1].disabled = true
b.targets[1].addresses[2].disabled = true
b:deleteDisabledAddresses(b.targets[1])
ngx.sleep(0)
assert.equal(2, count_add)
assert.equal(2, count_remove)
end)
end)
describe("wheel manipulation", function()
it("wheel updates are atomic", function()
-- testcase for issue #49, see:
-- https://github.com/Kong/lua-resty-dns-client/issues/49
local order_of_events = {}
local b
b = new_balancer({
hosts = {}, -- no hosts, so balancer is empty
dns = client,
wheelSize = 10,
callback = function(balancer, action, ip, port, hostname)
table.insert(order_of_events, "callback")
-- this callback is called when updating. So yield here and
-- verify that the second thread does not interfere with
-- the first update, yielded here.
ngx.sleep(0)
end
})
dnsA({
{ name = "mashape1.com", address = "12.34.56.78" },
})
dnsA({
{ name = "mashape2.com", address = "123.45.67.89" },
})
local t1 = ngx.thread.spawn(function()
table.insert(order_of_events, "thread1 start")
add_target(b, "mashape1.com")
table.insert(order_of_events, "thread1 end")
end)
local t2 = ngx.thread.spawn(function()
table.insert(order_of_events, "thread2 start")
add_target(b, "mashape2.com")
table.insert(order_of_events, "thread2 end")
end)
ngx.thread.wait(t1)
ngx.thread.wait(t2)
ngx.sleep(0)
assert.same({
[1] = 'thread1 start',
[2] = 'thread1 end',
[3] = 'thread2 start',
[4] = 'thread2 end',
[5] = 'callback',
[6] = 'callback',
[7] = 'callback',
}, order_of_events)
end)
it("equal weights and 'fitting' indices", function()
dnsA({
{ name = "mashape.com", address = "1.2.3.4" },
{ name = "mashape.com", address = "1.2.3.5" },
})
local b = new_balancer({
hosts = {"mashape.com"},
dns = client,
wheelSize = 1000,
})
local expected = {
["1.2.3.4:80"] = 80,
["1.2.3.5:80"] = 80,
}
assert.are.same(expected, count_indices(b))
end)
it("DNS record order has no effect", function()
dnsA({
{ name = "mashape.com", address = "1.2.3.1" },
{ name = "mashape.com", address = "1.2.3.2" },
{ name = "mashape.com", address = "1.2.3.3" },
{ name = "mashape.com", address = "1.2.3.4" },
{ name = "mashape.com", address = "1.2.3.5" },
{ name = "mashape.com", address = "1.2.3.6" },
{ name = "mashape.com", address = "1.2.3.7" },
{ name = "mashape.com", address = "1.2.3.8" },
{ name = "mashape.com", address = "1.2.3.9" },
{ name = "mashape.com", address = "1.2.3.10" },
})
local b = new_balancer({
hosts = {"mashape.com"},
dns = client,
wheelSize = 1000,
})
local expected = count_indices(b)
dnsA({
{ name = "mashape.com", address = "1.2.3.8" },
{ name = "mashape.com", address = "1.2.3.3" },
{ name = "mashape.com", address = "1.2.3.1" },
{ name = "mashape.com", address = "1.2.3.2" },
{ name = "mashape.com", address = "1.2.3.4" },
{ name = "mashape.com", address = "1.2.3.5" },
{ name = "mashape.com", address = "1.2.3.6" },
{ name = "mashape.com", address = "1.2.3.9" },
{ name = "mashape.com", address = "1.2.3.10" },
{ name = "mashape.com", address = "1.2.3.7" },
})
b = new_balancer({
hosts = {"mashape.com"},
dns = client,
wheelSize = 1000,
})
assert.are.same(expected, count_indices(b))
end)
it("changing hostname order has no effect", function()
dnsA({
{ name = "mashape.com", address = "1.2.3.1" },
})
dnsA({
{ name = "getkong.org", address = "1.2.3.2" },
})
local b = new_balancer {
hosts = {"mashape.com", "getkong.org"},
dns = client,
wheelSize = 1000,
}
local expected = count_indices(b)
b = new_balancer({
hosts = {"getkong.org", "mashape.com"}, -- changed host order
dns = client,
wheelSize = 1000,
})
assert.are.same(expected, count_indices(b))
end)
it("adding a host", function()
dnsA({
{ name = "mashape.com", address = "1.2.3.4" },
{ name = "mashape.com", address = "1.2.3.5" },
})
dnsAAAA({
{ name = "getkong.org", address = "::1" },
})
local b = new_balancer({
hosts = { { name = "mashape.com", port = 80, weight = 5 } },
dns = client,
wheelSize = 2000,
})
add_target(b, "getkong.org", 8080, 10 )
local expected = {
["1.2.3.4:80"] = 80,
["1.2.3.5:80"] = 80,
["[::1]:8080"] = 160,
}
assert.are.same(expected, count_indices(b))
end)
it("removing the last host", function()
dnsA({
{ name = "mashape.com", address = "1.2.3.4" },
{ name = "mashape.com", address = "1.2.3.5" },
})
dnsAAAA({
{ name = "getkong.org", address = "::1" },
})
local b = new_balancer({
dns = client,
wheelSize = 1000,
})
add_target(b, "mashape.com", 80, 5)
add_target(b, "getkong.org", 8080, 10)
--b:removeHost("getkong.org", 8080)
--b:removeHost("mashape.com", 80)
end)
it("weight change updates properly", function()
dnsA({
{ name = "mashape.com", address = "1.2.3.4" },
{ name = "mashape.com", address = "1.2.3.5" },
})
dnsAAAA({
{ name = "getkong.org", address = "::1" },
})
local b = new_balancer({
dns = client,
wheelSize = 1000,
})
add_target(b, "mashape.com", 80, 10)
add_target(b, "getkong.org", 80, 10)
local count = count_indices(b)
-- 2 hosts -> 320 points
-- resolved to 3 addresses with same weight -> 106 points each
assert.same({
["1.2.3.4:80"] = 106,
["1.2.3.5:80"] = 106,
["[::1]:80"] = 106,
}, count)
add_target(b, "mashape.com", 80, 25)
count = count_indices(b)
-- 2 hosts -> 320 points
-- 1 with 83% of weight resolved to 2 addresses -> 133 points each addr
-- 1 with 16% of weight resolved to 1 address -> 53 points
assert.same({
["1.2.3.4:80"] = 133,
["1.2.3.5:80"] = 133,
["[::1]:80"] = 53,
}, count)
end)
it("weight change ttl=0 record, updates properly", function()
-- mock the resolve/toip methods
local old_resolve = client.resolve
local old_toip = client.toip
finally(function()
client.resolve = old_resolve
client.toip = old_toip
end)
client.resolve = function(name, ...)
if name == "mashape.com" then
local record = dnsA({
{ name = "mashape.com", address = "1.2.3.4", ttl = 0 },
})
return record
else
return old_resolve(name, ...)
end
end
client.toip = function(name, ...)
if name == "mashape.com" then
return "1.2.3.4", ...
else
return old_toip(name, ...)
end
end
-- insert 2nd address
dnsA({
{ name = "getkong.org", address = "9.9.9.9", ttl = 60*60 },
})
local b = new_balancer({
hosts = {
{ name = "mashape.com", port = 80, weight = 50 },
{ name = "getkong.org", port = 123, weight = 50 },
},
dns = client,
wheelSize = 100,
ttl0 = 2,
})
local count = count_indices(b)
assert.same({
["mashape.com:80"] = 160,
["9.9.9.9:123"] = 160,
}, count)
-- update weights
add_target(b, "mashape.com", 80, 150)
count = count_indices(b)
-- total weight: 200
-- 2 hosts: 320 points
-- 75%: 240, 25%: 80
assert.same({
["mashape.com:80"] = 240,
["9.9.9.9:123"] = 80,
}, count)
end)
it("weight change for unresolved record, updates properly", function()
local record = dnsA({
{ name = "really.really.really.does.not.exist.host.test", address = "1.2.3.4" },
})
dnsAAAA({
{ name = "getkong.org", address = "::1" },
})
local b = new_balancer({
dns = client,
wheelSize = 1000,
requery = 1,
})
add_target(b, "really.really.really.does.not.exist.host.test", 80, 10)
add_target(b, "getkong.org", 80, 10)
local count = count_indices(b)
assert.same({
["1.2.3.4:80"] = 160,
["[::1]:80"] = 160,
}, count)
-- expire the existing record
record.expire = 0
record.expired = true
-- do a lookup to trigger the async lookup
client.resolve("really.really.really.does.not.exist.host.test", {qtype = client.TYPE_A})
sleep(1) -- provide time for async lookup to complete
--b:_hit_all() -- hit them all to force renewal
targets.resolve_targets(b.targets)
count = count_indices(b)
assert.same({
--["1.2.3.4:80"] = 0, --> failed to resolve, no more entries
["[::1]:80"] = 320,
}, count)
-- update the failed record
add_target(b, "really.really.really.does.not.exist.host.test", 80, 20)
-- reinsert a cache entry
dnsA({
{ name = "really.really.really.does.not.exist.host.test", address = "1.2.3.4" },
})
--sleep(2) -- wait for timer to re-resolve the record
targets.resolve_targets(b.targets)
count = count_indices(b)
-- 66%: 213 points
-- 33%: 106 points
assert.same({
["1.2.3.4:80"] = 213,
["[::1]:80"] = 106,
}, count)
end)
it("weight change SRV record, has no effect", function()
dnsA({
{ name = "mashape.com", address = "1.2.3.4" },
{ name = "mashape.com", address = "1.2.3.5" },
})
dnsSRV({
{ name = "gelato.io", target = "1.2.3.6", port = 8001, weight = 5 },
{ name = "gelato.io", target = "1.2.3.6", port = 8002, weight = 5 },
})
local b = new_balancer({
dns = client,
wheelSize = 1000,
})
add_target(b, "mashape.com", 80, 10)
add_target(b, "gelato.io", 80, 10) --> port + weight will be ignored
local count = count_indices(b)
local state = copyWheel(b)
-- 33%: 106 points
-- 16%: 53 points
assert.same({
["1.2.3.4:80"] = 106,
["1.2.3.5:80"] = 106,
["1.2.3.6:8001"] = 53,
["1.2.3.6:8002"] = 53,
}, count)
add_target(b, "gelato.io", 80, 20) --> port + weight will be ignored
count = count_indices(b)
assert.same({
["1.2.3.4:80"] = 106,
["1.2.3.5:80"] = 106,
["1.2.3.6:8001"] = 53,
["1.2.3.6:8002"] = 53,
}, count)
assert.same(state, copyWheel(b))
end)
it("renewed DNS A record; no changes", function()
local record = dnsA({
{ name = "mashape.com", address = "1.2.3.4" },
{ name = "mashape.com", address = "1.2.3.5" },
})
dnsA({
{ name = "getkong.org", address = "9.9.9.9" },
})
local b = new_balancer({
hosts = {
{ name = "mashape.com", port = 80, weight = 5 },
{ name = "getkong.org", port = 123, weight = 10 },
},
dns = client,
wheelSize = 100,
})
local state = copyWheel(b)
record.expire = gettime() -1 -- expire current dns cache record
dnsA({ -- create a new record (identical)
{ name = "mashape.com", address = "1.2.3.4" },
{ name = "mashape.com", address = "1.2.3.5" },
})
-- create a spy to check whether dns was queried
spy.on(client, "resolve")
-- call all, to make sure we hit the expired one
-- invoke balancer, to expire record and re-query dns
--b:_hit_all()
targets.resolve_targets(b.targets)
assert.spy(client.resolve).was_called_with("mashape.com",nil, nil)
assert.same(state, copyWheel(b))
end)
it("renewed DNS AAAA record; no changes", function()
local record = dnsAAAA({
{ name = "mashape.com", address = "::1" },
{ name = "mashape.com", address = "::2" },
})
dnsA({
{ name = "getkong.org", address = "9.9.9.9" },
})
local b = new_balancer({
hosts = {
{ name = "mashape.com", port = 80, weight = 5 },
{ name = "getkong.org", port = 123, weight = 10 },
},
dns = client,
wheelSize = 100,
})
local state = copyWheel(b)
record.expire = gettime() -1 -- expire current dns cache record
dnsAAAA({ -- create a new record (identical)
{ name = "mashape.com", address = "::1" },
{ name = "mashape.com", address = "::2" },
})
-- create a spy to check whether dns was queried
spy.on(client, "resolve")
-- call all, to make sure we hit the expired one
-- invoke balancer, to expire record and re-query dns
--b:_hit_all()
targets.resolve_targets(b.targets)
assert.spy(client.resolve).was_called_with("mashape.com",nil, nil)
assert.same(state, copyWheel(b))
end)
it("renewed DNS SRV record; no changes", function()
local record = dnsSRV({
{ name = "gelato.io", target = "1.2.3.6", port = 8001, weight = 5 },
{ name = "gelato.io", target = "1.2.3.6", port = 8002, weight = 5 },
{ name = "gelato.io", target = "1.2.3.6", port = 8003, weight = 5 },
})
dnsA({
{ name = "getkong.org", address = "9.9.9.9" },
})
local b = new_balancer({
hosts = {
{ name = "gelato.io" },
{ name = "getkong.org", port = 123, weight = 10 },
},
dns = client,
wheelSize = 100,
})
local state = copyWheel(b)
record.expire = gettime() -1 -- expire current dns cache record
dnsSRV({ -- create a new record (identical)
{ name = "gelato.io", target = "1.2.3.6", port = 8001, weight = 5 },
{ name = "gelato.io", target = "1.2.3.6", port = 8002, weight = 5 },
{ name = "gelato.io", target = "1.2.3.6", port = 8003, weight = 5 },
})
-- create a spy to check whether dns was queried
spy.on(client, "resolve")
-- call all, to make sure we hit the expired one
-- invoke balancer, to expire record and re-query dns
--b:_hit_all()
targets.resolve_targets(b.targets)
assert.spy(client.resolve).was_called_with("gelato.io",nil, nil)
assert.same(state, copyWheel(b))
end)
it("low weight with zero-indices assigned doesn't fail", function()
-- depending on order of insertion it is either 1 or 0 indices
-- but it may never error.
dnsA({
{ name = "mashape.com", address = "1.2.3.4" },
})
dnsA({
{ name = "getkong.org", address = "9.9.9.9" },
})
new_balancer({
hosts = {
{ name = "mashape.com", port = 80, weight = 99999 },
{ name = "getkong.org", port = 123, weight = 1 },
},
dns = client,
wheelSize = 1000,
})
-- Now the order reversed (weights exchanged)
dnsA({
{ name = "mashape.com", address = "1.2.3.4" },
})
dnsA({
{ name = "getkong.org", address = "9.9.9.9" },
})
new_balancer({
hosts = {
{ name = "mashape.com", port = 80, weight = 1 },
{ name = "getkong.org", port = 123, weight = 99999 },
},
dns = client,
wheelSize = 1000,
})
end)
it("SRV record with 0 weight doesn't fail resolving", function()
-- depending on order of insertion it is either 1 or 0 indices
-- but it may never error.
dnsSRV({
{ name = "gelato.io", target = "1.2.3.6", port = 8001, weight = 0 },
{ name = "gelato.io", target = "1.2.3.6", port = 8002, weight = 0 },
})
local b = new_balancer({
hosts = {
-- port and weight will be overridden by the above
{ name = "gelato.io", port = 80, weight = 99999 },
},
dns = client,
wheelSize = 100,
})
local ip, port = b:getPeer(false, nil, "test")
assert.equal("1.2.3.6", ip)
assert(port == 8001 or port == 8002, "port expected 8001 or 8002")
end)
it("recreate Kong issue #2131", function()
-- erasing does not remove the address from the host
-- so if the same address is added again, and then deleted again
-- then upon erasing it will find the previous erased address object,
-- and upon erasing again a nil-referencing issue then occurs
local ttl = 1
local record
local hostname = "dnstest.mashape.com"
-- mock the resolve/toip methods
local old_resolve = client.resolve
local old_toip = client.toip
finally(function()
client.resolve = old_resolve
client.toip = old_toip
end)
client.resolve = function(name, ...)
if name == hostname then
record = dnsA({
{ name = hostname, address = "1.2.3.4", ttl = ttl },
})
return record
else
return old_resolve(name, ...)
end
end
client.toip = function(name, ...)
if name == hostname then
return "1.2.3.4", ...
else
return old_toip(name, ...)
end
end
-- create a new balancer
local b = new_balancer({
hosts = {
{ name = hostname, port = 80, weight = 50 },
},
dns = client,
wheelSize = 1000,
ttl0 = 1,
})
sleep(1.1) -- wait for ttl to expire
-- fetch a peer to reinvoke dns and update balancer, with a ttl=0
ttl = 0
b:getPeer(false, nil, "value") --> force update internal from A to SRV
sleep(1.1) -- wait for ttl0, as provided to balancer, to expire
-- restore ttl to non-0, and fetch a peer to update balancer
ttl = 1
b:getPeer(false, nil, "value") --> force update internal from SRV to A
sleep(1.1) -- wait for ttl to expire
-- fetch a peer to reinvoke dns and update balancer, with a ttl=0
ttl = 0
b:getPeer(false, nil, "value") --> force update internal from A to SRV
end)
end)
end)