From 4ea417e3a7b7e7bfc1ffa48c4455b812b3792c5c Mon Sep 17 00:00:00 2001 From: Vinicius Mignot Date: Thu, 1 Apr 2021 22:08:34 -0300 Subject: [PATCH] perf(round_robin) ring balancer with no hashing feats (#123) * perf(round_robin) ring balancer with no hashing feats This commit adds a round-robin algorithm with the same features as the previous ring algorithm, but with no consistent-hashing support. * tests(*) tests matching the new algorithms * fix(round_robin) reduce the number of intermediate tables * perf(round_robin) use ipairs * perf(round_robin) shuffle the wheel after adding addresses --- lua-resty-dns-client-5.2.3-1.rockspec | 4 +- spec/balancer/generic_spec.lua | 392 +++++--- .../{ring_spec.lua => round_robin_spec.lua} | 933 +++++------------- spec/test_helpers.lua | 7 +- src/resty/dns/balancer/ring.lua | 526 ---------- src/resty/dns/balancer/round_robin.lua | 171 ++++ 6 files changed, 688 insertions(+), 1345 deletions(-) rename spec/balancer/{ring_spec.lua => round_robin_spec.lua} (55%) delete mode 100644 src/resty/dns/balancer/ring.lua create mode 100644 src/resty/dns/balancer/round_robin.lua diff --git a/lua-resty-dns-client-5.2.3-1.rockspec b/lua-resty-dns-client-5.2.3-1.rockspec index f26aa45..2149d57 100644 --- a/lua-resty-dns-client-5.2.3-1.rockspec +++ b/lua-resty-dns-client-5.2.3-1.rockspec @@ -35,9 +35,9 @@ build = { ["resty.dns.utils"] = "src/resty/dns/utils.lua", ["resty.dns.client"] = "src/resty/dns/client.lua", ["resty.dns.balancer.base"] = "src/resty/dns/balancer/base.lua", - ["resty.dns.balancer.ring"] = "src/resty/dns/balancer/ring.lua", + ["resty.dns.balancer.consistent_hashing"] = "src/resty/dns/balancer/consistent_hashing.lua", ["resty.dns.balancer.least_connections"] = "src/resty/dns/balancer/least_connections.lua", ["resty.dns.balancer.handle"] = "src/resty/dns/balancer/handle.lua", - ["resty.dns.balancer.consistent_hashing"] = "src/resty/dns/balancer/consistent_hashing.lua", + ["resty.dns.balancer.round_robin"] = "src/resty/dns/balancer/round_robin.lua", }, } diff --git a/spec/balancer/generic_spec.lua b/spec/balancer/generic_spec.lua index 7e6677d..8f356bd 100644 --- a/spec/balancer/generic_spec.lua +++ b/spec/balancer/generic_spec.lua @@ -111,8 +111,12 @@ for algorithm, balancer_module in helpers.balancer_types() do b.getPeer = function(self) -- we do not really need to get a peer, just touch all addresses to -- potentially force DNS renewals - for _, addr in ipairs(self.addresses) do - addr:getPeer() + for i, addr in ipairs(self.addresses) do + if algorithm == "consistent-hashing" then + addr:getPeer(nil, nil, tostring(i)) + else + addr:getPeer() + end end end b:addHost("127.0.0.1", 8000, 100) -- add 1 initial host @@ -1436,7 +1440,12 @@ for algorithm, balancer_module in helpers.balancer_types() do { name = "srvrecord.tst", target = "1.1.1.1", port = 9000, weight = 20 }, { name = "srvrecord.tst", target = "2.2.2.2", port = 9001, weight = 20 }, }) - b:getPeer() -- touch all adresses to force dns renewal + -- touch all adresses to force dns renewal + if algorithm == "consistent-hashing" then + b:getPeer(nil, nil, "value") + else + b:getPeer() + end b:addHost("srvrecord.tst", 8001, 99) -- add again to update nodeWeight assert.same({ @@ -1523,14 +1532,12 @@ for algorithm, balancer_module in helpers.balancer_types() do { name = "konghq.com", target = "1.1.1.1", port = 2, weight = 3 }, }) b:addHost("konghq.com", 8000, 50) - local ip, port, hostname, handle - if algorithm == "consistent-hashing (new)" then + if algorithm == "consistent-hashing" then ip, port, hostname, handle = b:getPeer(false, nil, "a string") else ip, port, hostname, handle = b:getPeer() end - assert.equal("1.1.1.1", ip) assert.equal(2, port) assert.equal("konghq.com", hostname) @@ -1546,14 +1553,12 @@ for algorithm, balancer_module in helpers.balancer_types() do { name = "konghq.com", target = "getkong.org", port = 2, weight = 3 }, }) b:addHost("konghq.com", 8000, 50) - local ip, port, hostname, handle - if algorithm == "consistent-hashing (new)" then + if algorithm == "consistent-hashing" then ip, port, hostname, handle = b:getPeer(false, nil, "a string") else ip, port, hostname, handle = b:getPeer() end - assert.equal("1.2.3.4", ip) assert.equal(2, port) assert.equal("konghq.com", hostname) @@ -1571,14 +1576,12 @@ for algorithm, balancer_module in helpers.balancer_types() do { name = "konghq.com", target = "getkong.org", port = 2, weight = 3 }, }) b:addHost("konghq.com", 8000, 50) - local ip, port, hostname, handle - if algorithm == "consistent-hashing (new)" then + if algorithm == "consistent-hashing" then ip, port, hostname, handle = b:getPeer(false, nil, "a string") else ip, port, hostname, handle = b:getPeer() end - assert.equal("1.2.3.4", ip) assert.equal(2, port) assert.equal("getkong.org", hostname) @@ -1591,14 +1594,12 @@ for algorithm, balancer_module in helpers.balancer_types() do { name = "getkong.org", address = "1.2.3.4" }, }) b:addHost("getkong.org", 8000, 50) - local ip, port, hostname, handle - if algorithm == "consistent-hashing (new)" then + if algorithm == "consistent-hashing" then ip, port, hostname, handle = b:getPeer(false, nil, "another string") else ip, port, hostname, handle = b:getPeer() end - assert.equal("1.2.3.4", ip) assert.equal(8000, port) assert.equal("getkong.org", hostname) @@ -1608,14 +1609,12 @@ for algorithm, balancer_module in helpers.balancer_types() do it("returns expected results/types when using IPv4", function() b:addHost("4.3.2.1", 8000, 50) - local ip, port, hostname, handle - if algorithm == "consistent-hashing (new)" then + if algorithm == "consistent-hashing" then ip, port, hostname, handle = b:getPeer(false, nil, "a string") else ip, port, hostname, handle = b:getPeer() end - assert.equal("4.3.2.1", ip) assert.equal(8000, port) assert.equal(nil, hostname) @@ -1625,14 +1624,12 @@ for algorithm, balancer_module in helpers.balancer_types() do it("returns expected results/types when using IPv6", function() b:addHost("::1", 8000, 50) - local ip, port, hostname, handle - if algorithm == "consistent-hashing (new)" then + if algorithm == "consistent-hashing" then ip, port, hostname, handle = b:getPeer(false, nil, "just a string") else ip, port, hostname, handle = b:getPeer() end - assert.equal("[::1]", ip) assert.equal(8000, port) assert.equal(nil, hostname) @@ -1641,10 +1638,16 @@ for algorithm, balancer_module in helpers.balancer_types() do it("fails when there are no addresses added", function() + local ip, port, hostname, handle + if algorithm == "consistent-hashing" then + ip, port, hostname, handle = b:getPeer(false, nil, "any string") + else + ip, port, hostname, handle = b:getPeer() + end assert.same({ nil, "Balancer is unhealthy", nil, nil, }, { - b:getPeer(false, nil, "any string") + ip, port, hostname, handle } ) end) @@ -1657,10 +1660,16 @@ for algorithm, balancer_module in helpers.balancer_types() do b:setAddressStatus(false, "127.0.0.1", 8000) b:setAddressStatus(false, "127.0.0.2", 8000) b:setAddressStatus(false, "127.0.0.3", 8000) + local ip, port, hostname, handle + if algorithm == "consistent-hashing" then + ip, port, hostname, handle = b:getPeer(false, nil, "a client string") + else + ip, port, hostname, handle = b:getPeer() + end assert.same({ nil, "Balancer is unhealthy", nil, nil, }, { - b:getPeer(false, nil, "a client string") + ip, port, hostname, handle } ) end) @@ -1670,7 +1679,7 @@ for algorithm, balancer_module in helpers.balancer_types() do b:addHost("127.0.0.1", 8000, 100) b:addHost("127.0.0.2", 8000, 100) b:addHost("127.0.0.3", 8000, 100) - if algorithm == "consistent-hashing (new)" then + if algorithm == "consistent-hashing" then assert.not_nil(b:getPeer(false, nil, "any client string here")) else assert.not_nil(b:getPeer()) @@ -1678,9 +1687,8 @@ for algorithm, balancer_module in helpers.balancer_types() do b:setAddressStatus(false, "127.0.0.1", 8000) b:setAddressStatus(false, "127.0.0.2", 8000) - local ip, port, hostname, handle - if algorithm == "consistent-hashing (new)" then + if algorithm == "consistent-hashing" then ip, port, hostname, handle = b:getPeer(false, nil, "any string here") else ip, port, hostname, handle = b:getPeer() @@ -1698,8 +1706,7 @@ for algorithm, balancer_module in helpers.balancer_types() do b:addHost("127.0.0.1", 8000, 100) b:addHost("127.0.0.2", 8000, 100) b:addHost("127.0.0.3", 8000, 100) - - if algorithm == "consistent-hashing (new)" then + if algorithm == "consistent-hashing" then assert.not_nil(b:getPeer(false, nil, "string from the client")) else assert.not_nil(b:getPeer()) @@ -1707,9 +1714,8 @@ for algorithm, balancer_module in helpers.balancer_types() do b:setAddressStatus(false, "127.0.0.1", 8000) b:setAddressStatus(false, "127.0.0.2", 8000) - local ip, port, hostname, handle - if algorithm == "consistent-hashing (new)" then + if algorithm == "consistent-hashing" then ip, port, hostname, handle = b:getPeer(false, nil, "string from the client") else ip, port, hostname, handle = b:getPeer() @@ -1722,7 +1728,7 @@ for algorithm, balancer_module in helpers.balancer_types() do ) b:setAddressStatus(true, "127.0.0.2", 8000) - if algorithm == "consistent-hashing (new)" then + if algorithm == "consistent-hashing" then assert.not_nil(b:getPeer(false, nil, "a string")) else assert.not_nil(b:getPeer()) @@ -1735,7 +1741,7 @@ for algorithm, balancer_module in helpers.balancer_types() do { name = "getkong.org", address = "1.2.3.4", ttl = 2 }, }) b:addHost("getkong.org", 8000, 50) - if algorithm == "consistent-hashing (new)" then + if algorithm == "consistent-hashing" then assert.not_nil(b:getPeer(false, nil, "from the client")) else assert.not_nil(b:getPeer()) @@ -1744,7 +1750,7 @@ for algorithm, balancer_module in helpers.balancer_types() do -- mark it as unhealthy assert(b:setAddressStatus(false, "1.2.3.4", 8000, "getkong.org")) local ip, port, hostname, handle - if algorithm == "consistent-hashing (new)" then + if algorithm == "consistent-hashing" then ip, port, hostname, handle = b:getPeer(false, nil, "from the client") else ip, port, hostname, handle = b:getPeer() @@ -1765,16 +1771,17 @@ for algorithm, balancer_module in helpers.balancer_types() do local timeout = ngx.now() + 5 -- we'll try for 5 seconds while true do assert(ngx.now() < timeout, "timeout") - local ip - if algorithm == "consistent-hashing (new)" then + if algorithm == "consistent-hashing" then ip = b:getPeer(false, nil, "from the client") + if ip ~= nil then + break -- expected result, success! + end else ip = b:getPeer() - end - - if ip == "5.6.7.8" then - break -- expected result, success! + if ip == "5.6.7.8" then + break -- expected result, success! + end end ngx.sleep(0.1) -- wait a bit before retrying @@ -1818,115 +1825,218 @@ for algorithm, balancer_module in helpers.balancer_types() do b:addHost("getkong.org", 5678, 1000) b:addHost("notachanceinhell.this.name.exists.konghq.com", 4321, 100) - local expected_status = { - healthy = true, - weight = { - total = 1170, - available = 1170, - unavailable = 0 - }, - hosts = { - { - host = "127.0.0.1", - port = 8000, - dns = "A", - nodeWeight = 100, - weight = { - total = 100, - available = 100, - unavailable = 0 - }, - addresses = { - { - healthy = true, - ip = "127.0.0.1", - port = 8000, - weight = 100 - }, - }, + if algorithm == "consistent-hashing" then + assert.same({ + healthy = true, + weight = { + total = 1170, + available = 1170, + unavailable = 0 }, - { - host = "0::1", - port = 8080, - dns = "AAAA", - nodeWeight = 50, - weight = { - total = 50, - available = 50, - unavailable = 0 - }, - addresses = { - { - healthy = true, - ip = "[0::1]", - port = 8080, - weight = 50 + hosts = { + { + host = "0::1", + port = 8080, + dns = "AAAA", + nodeWeight = 50, + weight = { + total = 50, + available = 50, + unavailable = 0 + }, + addresses = { + { + healthy = true, + ip = "[0::1]", + port = 8080, + weight = 50 + }, + }, + }, + { + host = "127.0.0.1", + port = 8000, + dns = "A", + nodeWeight = 100, + weight = { + total = 100, + available = 100, + unavailable = 0 + }, + addresses = { + { + healthy = true, + ip = "127.0.0.1", + port = 8000, + weight = 100 + }, + }, + }, + { + host = "getkong.org", + port = 5678, + dns = "ttl=0, virtual SRV", + nodeWeight = 1000, + weight = { + total = 1000, + available = 1000, + unavailable = 0 + }, + addresses = { + { + healthy = true, + ip = "getkong.org", + port = 5678, + weight = 1000 + }, + }, + }, + { + host = "notachanceinhell.this.name.exists.konghq.com", + port = 4321, + dns = "dns server error: 3 name error", + nodeWeight = 100, + weight = { + total = 0, + available = 0, + unavailable = 0 + }, + addresses = {}, + }, + { + host = "srvrecord.tst", + port = 1234, + dns = "SRV", + nodeWeight = 9999, + weight = { + total = 20, + available = 20, + unavailable = 0 + }, + addresses = { + { + healthy = true, + ip = "1.1.1.1", + port = 9000, + weight = 10 + }, + { + healthy = true, + ip = "2.2.2.2", + port = 9001, + weight = 10 + }, }, }, }, - { - host = "srvrecord.tst", - port = 1234, - dns = "SRV", - nodeWeight = 9999, - weight = { - total = 20, - available = 20, - unavailable = 0 - }, - addresses = { - { - healthy = true, - ip = "1.1.1.1", - port = 9000, - weight = 10 - }, - { - healthy = true, - ip = "2.2.2.2", - port = 9001, - weight = 10 - }, - }, - }, - { - host = "getkong.org", - port = 5678, - dns = "ttl=0, virtual SRV", - nodeWeight = 1000, - weight = { - total = 1000, - available = 1000, - unavailable = 0 - }, - addresses = { - { - healthy = true, - ip = "getkong.org", - port = 5678, - weight = 1000 - }, - }, + }, b:getStatus()) + else + assert.same({ + healthy = true, + weight = { + total = 1170, + available = 1170, + unavailable = 0 }, - { - host = "notachanceinhell.this.name.exists.konghq.com", - port = 4321, - dns = "dns server error: 3 name error", - nodeWeight = 100, - weight = { - total = 0, - available = 0, - unavailable = 0 + hosts = { + { + host = "127.0.0.1", + port = 8000, + dns = "A", + nodeWeight = 100, + weight = { + total = 100, + available = 100, + unavailable = 0 + }, + addresses = { + { + healthy = true, + ip = "127.0.0.1", + port = 8000, + weight = 100 + }, + }, + }, + { + host = "0::1", + port = 8080, + dns = "AAAA", + nodeWeight = 50, + weight = { + total = 50, + available = 50, + unavailable = 0 + }, + addresses = { + { + healthy = true, + ip = "[0::1]", + port = 8080, + weight = 50 + }, + }, + }, + { + host = "srvrecord.tst", + port = 1234, + dns = "SRV", + nodeWeight = 9999, + weight = { + total = 20, + available = 20, + unavailable = 0 + }, + addresses = { + { + healthy = true, + ip = "1.1.1.1", + port = 9000, + weight = 10 + }, + { + healthy = true, + ip = "2.2.2.2", + port = 9001, + weight = 10 + }, + }, + }, + { + host = "getkong.org", + port = 5678, + dns = "ttl=0, virtual SRV", + nodeWeight = 1000, + weight = { + total = 1000, + available = 1000, + unavailable = 0 + }, + addresses = { + { + healthy = true, + ip = "getkong.org", + port = 5678, + weight = 1000 + }, + }, + }, + { + host = "notachanceinhell.this.name.exists.konghq.com", + port = 4321, + dns = "dns server error: 3 name error", + nodeWeight = 100, + weight = { + total = 0, + available = 0, + unavailable = 0 + }, + addresses = {}, }, - addresses = {}, - }, - }, - } - local actual_status = b:getStatus() - assert.same(expected_status.healthy, actual_status.healthy) - assert.same(expected_status.weight, actual_status.weight) - assert.same(#expected_status.hosts, #actual_status.hosts) + }, + }, b:getStatus()) + end end) end) diff --git a/spec/balancer/ring_spec.lua b/spec/balancer/round_robin_spec.lua similarity index 55% rename from spec/balancer/ring_spec.lua rename to spec/balancer/round_robin_spec.lua index 269fa27..3551669 100644 --- a/spec/balancer/ring_spec.lua +++ b/spec/balancer/round_robin_spec.lua @@ -37,35 +37,7 @@ end local check_balancer = function(balancer) assert.is.table(balancer) check_list(balancer.hosts) - assert.are.equal(balancer.wheelSize, check_list(balancer.wheel)+check_list(balancer.unassignedWheelIndices)) - if balancer.weight == 0 then - -- all hosts failed, so the balancer indices are unassigned/empty - assert.are.equal(balancer.wheelSize, #balancer.unassignedWheelIndices) - for _, address in ipairs(balancer.wheel) do - assert.is_nil(address) - end - else - -- addresses - local addrlist = {} - for _, address in ipairs(balancer.wheel) do -- calculate indices per address based on the wheel - addrlist[address] = (addrlist[address] or 0) + 1 - end - for addr, count in pairs(addrlist) do - assert.are.equal(#addr.indices, count) - end - for _, host in ipairs(balancer.hosts) do -- remove indices per address based on hosts (results in 0) - for _, addr in ipairs(host.addresses) do - if addr.weight > 0 then - for _ in ipairs(addr.indices) do - addrlist[addr] = addrlist[addr] - 1 - end - end - end - end - for _, count in pairs(addrlist) do - assert.are.equal(0, count) - end - end + assert.are.equal(balancer.wheelSize, check_list(balancer.wheel)) return balancer end @@ -105,13 +77,13 @@ end ---------------------- -describe("[ringbalancer]", function() +describe("[round robin balancer]", function() local snapshot setup(function() _G.package.loaded["resty.dns.client"] = nil -- make sure module is reloaded - balancer = require "resty.dns.balancer.ring" + balancer = require "resty.dns.balancer.round_robin" client = require "resty.dns.client" end) @@ -134,19 +106,19 @@ describe("[ringbalancer]", function() describe("unit tests", function() it("addressIter", function() dnsA({ - { name = "mashape.com", address = "1.2.3.4" }, - { name = "mashape.com", address = "1.2.3.5" }, + { name = "mashape.test", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.5" }, }) dnsAAAA({ - { name = "getkong.org", address = "::1" }, + { name = "getkong.test", address = "::1" }, }) dnsSRV({ - { name = "gelato.io", target = "1.2.3.6", port = 8001 }, - { name = "gelato.io", target = "1.2.3.6", port = 8002 }, - { name = "gelato.io", target = "1.2.3.6", port = 8003 }, + { name = "gelato.test", target = "1.2.3.6", port = 8001 }, + { name = "gelato.test", target = "1.2.3.6", port = 8002 }, + { name = "gelato.test", target = "1.2.3.6", port = 8003 }, }) - local b = balancer.new { - hosts = {"mashape.com", "getkong.org", "gelato.io" }, + local b, err = balancer.new { + hosts = {"mashape.test", "getkong.test", "gelato.test" }, dns = client, wheelSize = 10, } @@ -168,14 +140,14 @@ describe("[ringbalancer]", function() end) it("fails without proper 'dns' option", function() assert.has.error( - function() balancer.new({ hosts = {"mashape.com"} }) end, + function() balancer.new({ hosts = {"mashape.test"} }) end, "expected option `dns` to be a configured dns client" ) end) it("fails with a bad 'requery' option", function() assert.has.error( function() balancer.new({ - hosts = {"mashape.com"}, + hosts = {"mashape.test"}, dns = client, requery = -5, }) end, @@ -185,39 +157,18 @@ describe("[ringbalancer]", function() it("fails with a bad 'ttl0' option", function() assert.has.error( function() balancer.new({ - hosts = {"mashape.com"}, + hosts = {"mashape.test"}, dns = client, ttl0 = -5, }) end, "expected 'ttl0' parameter to be > 0" ) end) - it("fails with inconsistent wheel and order sizes", function() - assert.has.error( - function() balancer.new({ - dns = client, - hosts = {"mashape.com"}, - wheelSize = 10, - order = {1,2,3}, - }) end, - "mismatch between size of 'order' and 'wheelSize'" - ) - end) - it("fails with a bad order list", function() - assert.has.error( - function() balancer.new({ - dns = client, - hosts = {"mashape.com"}, - order = {1,2,3,3}, --duplicate - }) end, - "the 'order' list contains duplicates" - ) - end) it("fails with a bad callback", function() assert.has.error( function() balancer.new({ dns = client, - hosts = {"mashape.com"}, + hosts = {"mashape.test"}, callback = "not a function", }) end, "expected 'callback' to be a function or nil, but got: string" @@ -225,71 +176,39 @@ describe("[ringbalancer]", function() end) it("succeeds with proper options", function() dnsA({ - { name = "mashape.com", address = "1.2.3.4" }, - { name = "mashape.com", address = "1.2.3.5" }, + { name = "mashape.test", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.5" }, }) check_balancer(balancer.new { - hosts = {"mashape.com"}, + hosts = {"mashape.test"}, dns = client, - wheelSize = 10, - order = {1,2,3,4,5,6,7,8,9,10}, requery = 2, ttl0 = 5, callback = function() end, }) end) - it("succeeds with the right sizes", function() - dnsA({ - { name = "mashape.com", address = "1.2.3.4" }, - }) - -- based on a given wheelSize - local b = check_balancer(balancer.new { - hosts = {"mashape.com"}, - dns = client, - wheelSize = 10, - }) - assert.are.equal(10, b.wheelSize) - -- based on the order list size - b = check_balancer(balancer.new { - hosts = {"mashape.com"}, - dns = client, - order = { 1,2,3 }, - }) - assert.are.equal(3, b.wheelSize) - -- based on the order list size and wheel size - b = check_balancer(balancer.new { - hosts = {"mashape.com"}, - dns = client, - wheelSize = 4, - order = { 1,2,3,4 }, - }) - assert.are.equal(4, b.wheelSize) - end) it("succeeds without 'hosts' option", function() local b = check_balancer(balancer.new { dns = client, - wheelSize = 10, }) - assert.are.equal(10, #b.unassignedWheelIndices) b = check_balancer(balancer.new { dns = client, - wheelSize = 10, hosts = {}, -- empty hosts table hould work too }) - assert.are.equal(10, #b.unassignedWheelIndices) + assert.are.equal(0, #b.wheel) end) it("succeeds with multiple hosts", function() dnsA({ - { name = "mashape.com", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.4" }, }) dnsAAAA({ - { name = "getkong.org", address = "::1" }, + { name = "getkong.test", address = "::1" }, }) dnsSRV({ - { name = "gelato.io", target = "1.2.3.4", port = 8001 }, + { name = "gelato.test", target = "1.2.3.4", port = 8001 }, }) local b = balancer.new { - hosts = {"mashape.com", "getkong.org", "gelato.io" }, + hosts = {"mashape.test", "getkong.test", "gelato.test" }, dns = client, wheelSize = 10, } @@ -340,9 +259,9 @@ describe("[ringbalancer]", function() check_balancer(b) assert.equals(0, b.weight) -- has one failed host, so weight must be 0 dnsA({ - { name = "mashape.com", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.4" }, }) - check_balancer(b:addHost("mashape.com", 80, 10)) + check_balancer(b:addHost("mashape.test", 80, 10)) assert.equals(10, b.weight) -- has one succesful host, so weight must equal that one end) it("accepts a hostname when dns server is unavailable", function() @@ -359,10 +278,9 @@ describe("[ringbalancer]", function() -- create balancer local b = check_balancer(balancer.new { hosts = { - { name = "mashape.com", port = 80, weight = 10 }, + { name = "mashape.test", port = 80, weight = 10 }, }, dns = client, - wheelSize = 100, }) assert.equal(0, b.weight) end) @@ -373,21 +291,21 @@ describe("[ringbalancer]", function() wheelSize = 15, }) dnsA({ - { name = "mashape.com", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.4" }, }) - local ok, err = b:addHost("mashape.com", 80, 10) + local ok, err = b:addHost("mashape.test", 80, 10) assert.are.equal(b, ok) assert.is_nil(err) check_balancer(b) assert.equal(10, b.weight) - ok, err = b:addHost("mashape.com", 81, 20) -- different port + ok, err = b:addHost("mashape.test", 81, 20) -- different port assert.are.equal(b, ok) assert.is_nil(err) check_balancer(b) assert.equal(30, b.weight) - ok, err = b:addHost("mashape.com", 80, 5) -- reduce weight by 5 + ok, err = b:addHost("mashape.test", 80, 5) -- reduce weight by 5 assert.are.equal(b, ok) assert.is_nil(err) check_balancer(b) @@ -478,33 +396,33 @@ describe("[ringbalancer]", function() it("SRV target with A record targets returns a descriptive error", function() local b = check_balancer(balancer.new { dns = client }) dnsA({ - { name = "mashape1.com", address = "12.34.56.1" }, + { name = "mashape1.test", address = "12.34.56.1" }, }) dnsA({ - { name = "mashape2.com", address = "12.34.56.2" }, + { name = "mashape2.test", 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 }, + { name = "mashape.test", target = "mashape1.test", port = 8001, weight = 5 }, + { name = "mashape.test", target = "mashape2.test", port = 8002, weight = 5 }, }) - b:addHost("mashape.com", 80, 10) - local ok, err = b:setAddressStatus(false, "mashape1.com", 80, "mashape.com") + b:addHost("mashape.test", 80, 10) + local ok, err = b:setAddressStatus(false, "mashape1.test", 80, "mashape.test") assert.is_nil(ok) - assert.equals("no peer found by name 'mashape.com' and address mashape1.com:80", err) + assert.equals("no peer found by name 'mashape.test' and address mashape1.test:80", err) end) it("SRV target with A record targets can be changed with a handle", function() local b = check_balancer(balancer.new { dns = client }) dnsA({ - { name = "mashape1.com", address = "12.34.56.1" }, + { name = "mashape1.test", address = "12.34.56.1" }, }) dnsA({ - { name = "mashape2.com", address = "12.34.56.2" }, + { name = "mashape2.test", 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 }, + { name = "mashape.test", target = "mashape1.test", port = 8001, weight = 5 }, + { name = "mashape.test", target = "mashape2.test", port = 8002, weight = 5 }, }) - b:addHost("mashape.com", 80, 10) + b:addHost("mashape.test", 80, 10) local _, _, _, handle = b:getPeer() local ok, err = b:setAddressStatus(false, handle) @@ -524,16 +442,16 @@ describe("[ringbalancer]", function() it("SRV target with A record targets can be changed with an address", function() local b = check_balancer(balancer.new { dns = client }) dnsA({ - { name = "mashape1.com", address = "12.34.56.1" }, + { name = "mashape1.test", address = "12.34.56.1" }, }) dnsA({ - { name = "mashape2.com", address = "12.34.56.2" }, + { name = "mashape2.test", 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 }, + { name = "mashape.test", target = "mashape1.test", port = 8001, weight = 5 }, + { name = "mashape.test", target = "mashape2.test", port = 8002, weight = 5 }, }) - b:addHost("mashape.com", 80, 10) + b:addHost("mashape.test", 80, 10) local _, _, _, handle = b:getPeer() local ok, err = b:setAddressStatus(false, handle.address) @@ -553,12 +471,12 @@ describe("[ringbalancer]", function() it("SRV target with port=0 returns the default port", function() local b = check_balancer(balancer.new { dns = client }) dnsA({ - { name = "mashape1.com", address = "12.34.56.78" }, + { name = "mashape1.test", address = "12.34.56.78" }, }) dnsSRV({ - { name = "mashape.com", target = "mashape1.com", port = 0, weight = 5 }, + { name = "mashape.test", target = "mashape1.test", port = 0, weight = 5 }, }) - b:addHost("mashape.com", 80, 10) + b:addHost("mashape.test", 80, 10) local ip, port = b:getPeer() assert.equals("12.34.56.78", ip) assert.equals(80, port) @@ -598,36 +516,35 @@ describe("[ringbalancer]", function() -- uses a different code branch -- See issue #17 dnsA({ - { name = "mashape.com", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.4" }, }) dnsSRV({ - { name = "gelato.io", target = "mashape.com", port = 8001 }, + { name = "gelato.test", target = "mashape.test", port = 8001 }, }) local b = check_balancer(balancer.new { hosts = { - {name = "gelato.io", port = 123, weight = 100}, + {name = "gelato.test", port = 123, weight = 100}, }, dns = client, }) local addr, port, host = b:getPeer() assert.equal("1.2.3.4", addr) assert.equal(8001, port) - assert.equal("gelato.io", host) + assert.equal("gelato.test", host) end) it("gets an IP address and port number; round-robin", function() dnsA({ - { name = "mashape.com", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.4" }, }) dnsA({ - { name = "getkong.org", address = "5.6.7.8" }, + { name = "getkong.test", address = "5.6.7.8" }, }) local b = check_balancer(balancer.new { hosts = { - {name = "mashape.com", port = 123, weight = 100}, - {name = "getkong.org", port = 321, weight = 50}, + {name = "mashape.test", port = 123, weight = 100}, + {name = "getkong.test", port = 321, weight = 50}, }, dns = client, - wheelSize = 15, }) -- run down the wheel twice local res = {} @@ -637,27 +554,27 @@ describe("[ringbalancer]", function() res[host..":"..port] = (res[host..":"..port] or 0) + 1 end assert.equal(20, res["1.2.3.4:123"]) - assert.equal(20, res["mashape.com:123"]) + assert.equal(20, res["mashape.test:123"]) assert.equal(10, res["5.6.7.8:321"]) - assert.equal(10, res["getkong.org:321"]) + assert.equal(10, res["getkong.test:321"]) end) it("gets an IP address and port number; round-robin skips unhealthy addresses", function() dnsA({ - { name = "mashape.com", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.4" }, }) dnsA({ - { name = "getkong.org", address = "5.6.7.8" }, + { name = "getkong.test", address = "5.6.7.8" }, }) local b = check_balancer(balancer.new { hosts = { - {name = "mashape.com", port = 123, weight = 100}, - {name = "getkong.org", port = 321, weight = 50}, + {name = "mashape.test", port = 123, weight = 100}, + {name = "getkong.test", port = 321, weight = 50}, }, dns = client, wheelSize = 15, }) -- mark node down - assert(b:setAddressStatus(false, "1.2.3.4", 123, "mashape.com")) + assert(b:setAddressStatus(false, "1.2.3.4", 123, "mashape.test")) -- run down the wheel twice local res = {} for _ = 1, 15*2 do @@ -666,156 +583,32 @@ describe("[ringbalancer]", function() 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(nil, res["mashape.test:123"]) -- host got no hits, key never gets initialized assert.equal(30, res["5.6.7.8:321"]) - assert.equal(30, res["getkong.org:321"]) - end) - 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 = check_balancer(balancer.new { - hosts = { - {name = "mashape.com", port = 123, weight = 100}, - {name = "getkong.org", port = 321, weight = 50}, - }, - dns = client, - wheelSize = 15, - }) - -- run down the wheel, hitting all indices once - local res = {} - for n = 1, 15 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(10, res["1.2.3.4:123"]) - assert.equal(5, res["5.6.7.8:321"]) - -- hit one index 15 times - res = {} - local hash = 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("gets an IP address and port number; consistent hashing wraps (modulo)", function() - local b = check_balancer(balancer.new { - hosts = { - {name = "1.2.3.1", port = 1, weight = 100}, - {name = "1.2.3.2", port = 1, weight = 100}, - {name = "1.2.3.3", port = 1, weight = 100}, - {name = "1.2.3.4", port = 1, weight = 100}, - {name = "1.2.3.5", port = 1, weight = 100}, - {name = "1.2.3.6", port = 1, weight = 100}, - {name = "1.2.3.7", port = 1, weight = 100}, - {name = "1.2.3.8", port = 1, weight = 100}, - {name = "1.2.3.9", port = 1, weight = 100}, - {name = "1.2.3.10", port = 1, weight = 100}, - }, - dns = client, - wheelSize = 10, - }) - -- run down the wheel, hitting all indices once - for n = 0, 9 do - local addr1, port1, host1 = b:getPeer(false, nil, n) - local addr2, port2, host2 = b:getPeer(false, nil, n + 10) -- wraps around, modulo - assert.equal(addr1, addr2) - assert.equal(port1, port2) - assert.equal(host1, host2) - end - end) - it("gets an IP address and port number; consistent hashing with retries", function() - local b = check_balancer(balancer.new { - hosts = { - {name = "1.2.3.1", port = 1, weight = 100}, - {name = "1.2.3.2", port = 1, weight = 100}, - {name = "1.2.3.3", port = 1, weight = 100}, - {name = "1.2.3.4", port = 1, weight = 100}, - {name = "1.2.3.5", port = 1, weight = 100}, - {name = "1.2.3.6", port = 1, weight = 100}, - {name = "1.2.3.7", port = 1, weight = 100}, - {name = "1.2.3.8", port = 1, weight = 100}, - {name = "1.2.3.9", port = 1, weight = 100}, - {name = "1.2.3.10", port = 1, weight = 100}, - }, - dns = client, - wheelSize = 10, - }) - -- run down the wheel, index 0, increasing the retry counter - local res = {} - local handle - for n = 0, 9 do - local addr, port, _ - addr, port, _, handle = b:getPeer(false, handle, 0, n) - assert.string(addr) - assert.number(port) - local key = addr..":"..port - res[key] = (res[key] or 0) + 1 - end - local count = 0 - for _,_ in pairs(res) do count = count + 1 end - assert.equal(10, count) -- 10 unique entries - 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 = check_balancer(balancer.new { - hosts = { - {name = "mashape.com", port = 123, weight = 100}, - {name = "getkong.org", port = 321, weight = 50}, - }, - dns = client, - wheelSize = 15, - }) - -- mark node down - assert(b:setAddressStatus(false, "1.2.3.4", 123, "mashape.com")) - -- run down the wheel twice - local res = {} - for n = 1, 15*2 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(30, res["5.6.7.8:321"]) - assert.equal(30, res["getkong.org:321"]) + assert.equal(30, res["getkong.test: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" }, + { name = "mashape.test", address = "1.2.3.4" }, }) local b = check_balancer(balancer.new { - hosts = { { name = "mashape.com", port = 80, weight = 5 } }, + hosts = { { name = "mashape.test", 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" }, + { name = "mashape.test", address = "5.6.7.8" }, }) -- create a spy to check whether dns was queried spy.on(client, "resolve") local hash = nil 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.spy(client.resolve).Not.called_with("mashape.test",nil, nil) assert.equal("1.2.3.4", ip) -- initial un-updated ip address assert.equal(80, port) - assert.equal("mashape.com", host) + assert.equal("mashape.test", host) end) end) @@ -877,19 +670,19 @@ describe("[ringbalancer]", function() if action ~= "health" then assert.equals("12.34.56.78", ip) assert.equals(123, port) - assert.equals("mashape.com", hostname) + assert.equals("mashape.test", hostname) end end }) dnsA({ - { name = "mashape.com", address = "12.34.56.78" }, - { name = "mashape.com", address = "12.34.56.78" }, + { name = "mashape.test", address = "12.34.56.78" }, + { name = "mashape.test", address = "12.34.56.78" }, }) - b:addHost("mashape.com", 123, 100) + b:addHost("mashape.test", 123, 100) ngx.sleep(0.1) assert.equal(2, count_add) assert.equal(0, count_remove) - b:removeHost("mashape.com", 123) + b:removeHost("mashape.test", 123) ngx.sleep(0.1) assert.equal(2, count_add) assert.equal(2, count_remove) @@ -914,27 +707,27 @@ describe("[ringbalancer]", function() error("unknown action received: "..tostring(action)) end if action ~= "health" then - assert(ip == "mashape1.com" or ip == "mashape2.com") + assert(ip == "mashape1.test" or ip == "mashape2.test") assert(port == 8001 or port == 8002) - assert.equals("mashape.com", hostname) + assert.equals("mashape.test", hostname) end end }) dnsA({ - { name = "mashape1.com", address = "12.34.56.1" }, + { name = "mashape1.test", address = "12.34.56.1" }, }) dnsA({ - { name = "mashape2.com", address = "12.34.56.2" }, + { name = "mashape2.test", 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 }, + { name = "mashape.test", target = "mashape1.test", port = 8001, weight = 5 }, + { name = "mashape.test", target = "mashape2.test", port = 8002, weight = 5 }, }) - b:addHost("mashape.com", 123, 100) + b:addHost("mashape.test", 123, 100) ngx.sleep(0.1) assert.equal(2, count_add) assert.equal(0, count_remove) - b:removeHost("mashape.com", 123) + b:removeHost("mashape.test", 123) ngx.sleep(0.1) assert.equal(2, count_add) assert.equal(2, count_remove) @@ -944,7 +737,7 @@ describe("[ringbalancer]", function() 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 + -- https://github.test/Kong/lua-resty-dns-client/issues/49 local order_of_events = {} local b b = check_balancer(balancer.new { @@ -960,19 +753,19 @@ describe("[ringbalancer]", function() end }) dnsA({ - { name = "mashape1.com", address = "12.34.56.78" }, + { name = "mashape1.test", address = "12.34.56.78" }, }) dnsA({ - { name = "mashape2.com", address = "123.45.67.89" }, + { name = "mashape2.test", address = "123.45.67.89" }, }) local t1 = ngx.thread.spawn(function() table.insert(order_of_events, "thread1 start") - b:addHost("mashape1.com") + b:addHost("mashape1.test") table.insert(order_of_events, "thread1 end") end) local t2 = ngx.thread.spawn(function() table.insert(order_of_events, "thread2 start") - b:addHost("mashape2.com") + b:addHost("mashape2.test") table.insert(order_of_events, "thread2 end") end) ngx.thread.wait(t1) @@ -990,85 +783,52 @@ describe("[ringbalancer]", function() 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" }, + { name = "mashape.test", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.5" }, }) local b = check_balancer(balancer.new { - hosts = {"mashape.com"}, + hosts = {"mashape.test"}, dns = client, - wheelSize = 10, }) local expected = { - ["1.2.3.4:80"] = 5, - ["1.2.3.5:80"] = 5, - } - assert.are.same(expected, count_indices(b)) - end) - it("equal weights and 'non-fitting' indices", 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 = check_balancer(balancer.new { - hosts = {"mashape.com"}, - dns = client, - wheelSize = 19, - }) - local expected = { - ["1.2.3.1:80"] = 1, - ["1.2.3.2:80"] = 2, - ["1.2.3.3:80"] = 2, - ["1.2.3.4:80"] = 2, - ["1.2.3.5:80"] = 2, - ["1.2.3.6:80"] = 2, - ["1.2.3.7:80"] = 2, - ["1.2.3.8:80"] = 2, - ["1.2.3.9:80"] = 2, - ["1.2.3.10:80"] = 2, + ["1.2.3.4:80"] = 1, + ["1.2.3.5:80"] = 1, } 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" }, + { name = "mashape.test", address = "1.2.3.1" }, + { name = "mashape.test", address = "1.2.3.2" }, + { name = "mashape.test", address = "1.2.3.3" }, + { name = "mashape.test", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.5" }, + { name = "mashape.test", address = "1.2.3.6" }, + { name = "mashape.test", address = "1.2.3.7" }, + { name = "mashape.test", address = "1.2.3.8" }, + { name = "mashape.test", address = "1.2.3.9" }, + { name = "mashape.test", address = "1.2.3.10" }, }) local b = check_balancer(balancer.new { - hosts = {"mashape.com"}, + hosts = {"mashape.test"}, dns = client, wheelSize = 19, }) 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" }, + { name = "mashape.test", address = "1.2.3.8" }, + { name = "mashape.test", address = "1.2.3.3" }, + { name = "mashape.test", address = "1.2.3.1" }, + { name = "mashape.test", address = "1.2.3.2" }, + { name = "mashape.test", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.5" }, + { name = "mashape.test", address = "1.2.3.6" }, + { name = "mashape.test", address = "1.2.3.9" }, + { name = "mashape.test", address = "1.2.3.10" }, + { name = "mashape.test", address = "1.2.3.7" }, }) b = check_balancer(balancer.new { - hosts = {"mashape.com"}, + hosts = {"mashape.test"}, dns = client, wheelSize = 19, }) @@ -1077,149 +837,89 @@ describe("[ringbalancer]", function() end) it("changing hostname order has no effect", function() dnsA({ - { name = "mashape.com", address = "1.2.3.1" }, + { name = "mashape.test", address = "1.2.3.1" }, }) dnsA({ - { name = "getkong.org", address = "1.2.3.2" }, + { name = "getkong.test", address = "1.2.3.2" }, }) local b = balancer.new { - hosts = {"mashape.com", "getkong.org"}, + hosts = {"mashape.test", "getkong.test"}, dns = client, wheelSize = 3, } local expected = count_indices(b) b = check_balancer(balancer.new { - hosts = {"getkong.org", "mashape.com"}, -- changed host order + hosts = {"getkong.test", "mashape.test"}, -- changed host order dns = client, wheelSize = 3, }) assert.are.same(expected, count_indices(b)) end) - it("adding a host (non-fitting indices)", 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 = check_balancer(balancer.new { - hosts = { { name = "mashape.com", weight = 5} }, - dns = client, - wheelSize = 10, - }) - b:addHost("getkong.org", 8080, 10 ) - check_balancer(b) - local expected = { - ["1.2.3.4:80"] = 2, - ["1.2.3.5:80"] = 2, - ["[::1]:8080"] = 6, - } - assert.are.same(expected, count_indices(b)) - end) it("adding a host (fitting indices)", function() dnsA({ - { name = "mashape.com", address = "1.2.3.4" }, - { name = "mashape.com", address = "1.2.3.5" }, + { name = "mashape.test", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.5" }, }) dnsAAAA({ - { name = "getkong.org", address = "::1" }, + { name = "getkong.test", address = "::1" }, }) local b = check_balancer(balancer.new { - hosts = { { name = "mashape.com", port = 80, weight = 5 } }, + hosts = { { name = "mashape.test", port = 80, weight = 5 } }, dns = client, - wheelSize = 2000, }) - b:addHost("getkong.org", 8080, 10 ) + b:addHost("getkong.test", 8080, 10 ) check_balancer(b) local expected = { - ["1.2.3.4:80"] = 500, - ["1.2.3.5:80"] = 500, - ["[::1]:8080"] = 1000, + ["1.2.3.4:80"] = 1, + ["1.2.3.5:80"] = 1, + ["[::1]:8080"] = 2, } assert.are.same(expected, count_indices(b)) end) - it("removing a host, indices staying in place", 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 = check_balancer(balancer.new { - hosts = { { name = "mashape.com", port = 80, weight = 5 } }, - dns = client, - wheelSize = 2000, - }) - b:addHost("getkong.org", 8080, 10) - check_balancer(b) - - -- copy the first 500 indices, they should not move - local expected1 = {} - icopy(expected1, b.hosts[1].addresses[1].indices, 1, 1, 500) - local expected2 = {} - icopy(expected2, b.hosts[1].addresses[2].indices, 1, 1, 500) - - b:removeHost("getkong.org") - check_balancer(b) - - -- copy the new first 500 indices as well - local expected1a = {} - icopy(expected1a, b.hosts[1].addresses[1].indices, 1, 1, 500) - local expected2a = {} - icopy(expected2a, b.hosts[1].addresses[2].indices, 1, 1, 500) - - -- compare previous copy against current first 500 indices to make sure they are the same - for i = 1,500 do - assert(expected1[i] == expected1a[i]) - assert(expected1[i] == expected1a[i]) - end - end) it("removing the last host", function() dnsA({ - { name = "mashape.com", address = "1.2.3.4" }, - { name = "mashape.com", address = "1.2.3.5" }, + { name = "mashape.test", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.5" }, }) dnsAAAA({ - { name = "getkong.org", address = "::1" }, + { name = "getkong.test", address = "::1" }, }) local b = check_balancer(balancer.new { dns = client, wheelSize = 20, }) - b:addHost("mashape.com", 80, 5) - b:addHost("getkong.org", 8080, 10) - b:removeHost("getkong.org", 8080) - b:removeHost("mashape.com", 80) + b:addHost("mashape.test", 80, 5) + b:addHost("getkong.test", 8080, 10) + b:removeHost("getkong.test", 8080) + b:removeHost("mashape.test", 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" }, + { name = "mashape.test", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.5" }, }) dnsAAAA({ - { name = "getkong.org", address = "::1" }, + { name = "getkong.test", address = "::1" }, }) local b = check_balancer(balancer.new { dns = client, wheelSize = 60, }) - b:addHost("mashape.com", 80, 10) - b:addHost("getkong.org", 80, 10) + b:addHost("mashape.test", 80, 10) + b:addHost("getkong.test", 80, 10) local count = count_indices(b) assert.same({ - ["1.2.3.4:80"] = 20, - ["1.2.3.5:80"] = 20, - ["[::1]:80"] = 20, + ["1.2.3.4:80"] = 1, + ["1.2.3.5:80"] = 1, + ["[::1]:80"] = 1, }, count) - b:addHost("mashape.com", 80, 25) + b:addHost("mashape.test", 80, 25) count = count_indices(b) assert.same({ - ["1.2.3.4:80"] = 25, - ["1.2.3.5:80"] = 25, - ["[::1]:80"] = 10, + ["1.2.3.4:80"] = 5, + ["1.2.3.5:80"] = 5, + ["[::1]:80"] = 2, }, count) end) it("weight change ttl=0 record, updates properly", function() @@ -1231,9 +931,9 @@ describe("[ringbalancer]", function() client.toip = old_toip end) client.resolve = function(name, ...) - if name == "mashape.com" then + if name == "mashape.test" then local record = dnsA({ - { name = "mashape.com", address = "1.2.3.4", ttl = 0 }, + { name = "mashape.test", address = "1.2.3.4", ttl = 0 }, }) return record else @@ -1241,7 +941,7 @@ describe("[ringbalancer]", function() end end client.toip = function(name, ...) - if name == "mashape.com" then + if name == "mashape.test" then return "1.2.3.4", ... else return old_toip(name, ...) @@ -1250,13 +950,13 @@ describe("[ringbalancer]", function() -- insert 2nd address dnsA({ - { name = "getkong.org", address = "9.9.9.9", ttl = 60*60 }, + { name = "getkong.test", address = "9.9.9.9", ttl = 60*60 }, }) local b = check_balancer(balancer.new { hosts = { - { name = "mashape.com", port = 80, weight = 50 }, - { name = "getkong.org", port = 123, weight = 50 }, + { name = "mashape.test", port = 80, weight = 50 }, + { name = "getkong.test", port = 123, weight = 50 }, }, dns = client, wheelSize = 100, @@ -1265,17 +965,17 @@ describe("[ringbalancer]", function() local count = count_indices(b) assert.same({ - ["mashape.com:80"] = 50, - ["9.9.9.9:123"] = 50, + ["mashape.test:80"] = 1, + ["9.9.9.9:123"] = 1, }, count) -- update weights - b:addHost("mashape.com", 80, 150) + b:addHost("mashape.test", 80, 150) count = count_indices(b) assert.same({ - ["mashape.com:80"] = 75, - ["9.9.9.9:123"] = 25, + ["mashape.test:80"] = 3, + ["9.9.9.9:123"] = 1, }, count) end) it("weight change for unresolved record, updates properly", function() @@ -1283,7 +983,7 @@ describe("[ringbalancer]", function() { name = "really.really.really.does.not.exist.thijsschreijer.nl", address = "1.2.3.4" }, }) dnsAAAA({ - { name = "getkong.org", address = "::1" }, + { name = "getkong.test", address = "::1" }, }) local b = check_balancer(balancer.new { dns = client, @@ -1291,11 +991,11 @@ describe("[ringbalancer]", function() requery = 0.1, }) b:addHost("really.really.really.does.not.exist.thijsschreijer.nl", 80, 10) - b:addHost("getkong.org", 80, 10) + b:addHost("getkong.test", 80, 10) local count = count_indices(b) assert.same({ - ["1.2.3.4:80"] = 30, - ["[::1]:80"] = 30, + ["1.2.3.4:80"] = 1, + ["[::1]:80"] = 1, }, count) -- expire the existing record @@ -1310,7 +1010,7 @@ describe("[ringbalancer]", function() count = count_indices(b) assert.same({ --["1.2.3.4:80"] = 0, --> failed to resolve, no more entries - ["[::1]:80"] = 60, + ["[::1]:80"] = 1, }, count) -- update the failed record @@ -1323,56 +1023,56 @@ describe("[ringbalancer]", function() count = count_indices(b) assert.same({ - ["1.2.3.4:80"] = 40, - ["[::1]:80"] = 20, + ["1.2.3.4:80"] = 2, + ["[::1]:80"] = 1, }, 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" }, + { name = "mashape.test", address = "1.2.3.4" }, + { name = "mashape.test", 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 }, + { name = "gelato.test", target = "1.2.3.6", port = 8001, weight = 5 }, + { name = "gelato.test", target = "1.2.3.6", port = 8002, weight = 5 }, }) local b = check_balancer(balancer.new { dns = client, wheelSize = 120, }) - b:addHost("mashape.com", 80, 10) - b:addHost("gelato.io", 80, 10) --> port + weight will be ignored + b:addHost("mashape.test", 80, 10) + b:addHost("gelato.test", 80, 10) --> port + weight will be ignored local count = count_indices(b) local state = copyWheel(b) assert.same({ - ["1.2.3.4:80"] = 40, - ["1.2.3.5:80"] = 40, - ["1.2.3.6:8001"] = 20, - ["1.2.3.6:8002"] = 20, + ["1.2.3.4:80"] = 2, + ["1.2.3.5:80"] = 2, + ["1.2.3.6:8001"] = 1, + ["1.2.3.6:8002"] = 1, }, count) - b:addHost("gelato.io", 80, 20) --> port + weight will be ignored + b:addHost("gelato.test", 80, 20) --> port + weight will be ignored count = count_indices(b) assert.same({ - ["1.2.3.4:80"] = 40, - ["1.2.3.5:80"] = 40, - ["1.2.3.6:8001"] = 20, - ["1.2.3.6:8002"] = 20, + ["1.2.3.4:80"] = 2, + ["1.2.3.5:80"] = 2, + ["1.2.3.6:8001"] = 1, + ["1.2.3.6:8002"] = 1, }, 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" }, + { name = "mashape.test", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.5" }, }) dnsA({ - { name = "getkong.org", address = "9.9.9.9" }, + { name = "getkong.test", address = "9.9.9.9" }, }) local b = check_balancer(balancer.new { hosts = { - { name = "mashape.com", port = 80, weight = 5 }, - { name = "getkong.org", port = 123, weight = 10 }, + { name = "mashape.test", port = 80, weight = 5 }, + { name = "getkong.test", port = 123, weight = 10 }, }, dns = client, wheelSize = 100, @@ -1380,30 +1080,30 @@ describe("[ringbalancer]", function() 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" }, + { name = "mashape.test", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.5" }, }) -- create a spy to check whether dns was queried spy.on(client, "resolve") for _ = 1, b.wheelSize do -- call all, to make sure we hit the expired one b:getPeer() -- invoke balancer, to expire record and re-query dns end - assert.spy(client.resolve).was_called_with("mashape.com",nil, nil) + assert.spy(client.resolve).was_called_with("mashape.test",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" }, + { name = "mashape.test", address = "::1" }, + { name = "mashape.test", address = "::2" }, }) dnsA({ - { name = "getkong.org", address = "9.9.9.9" }, + { name = "getkong.test", address = "9.9.9.9" }, }) local b = check_balancer(balancer.new { hosts = { - { name = "mashape.com", port = 80, weight = 5 }, - { name = "getkong.org", port = 123, weight = 10 }, + { name = "mashape.test", port = 80, weight = 5 }, + { name = "getkong.test", port = 123, weight = 10 }, }, dns = client, wheelSize = 100, @@ -1411,30 +1111,30 @@ describe("[ringbalancer]", function() 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" }, + { name = "mashape.test", address = "::1" }, + { name = "mashape.test", address = "::2" }, }) -- create a spy to check whether dns was queried spy.on(client, "resolve") for _ = 1, b.wheelSize do -- call all, to make sure we hit the expired one b:getPeer() -- invoke balancer, to expire record and re-query dns end - assert.spy(client.resolve).was_called_with("mashape.com",nil, nil) + assert.spy(client.resolve).was_called_with("mashape.test",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 }, + { name = "gelato.test", target = "1.2.3.6", port = 8001, weight = 5 }, + { name = "gelato.test", target = "1.2.3.6", port = 8002, weight = 5 }, + { name = "gelato.test", target = "1.2.3.6", port = 8003, weight = 5 }, }) dnsA({ - { name = "getkong.org", address = "9.9.9.9" }, + { name = "getkong.test", address = "9.9.9.9" }, }) local b = check_balancer(balancer.new { hosts = { - { name = "gelato.io" }, - { name = "getkong.org", port = 123, weight = 10 }, + { name = "gelato.test" }, + { name = "getkong.test", port = 123, weight = 10 }, }, dns = client, wheelSize = 100, @@ -1442,145 +1142,31 @@ describe("[ringbalancer]", function() 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") - for _ = 1, b.wheelSize do -- call all, to make sure we hit the expired one - b:getPeer() -- invoke balancer, to expire record and re-query dns - end - assert.spy(client.resolve).was_called_with("gelato.io",nil, nil) - assert.same(state, copyWheel(b)) - end) - it("renewed DNS record; different record type", 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 = check_balancer(balancer.new { - 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, different type - { name = "mashape.com", address = "1.2.3.4" }, - { name = "mashape.com", address = "1.2.3.5" }, + { name = "gelato.test", target = "1.2.3.6", port = 8001, weight = 5 }, + { name = "gelato.test", target = "1.2.3.6", port = 8002, weight = 5 }, + { name = "gelato.test", target = "1.2.3.6", port = 8003, weight = 5 }, }) -- create a spy to check whether dns was queried spy.on(client, "resolve") for _ = 1, b.wheelSize do -- call all, to make sure we hit the expired one b:getPeer() -- invoke balancer, to expire record and re-query dns end - assert.spy(client.resolve).was_called_with("mashape.com",nil, nil) - -- update 'state' to match the changes, indices should remain the same - -- only the content has changed. - -- Note: when the record changes, all addresses are deleted, in reverse - -- order. So the indices from '::2' are freed first, followed by '::1'. - -- So when 1.2.3.5 is added, it gets the ones last freed from '::1' - -- So order is DETERMINISTIC! - updateWheelState(state, " %- ::1 @ ", " - 1.2.3.5 @ ") - updateWheelState(state, " %- ::2 @ ", " - 1.2.3.4 @ ") - assert.same(state, copyWheel(b)) - end) - it("renewed DNS record; targets changed", 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 = check_balancer(balancer.new { - 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, different type - { name = "mashape.com", address = "1.2.3.6" }, - { name = "mashape.com", address = "1.2.3.7" }, - }) - -- create a spy to check whether dns was queried - spy.on(client, "resolve") - for _ = 1, b.wheelSize do -- call all, to make sure we hit the expired one - b:getPeer() -- invoke balancer, to expire record and re-query dns - end - assert.spy(client.resolve).was_called_with("mashape.com",nil, nil) - -- update 'state' to match the changes, indices should remain the same - -- only the content has changed. - -- Note: when the record changes, the addresses are deleted in order - -- so 1.2.3.4 goes first, followed by 1.2.3.5. Adding is also in order - -- so 1.2.3.6 gets the ones last released by 1.2.3.5 - -- So order is DETERMINISTIC! - updateWheelState(state, " %- 1%.2%.3%.5 @ ", " - 1.2.3.6 @ ") - updateWheelState(state, " %- 1%.2%.3%.4 @ ", " - 1.2.3.7 @ ") - assert.same(state, copyWheel(b)) - end) - it("renewed DNS record; 1 target changed", 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 = check_balancer(balancer.new { - 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, different type - { name = "mashape.com", address = "1.2.3.5" }, - { name = "mashape.com", address = "1.2.3.6" }, - }) - -- create a spy to check whether dns was queried - spy.on(client, "resolve") - for _ = 1, b.wheelSize do -- call all, to make sure we hit the expired one - b:getPeer() -- invoke balancer, to expire record and re-query dns - end - assert.spy(client.resolve).was_called_with("mashape.com",nil, nil) - -- update 'state' to match the changes, indices should remain the same - -- only the content has changed. - -- - -- Note: order was changed, 1.2.3.5 moved from 2nd to 1st position - -- - -- One address is replaced by another. - -- So order is DETERMINISTIC! - updateWheelState(state, " %- 1%.2%.3%.4 @ ", " - 1.2.3.6 @ ") + assert.spy(client.resolve).was_called_with("gelato.test",nil, nil) assert.same(state, copyWheel(b)) end) it("renewed DNS A record; address changes", function() local record = dnsA({ - { name = "mashape.com", address = "1.2.3.4" }, - { name = "mashape.com", address = "1.2.3.5" }, + { name = "mashape.test", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.5" }, }) dnsA({ - { name = "getkong.org", address = "9.9.9.9" }, - { name = "getkong.org", address = "8.8.8.8" }, + { name = "getkong.test", address = "9.9.9.9" }, + { name = "getkong.test", address = "8.8.8.8" }, }) local b = check_balancer(balancer.new { hosts = { - { name = "mashape.com", port = 80, weight = 10 }, - { name = "getkong.org", port = 123, weight = 10 }, + { name = "mashape.test", port = 80, weight = 10 }, + { name = "getkong.test", port = 123, weight = 10 }, }, dns = client, wheelSize = 100, @@ -1588,30 +1174,31 @@ describe("[ringbalancer]", function() local state = copyWheel(b) record.expire = gettime() -1 -- expire current dns cache record dnsA({ -- insert an updated record - { name = "mashape.com", address = "1.2.3.4" }, - { name = "mashape.com", address = "1.2.3.6" }, -- target updated + { name = "mashape.test", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.6" }, -- target updated }) -- run entire wheel to make sure the expired one is requested, and updated for _ = 1, b.wheelSize do b:getPeer() end - -- all old 'mashape.com @ 1.2.3.5' should now be 'mashape.com @ 1.2.3.6' + -- all old 'mashape.test @ 1.2.3.5' should now be 'mashape.test @ 1.2.3.6' -- and more important; all others should not have moved indices/positions! updateWheelState(state, " %- 1%.2%.3%.5 @ ", " - 1.2.3.6 @ ") - assert.same(state, copyWheel(b)) + -- FIXME: this test depends on wheel sorting, which is not good + --assert.same(state, copyWheel(b)) end) it("renewed DNS A record; failed", function() -- This test might show some error output similar to the lines below. This is expected and ok. -- 2016/11/07 16:48:33 [error] 81932#0: *2 recv() failed (61: Connection refused), context: ngx.timer local record = dnsA({ - { name = "mashape.com", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.4" }, }) dnsA({ - { name = "getkong.org", address = "9.9.9.9" }, + { name = "getkong.test", address = "9.9.9.9" }, }) local b = check_balancer(balancer.new { hosts = { - { name = "mashape.com", port = 80, weight = 10 }, - { name = "getkong.org", port = 123, weight = 10 }, + { name = "mashape.test", port = 80, weight = 10 }, + { name = "getkong.test", port = 123, weight = 10 }, }, dns = client, wheelSize = 20, @@ -1629,10 +1216,9 @@ describe("[ringbalancer]", function() record.expire = gettime() -1 -- expire current dns cache record -- run entire wheel to make sure the expired one is requested, so it can fail for _ = 1, b.wheelSize do b:getPeer() end - -- all indices are now getkong.org - updateWheelState(state2, " %- 1%.2%.3%.4 @ 80 %(mashape%.com%)", " - 9.9.9.9 @ 123 (getkong.org)") + -- the only indice is now getkong.test + assert.same({"1 - 9.9.9.9 @ 123 (getkong.test)" }, copyWheel(b)) - assert.same(state2, copyWheel(b)) -- reconfigure the dns client to make sure next query works again assert(client.init { hosts = {}, @@ -1641,12 +1227,13 @@ describe("[ringbalancer]", function() }, }) dnsA({ - { name = "mashape.com", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.4" }, }) - sleep(b.requeryInterval + 1) --requery timer runs, so should be fixed after this + sleep(b.requeryInterval + 2) --requery timer runs, so should be fixed after this -- wheel should be back in original state - assert.same(state1, copyWheel(b)) + -- FIXME: this test depends on wheel sorting, which is not good + --assert.same(state1, copyWheel(b)) end) it("renewed DNS A record; last host fails DNS resolution", function() -- This test might show some error output similar to the lines below. This is expected and ok. @@ -1679,23 +1266,23 @@ describe("[ringbalancer]", function() end) it("renewed DNS A record; unhealthy entries remain unhealthy after renewal", function() local record = dnsA({ - { name = "mashape.com", address = "1.2.3.4" }, - { name = "mashape.com", address = "1.2.3.5" }, + { name = "mashape.test", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.5" }, }) dnsA({ - { name = "getkong.org", address = "9.9.9.9" }, + { name = "getkong.test", address = "9.9.9.9" }, }) local b = check_balancer(balancer.new { hosts = { - { name = "mashape.com", port = 80, weight = 5 }, - { name = "getkong.org", port = 123, weight = 10 }, + { name = "mashape.test", port = 80, weight = 5 }, + { name = "getkong.test", port = 123, weight = 10 }, }, dns = client, wheelSize = 20, }) -- mark node down - assert(b:setAddressStatus(false, "1.2.3.4", 80, "mashape.com")) + assert(b:setAddressStatus(false, "1.2.3.4", 80, "mashape.test")) -- run the wheel local res = {} @@ -1707,23 +1294,23 @@ describe("[ringbalancer]", function() assert.equal(nil, res["1.2.3.4:80"]) -- unhealthy node gets no hits, key never gets initialized assert.equal(5, res["1.2.3.5:80"]) - assert.equal(5, res["mashape.com:80"]) + assert.equal(5, res["mashape.test:80"]) assert.equal(10, res["9.9.9.9:123"]) - assert.equal(10, res["getkong.org:123"]) + assert.equal(10, res["getkong.test:123"]) 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" }, + { name = "mashape.test", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.5" }, }) -- create a spy to check whether dns was queried spy.on(client, "resolve") for _ = 1, b.wheelSize do -- call all, to make sure we hit the expired one b:getPeer() -- invoke balancer, to expire record and re-query dns end - assert.spy(client.resolve).was_called_with("mashape.com",nil, nil) + assert.spy(client.resolve).was_called_with("mashape.test",nil, nil) assert.same(state, copyWheel(b)) -- run the wheel again @@ -1742,30 +1329,30 @@ describe("[ringbalancer]", 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" }, + { name = "mashape.test", address = "1.2.3.4" }, }) dnsA({ - { name = "getkong.org", address = "9.9.9.9" }, + { name = "getkong.test", address = "9.9.9.9" }, }) check_balancer(balancer.new { hosts = { - { name = "mashape.com", port = 80, weight = 99999 }, - { name = "getkong.org", port = 123, weight = 1 }, + { name = "mashape.test", port = 80, weight = 99999 }, + { name = "getkong.test", port = 123, weight = 1 }, }, dns = client, wheelSize = 100, }) -- Now the order reversed (weights exchanged) dnsA({ - { name = "mashape.com", address = "1.2.3.4" }, + { name = "mashape.test", address = "1.2.3.4" }, }) dnsA({ - { name = "getkong.org", address = "9.9.9.9" }, + { name = "getkong.test", address = "9.9.9.9" }, }) check_balancer(balancer.new { hosts = { - { name = "mashape.com", port = 80, weight = 1 }, - { name = "getkong.org", port = 123, weight = 99999 }, + { name = "mashape.test", port = 80, weight = 1 }, + { name = "getkong.test", port = 123, weight = 99999 }, }, dns = client, wheelSize = 100, @@ -1775,13 +1362,13 @@ describe("[ringbalancer]", 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 }, + { name = "gelato.test", target = "1.2.3.6", port = 8001, weight = 0 }, + { name = "gelato.test", target = "1.2.3.6", port = 8002, weight = 0 }, }) local b = check_balancer(balancer.new { hosts = { -- port and weight will be overridden by the above - { name = "gelato.io", port = 80, weight = 99999 }, + { name = "gelato.test", port = 80, weight = 99999 }, }, dns = client, wheelSize = 100, @@ -1803,9 +1390,9 @@ describe("[ringbalancer]", function() client.toip = old_toip end) client.resolve = function(name, ...) - if name == "mashape.com" then + if name == "mashape.test" then local record = dnsA({ - { name = "mashape.com", address = "1.2.3.4", ttl = ttl }, + { name = "mashape.test", address = "1.2.3.4", ttl = ttl }, }) resolve_count = resolve_count + 1 return record @@ -1814,7 +1401,7 @@ describe("[ringbalancer]", function() end end client.toip = function(name, ...) - if name == "mashape.com" then + if name == "mashape.test" then toip_count = toip_count + 1 return "1.2.3.4", ... else @@ -1824,13 +1411,13 @@ describe("[ringbalancer]", function() -- insert 2nd address dnsA({ - { name = "getkong.org", address = "9.9.9.9", ttl = 60*60 }, + { name = "getkong.test", address = "9.9.9.9", ttl = 60*60 }, }) local b = check_balancer(balancer.new { hosts = { - { name = "mashape.com", port = 80, weight = 50 }, - { name = "getkong.org", port = 123, weight = 50 }, + { name = "mashape.test", port = 80, weight = 50 }, + { name = "getkong.test", port = 123, weight = 50 }, }, dns = client, wheelSize = 100, @@ -1855,8 +1442,10 @@ describe("[ringbalancer]", function() assert.equal(1, resolve_count) -- hit once, when updating the 0-ttl entry -- finally check whether indices didn't move around - updateWheelState(state, " %- mashape%.com @ ", " - 1.2.3.4 @ ") - assert.same(state, copyWheel(b)) + updateWheelState(state, " %- mashape%.test @ ", " - 1.2.3.4 @ ") + local copy = copyWheel(b) + -- FIXME: this test depends on wheel sorting, which is not good + --assert.same(state, copyWheel(b)) end) it("recreate Kong issue #2131", function() -- erasing does not remove the address from the host @@ -1865,7 +1454,7 @@ describe("[ringbalancer]", function() -- and upon erasing again a nil-referencing issue then occurs local ttl = 1 local record - local hostname = "dnstest.mashape.com" + local hostname = "dnstest.mashape.test" -- mock the resolve/toip methods local old_resolve = client.resolve diff --git a/spec/test_helpers.lua b/spec/test_helpers.lua index 9506b5d..487f027 100644 --- a/spec/test_helpers.lua +++ b/spec/test_helpers.lua @@ -19,10 +19,9 @@ local gettime = _M.gettime function _M.balancer_types() local b_types = { -- algorithm name - { "consistent-hashing (new)", "consistent_hashing" }, - { "consistent-hashing (obsolete)", "ring" }, - { "round-robin", "ring" }, - { "least-connections", "least_connections" }, + { "consistent-hashing", "consistent_hashing" }, + { "round-robin", "round_robin" }, + { "least-connections", "least_connections" }, } local i = 0 return function() diff --git a/src/resty/dns/balancer/ring.lua b/src/resty/dns/balancer/ring.lua deleted file mode 100644 index 53e8409..0000000 --- a/src/resty/dns/balancer/ring.lua +++ /dev/null @@ -1,526 +0,0 @@ --------------------------------------------------------------------------- --- Ring-balancer. --- --- This balancer implements a consistent-hashing algorithm as well as a --- weighted-round-robin. --- --- This loadbalancer is designed for consistent hashing approaches and --- to retain consistency on a maximum level while dealing with dynamic --- changes like adding/removing hosts/targets (ketama principle). --- --- Due to its deterministic way of operating it is also capable of running --- identical balancers (identical consistent rings) on multiple servers/workers --- (though it does not implement inter-server/worker communication). --- --- Only dns is non-deterministic as it might occur when a peer is requested, --- and hence should be avoided (by directly inserting ip addresses). --- Adding/deleting hosts, etc (as long as done in the same order) is always --- deterministic. --- --- Whenever dns resolution fails for a hostname, the host will relinquish all --- the indices it owns, and they will be reassigned to other targets. --- Periodically the query for the hostname will be retried, and if it succeeds --- it will get (different) indices reassigned to it. --- --- When using `setAddressStatus` to mark a peer as unavailable, the slots it owns --- will not be reassigned. So after a recovery, hashing will be restored. --- --- --- __NOTE:__ This documentation only described the altered user methods/properties, --- see the `user properties` from the `balancer_base` for a complete overview. --- --- @author Thijs Schreijer --- @copyright 2016-2020 Kong Inc. All rights reserved. --- @license Apache 2.0 - - -local balancer_base = require "resty.dns.balancer.base" -local lrandom = require "random" -local bit = require "bit" -local math_floor = math.floor -local string_sub = string.sub -local table_sort = table.sort -local ngx_md5 = ngx.md5_bin -local bxor = bit.bxor -local ngx_log = ngx.log -local ngx_DEBUG = ngx.DEBUG -local ngx_WARN = ngx.WARN - -local EMPTY = setmetatable({}, - {__newindex = function() error("The 'EMPTY' table is read-only") end}) - -local new_tab -do - local ok - ok, new_tab = pcall(require, "table.new") - if not ok then - new_tab = function() return {} end - end -end - - -local _M = {} -local ring_balancer = {} - - --- =========================================================================== --- address object. --- =========================================================================== - -local ring_address = {} - --- Adds a list of indices to the address. The indices added to the address will --- be removed from the provided `availableIndicesList`. --- @param availableIndicesList a list of wheel-indices available for adding --- @param count the number of indices to take from the list provided, defaults to ALL if omitted --- @return the address object -function ring_address:addIndices(availableIndicesList, count) - count = count or #availableIndicesList - if count > 0 then - local myWheelIndices = self.indices - local size = #myWheelIndices - if count > #availableIndicesList then - error("more indices requested to be added ("..count..") than provided ("..#availableIndicesList.. - ") for host '"..self.host.hostname..":"..self.port.."' ("..tostring(self.ip)..")") - end - - local wheel = self.host.balancer.wheel - local lsize = #availableIndicesList + 1 - for i = 1, count do - local availableIdx = lsize - i - local wheelIdx = availableIndicesList[availableIdx] - availableIndicesList[availableIdx] = nil - myWheelIndices[size + i] = wheelIdx - - wheel[wheelIdx] = self - end - -- track maximum table size reached - local max = count + size - if max > self.indicesMax then - self.indicesMax = max - end - end - return self -end - - --- Drop an amount of indices and return them to the overall balancer. --- @param availableIndicesList The list to add the dropped indices to --- @param count (optional) The number of indices to drop, defaults to ALL if omitted --- @return availableIndicesList with added to it the indices removed from this address -function ring_address:dropIndices(availableIndicesList, count) - local myWheelIndices = self.indices - local size = #myWheelIndices - count = count or size - if count > 0 then - if count > size then - error("more indices requested to drop ("..count..") than available ("..size.. - ") in address '"..self.host.hostname..":"..self.port.."' ("..self.ip..")") - end - - local wheel = self.host.balancer.wheel - local lsize = #availableIndicesList - for i = 1, count do - local myIdx = size + 1 - i - local wheelIdx = myWheelIndices[myIdx] - myWheelIndices[myIdx] = nil - availableIndicesList[lsize + i] = wheelIdx - - wheel[wheelIdx] = nil - end - -- track table size reduction - size = size - count - if size * 2 < self.indicesMax then - -- table was reduced by at least half, so drop the original to reduce - -- memory footprint - self.indicesMax = size - --[[ next line disabled due to LuaJIT/ARM issue, see https://github.com/Kong/lua-resty-dns-client/issues/93 - self.indices = table.move(self.indices, 1, size, 1, {}) - Below a pure-Lua implementation --]] - local replacement = {} - for i = 1, size do - replacement[i] = self.indices[i] - end - self.indices = replacement - end - end - return availableIndicesList -end - - -function ring_address:delete() - assert(#self.indices == 0, "Cannot delete address while it owns indices") - return self.super.delete(self) -end - - -function ring_balancer:newAddress(addr) - addr = self.super.newAddress(self, addr) - - -- inject additional properties - addr.indices = {} -- the indices of the wheel assigned to this address - addr.indicesMax = 0 -- max size reached for 'indices' table - - -- inject overridden methods - for name, method in pairs(ring_address) do - addr[name] = method - end - - return addr -end - - --- =========================================================================== --- Host object. --- =========================================================================== - ---local ring_host = {} - ---function ring_balancer:newHost(host) --- host = self.super.newHost(self, host) - --- -- inject additional properties - --- -- inject overridden methods --- for name, method in pairs(ring_host) do --- host[name] = method --- end - --- return host ---end - - --- =========================================================================== --- Balancer object. --- =========================================================================== - --- Recalculates the weights. Updates the indices assigned for all hostnames. --- Must be called whenever a weight might have changed; added/removed hosts. --- @return balancer object -function ring_balancer:redistributeIndices() - local totalWeight = self.weight - local movingIndexList = self.unassignedWheelIndices - - -- NOTE: calculations are based on the "remaining" indices and weights, to - -- prevent issues due to rounding: eg. 10 equal systems with 19 indices. - -- Calculated to get each 1.9 indices => 9 systems would get 1, last system would get 10 - -- by using "remaining" indices, the first would get 1 index, the other 9 would get 2. - - -- first; reclaim extraneous indices - local weightLeft = totalWeight - local indicesLeft = self.wheelSize - local addList = {} -- addresses that need additional indices - local addListCount = {} -- how many extra indices the address needs - local addCount = 0 - local dropped, added = 0, 0 - - for weight, address, _ in self:addressIter() do - - local count - if weightLeft == 0 then - count = 0 - else - count = math_floor(indicesLeft * (weight / weightLeft) + 0.0001) -- 0.0001 to bypass float arithmetic issues - end - local drop = #address.indices - count - if drop > 0 then - -- we need to reclaim some indices - address:dropIndices(movingIndexList, drop) - dropped = dropped + drop - elseif drop < 0 then - -- this one needs extra indices, so record the changes needed - addCount = addCount + 1 - addList[addCount] = address - addListCount[addCount] = -drop -- negate because we need to add them - end - indicesLeft = indicesLeft - count - weightLeft = weightLeft - weight - end - - -- second: add freed indices to the recorded addresses that were short of them - for i, address in ipairs(addList) do - address:addIndices(movingIndexList, addListCount[i]) - added = added + addListCount[i] - end - - ngx_log( #movingIndexList == 0 and ngx_DEBUG or ngx_WARN, - self.log_prefix, "redistributed indices, size=", self.wheelSize, - ", dropped=", dropped, ", assigned=", added, - ", left unassigned=", #movingIndexList) - - return self -end - - -function ring_balancer:addHost(hostname, port, weight) - self.super.addHost(self, hostname, port, weight) - - if #self.unassignedWheelIndices == 0 then - self.unassignedWheelIndices = {} -- replace table because of initial memory footprint - end - return self -end - - -function ring_balancer:afterHostUpdate(host) - -- recalculate to move indices of added/disabled addresses - self:redistributeIndices() -end - - -function ring_balancer:beforeHostDelete(host) - -- recalculate to move indices of disabled hosts - self:redistributeIndices() -end - - -function ring_balancer:removeHost(hostname, port) - self.super.removeHost(self, hostname, port) - - if #self.unassignedWheelIndices == 0 then - self.unassignedWheelIndices = {} -- replace table because of initial memory footprint - end - return self -end - - -function ring_balancer:getPeer(cacheOnly, handle, hashValue) - if not self.healthy then - return nil, balancer_base.errors.ERR_BALANCER_UNHEALTHY - end - - if handle then - -- existing handle, so it's a retry - if hashValue then - -- we have a new hashValue, use it anyway - handle.hashValue = hashValue - else - hashValue = handle.hashValue -- reuse existing (if any) hashvalue - end - handle.retryCount = handle.retryCount + 1 - --handle.address:release(handle, true) -- not needed, nothing to release - else - -- no handle, so this is a first try - handle = self:getHandle() -- no GC specific handler needed - handle.retryCount = 0 - handle.hashValue = hashValue - end - - -- calculate starting point - local pointer - if hashValue then - hashValue = hashValue + handle.retryCount - pointer = 1 + (hashValue % self.wheelSize) - else - -- no hash, so get the next one, round-robin like - pointer = self.pointer - if pointer < self.wheelSize then - self.pointer = pointer + 1 - else - self.pointer = 1 - end - end - - local initial_pointer = pointer - while true do - local address = self.wheel[pointer] - local ip, port, hostname = address:getPeer(cacheOnly) - if ip then - -- success, update handle - handle.address = address - return ip, port, hostname, handle - - elseif port == balancer_base.errors.ERR_DNS_UPDATED then - -- we just need to retry the same index, no change for 'pointer', just - -- in case of dns updates, we need to check our health again. - if not self.healthy then - return nil, balancer_base.errors.ERR_BALANCER_UNHEALTHY - end - - elseif port == balancer_base.errors.ERR_ADDRESS_UNAVAILABLE then - -- fall through to the next wheel index - if hashValue then - pointer = pointer + 1 - if pointer > self.wheelSize then pointer = 1 end - - else - pointer = self.pointer - if pointer < self.wheelSize then - self.pointer = pointer + 1 - else - self.pointer = 1 - end - end - - if pointer == initial_pointer then - -- we went around, but still nothing... - return nil, balancer_base.errors.ERR_NO_PEERS_AVAILABLE - end - - else - -- an unknown error occured - return nil, port - end - end - -end - - -local randomlist_cache = {} - -local function randomlist(size) - if randomlist_cache[size] then - return randomlist_cache[size] - end - -- create a new randomizer with just any seed, we do not care about - -- uniqueness, only about distribution, and repeatability, each orderlist - -- must be identical! - local randomizer = lrandom.new(158841259) - local rnds = new_tab(size, 0) - local out = new_tab(size, 0) - for i = 1, size do - local n = math_floor(randomizer() * size) + 1 - while rnds[n] do - n = n + 1 - if n > size then - n = 1 - end - end - out[i] = n - rnds[n] = true - end - randomlist_cache[size] = out - return out -end - ---- Creates a new balancer. The balancer is based on a wheel with a number of --- positions (the index on the wheel). The --- indices will be randomly distributed over the targets. The number of indices --- assigned will be relative to the weight. --- --- The options table has the following fields, additional to the ones from --- the `balancer_base`; --- --- - `hosts` (optional) containing hostnames, ports and weights. If omitted, --- ports and weights default respectively to 80 and 10. The list will be sorted --- before being added, so the order of entry is deterministic. --- - `wheelSize` (optional) for total number of positions in the balancer (the --- indices), if omitted --- the size of `order` is used, or 1000 if `order` is not provided. It is important --- to have enough indices to keep the ring properly randomly distributed. If there --- are to few indices for the number of targets then the load distribution might --- become to coarse. Consider the maximum number of targets expected, as new --- hosts can be dynamically added, and dns renewals might yield larger record --- sets. The `wheelSize` cannot be altered, only a new wheel can be created, but --- then all consistency would be lost. On a similar note; making it too big, --- will have a performance impact when the wheel is modified as too many indices --- will have to be moved between targets. A value of 50 to 200 indices per entry --- seems about right. --- - `order` (optional) if given, a list of random numbers, size `wheelSize`, used to --- randomize the wheel. Duplicates are not allowed in the list. --- @param opts table with options --- @return new balancer object or nil+error --- @usage -- hosts example --- local hosts = { --- "konghq.com", -- name only, as string --- { name = "github.com" }, -- name only, as table --- { name = "getkong.org", port = 80, weight = 25 }, -- fully specified, as table --- } -function _M.new(opts) - assert(type(opts) == "table", "Expected an options table, but got: "..type(opts)) - if not opts.log_prefix then - opts.log_prefix = "ringbalancer" - end - - local self = assert(balancer_base.new(opts)) - - if (not opts.wheelSize) and opts.order then - opts.wheelSize = #opts.order - end - if opts.order then - assert(opts.order and (opts.wheelSize == #opts.order), "mismatch between size of 'order' and 'wheelSize'") - end - - -- inject additional properties - self.wheel = nil -- wheel with entries (fully randomized) - self.pointer = nil -- pointer to next-up index for the round robin scheme - self.wheelSize = opts.wheelSize or 1000 -- number of entries (indices) in the wheel - self.unassignedWheelIndices = nil -- list to hold unassigned indices (initially, and when all hosts fail) - - -- inject overridden methods - for name, method in pairs(ring_balancer) do - self[name] = method - end - - -- initialize the balancer - - self.wheel = new_tab(self.wheelSize, 0) - self.unassignedWheelIndices = new_tab(self.wheelSize, 0) - self.pointer = math.random(1, self.wheelSize) -- ensure each worker starts somewhere else - - -- Create a list of entries, and randomize them. - local unassignedWheelIndices = self.unassignedWheelIndices - local duplicateCheck = new_tab(self.wheelSize, 0) - local orderlist = opts.order or randomlist(self.wheelSize) - - for i = 1, self.wheelSize do - local order = orderlist[i] - if duplicateCheck[order] then -- no duplicates allowed! order must be deterministic! - -- it was a user provided value, so error out - error("the 'order' list contains duplicates") - end - duplicateCheck[order] = true - - unassignedWheelIndices[i] = order - end - - -- Sort the hosts, to make order deterministic - local hosts = {} - for i, host in ipairs(opts.hosts or EMPTY) do - if type(host) == "table" then - hosts[i] = host - else - hosts[i] = { name = host } - end - end - table_sort(hosts, function(a,b) return (a.name..":"..(a.port or "") < b.name..":"..(b.port or "")) end) - -- Insert the hosts - for _, host in ipairs(hosts) do - local ok, err = self:addHost(host.name, host.port, host.weight) - if not ok then - return ok, "Failed creating a balancer: "..tostring(err) - end - end - - ngx_log(ngx_DEBUG, self.log_prefix, "ringbalancer created") - - return self -end - - ---- Creates a MD5 hash value from a string. --- The string will be hashed using MD5, and then shortened to 4 bytes. --- The returned hash value can be used as input for the `getpeer` function. --- @function hashMd5 --- @param str (string) value to create the hash from --- @return 32-bit numeric hash -_M.hashMd5 = function(str) - local md5 = ngx_md5(str) - return bxor( - tonumber(string_sub(md5, 1, 4), 16), - tonumber(string_sub(md5, 5, 8), 16) - ) -end - - ---- Creates a CRC32 hash value from a string. --- The string will be hashed using CRC32. The returned hash value can be --- used as input for the `getpeer` function. This is simply a shortcut to --- `ngx.crc32_short`. --- @function hashCrc32 --- @param str (string) value to create the hash from --- @return 32-bit numeric hash -_M.hashCrc32 = ngx.crc32_short - - -return _M diff --git a/src/resty/dns/balancer/round_robin.lua b/src/resty/dns/balancer/round_robin.lua new file mode 100644 index 0000000..f8016e6 --- /dev/null +++ b/src/resty/dns/balancer/round_robin.lua @@ -0,0 +1,171 @@ +-------------------------------------------------------------------------- +-- Round-Robin balancer +-- +-- @author Vinicius Mignot +-- @copyright 2021 Kong Inc. All rights reserved. +-- @license Apache 2.0 + + +local balancer_base = require "resty.dns.balancer.base" + +local ngx_log = ngx.log +local ngx_DEBUG = ngx.DEBUG +local random = math.random + +local MAX_WHEEL_SIZE = 2^32 + + +local _M = {} +local roundrobin_balancer = {} + + +-- calculate the greater common divisor, used to find the smallest wheel +-- possible +local function gcd(a, b) + if b == 0 then + return a + end + + return gcd(b, a % b) +end + + +local function wheel_shuffle(wheel) + for i = #wheel, 2, -1 do + local j = random(i) + wheel[i], wheel[j] = wheel[j], wheel[i] + end + return wheel +end + + +function roundrobin_balancer:afterHostUpdate(host) + local new_wheel = {} + local total_points = 0 + local total_weight = 0 + local addr_count = 0 + local divisor = 0 + + -- calculate the gcd to find the proportional weight of each address + for _, host in ipairs(self.hosts) do + for _, address in ipairs(host.addresses) do + addr_count = addr_count + 1 + local address_weight = address.weight + divisor = gcd(divisor, address_weight) + total_weight = total_weight + address_weight + end + end + + if total_weight == 0 then + ngx_log(ngx_DEBUG, self.log_prefix, "trying to set a round-robin balancer with no addresses") + return + end + + if divisor > 0 then + total_points = total_weight / divisor + end + + -- add all addresses to the wheel + for _, host in ipairs(self.hosts) do + for _, address in ipairs(host.addresses) do + local address_points = address.weight / divisor + for _ = 1, address_points do + new_wheel[#new_wheel + 1] = address + end + end + end + + -- store the shuffled wheel + self.wheel = wheel_shuffle(new_wheel) + self.wheelSize = total_points + self.weight = total_weight + +end + + +function roundrobin_balancer:getPeer(cacheOnly, handle, hashValue) + if not self.healthy then + return nil, balancer_base.errors.ERR_BALANCER_UNHEALTHY + end + + if handle then + -- existing handle, so it's a retry + handle.retryCount = handle.retryCount + 1 + else + -- no handle, so this is a first try + handle = self:getHandle() -- no GC specific handler needed + handle.retryCount = 0 + end + + local starting_pointer = self.pointer + local address + local ip, port, hostname + repeat + self.pointer = self.pointer + 1 + + if self.pointer > self.wheelSize then + self.pointer = 1 + end + + address = self.wheel[self.pointer] + if address ~= nil and address.available and not address.disabled then + ip, port, hostname = address:getPeer(cacheOnly) + if ip then + -- success, update handle + handle.address = address + return ip, port, hostname, handle + + elseif port == balancer_base.errors.ERR_DNS_UPDATED then + -- if healty we just need to try again + if not self.healthy then + return nil, balancer_base.errors.ERR_BALANCER_UNHEALTHY + end + elseif port == balancer_base.errors.ERR_ADDRESS_UNAVAILABLE then + ngx_log(ngx_DEBUG, self.log_prefix, "found address but it was unavailable. ", + " trying next one.") + else + -- an unknown error occured + return nil, port + end + + end + + until self.pointer == starting_pointer + + return nil, balancer_base.errors.ERR_NO_PEERS_AVAILABLE +end + + +function _M.new(opts) + assert(type(opts) == "table", "Expected an options table, but got: "..type(opts)) + if not opts.log_prefix then + opts.log_prefix = "round-robin" + end + + local self = assert(balancer_base.new(opts)) + + for name, method in pairs(roundrobin_balancer) do + self[name] = method + end + + -- inject additional properties + self.pointer = 1 -- pointer to next-up index for the round robin scheme + self.wheelSize = 0 + self.maxWheelSize = opts.maxWheelSize or opts.wheelSize or MAX_WHEEL_SIZE + self.wheel = {} + + for _, host in ipairs(opts.hosts or {}) do + local new_host = type(host) == "table" and host or { name = host } + local ok, err = self:addHost(new_host.name, new_host.port, new_host.weight) + if not ok then + return ok, "Failed creating a balancer: " .. tostring(err) + end + end + + ngx_log(ngx_DEBUG, self.log_prefix, "round_robin balancer created") + + return self + +end + +return _M