mirror of
https://github.com/bjc/prosody.git
synced 2025-04-01 20:27:39 +03:00
In this API, a 'node' is always a simple text string. Sometimes the caller may have a more complex structure representing a node, but the hash ring is really only concerned with the node's name. This API change allows :add_nodes() to take a table of `node_name = value` pairs, as well as the simple array of node names previously accepted. The 'value' of the selected node is returned as a new second result from :get_node(). If no value is passed when a node is added, it defaults to `true` (as before, but this was never previously exposed).
93 lines
2.4 KiB
Lua
93 lines
2.4 KiB
Lua
local hashring = require "util.hashring";
|
|
|
|
describe("util.hashring", function ()
|
|
randomize(false);
|
|
|
|
local sha256 = require "util.hashes".sha256;
|
|
|
|
local ring = hashring.new(128, sha256);
|
|
|
|
it("should fail to get a node that does not exist", function ()
|
|
assert.is_nil(ring:get_node("foo"))
|
|
end);
|
|
|
|
it("should support adding nodes", function ()
|
|
ring:add_node("node1");
|
|
end);
|
|
|
|
it("should return a single node for all keys if only one node exists", function ()
|
|
for i = 1, 100 do
|
|
assert.is_equal("node1", ring:get_node(tostring(i)))
|
|
end
|
|
end);
|
|
|
|
it("should support adding a second node", function ()
|
|
ring:add_node("node2");
|
|
end);
|
|
|
|
it("should fail to remove a non-existent node", function ()
|
|
assert.is_falsy(ring:remove_node("node3"));
|
|
end);
|
|
|
|
it("should succeed to remove a node", function ()
|
|
assert.is_truthy(ring:remove_node("node1"));
|
|
end);
|
|
|
|
it("should return the only node for all keys", function ()
|
|
for i = 1, 100 do
|
|
assert.is_equal("node2", ring:get_node(tostring(i)))
|
|
end
|
|
end);
|
|
|
|
it("should support adding multiple nodes", function ()
|
|
ring:add_nodes({ "node1", "node3", "node4", "node5" });
|
|
end);
|
|
|
|
it("should disrupt a minimal number of keys on node removal", function ()
|
|
local orig_ring = ring:clone();
|
|
local node_tallies = {};
|
|
|
|
local n = 1000;
|
|
|
|
for i = 1, n do
|
|
local key = tostring(i);
|
|
local node = ring:get_node(key);
|
|
node_tallies[node] = (node_tallies[node] or 0) + 1;
|
|
end
|
|
|
|
--[[
|
|
for node, key_count in pairs(node_tallies) do
|
|
print(node, key_count, ("%.2f%%"):format((key_count/n)*100));
|
|
end
|
|
]]
|
|
|
|
ring:remove_node("node5");
|
|
|
|
local disrupted_keys = 0;
|
|
for i = 1, n do
|
|
local key = tostring(i);
|
|
if orig_ring:get_node(key) ~= ring:get_node(key) then
|
|
disrupted_keys = disrupted_keys + 1;
|
|
end
|
|
end
|
|
assert.is_equal(node_tallies["node5"], disrupted_keys);
|
|
end);
|
|
|
|
it("should support removing multiple nodes", function ()
|
|
ring:remove_nodes({"node2", "node3", "node4", "node5"});
|
|
end);
|
|
|
|
it("should return a single node for all keys if only one node remains", function ()
|
|
for i = 1, 100 do
|
|
assert.is_equal("node1", ring:get_node(tostring(i)))
|
|
end
|
|
end);
|
|
|
|
it("should support values associated with nodes", function ()
|
|
local r = hashring.new(128, sha256);
|
|
r:add_node("node1", { a = 1 });
|
|
local node, value = r:get_node("foo");
|
|
assert.is_equal("node1", node);
|
|
assert.same({ a = 1 }, value);
|
|
end);
|
|
end);
|