Fix some bugs and problems in gossip (#3527)

* Allow configuration of debugOutput to be performed

* don't send to own IP or update own data

* Use same socket to send and receive. Avoid problems in many opened and closed sockets to send

* Add callback for REMOVEed hosts

* Send broascast messages if seedList is empty

* Adapt yeelink to new luacheck rules

* Fix building of luac.cross for win to win2019 and VS 2019
This commit is contained in:
Gregor Hartmann 2022-07-10 17:01:21 +02:00 committed by GitHub
parent 27dbe11fcb
commit 193fe3593e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 57 additions and 34 deletions

View File

@ -58,7 +58,7 @@ jobs:
build_luac_cross_win: build_luac_cross_win:
runs-on: windows-latest runs-on: windows-2019
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

2
.gitignore vendored
View File

@ -28,6 +28,8 @@ uz_zip
# ignore VS Code files # ignore VS Code files
.vscode/** .vscode/**
# ignore VS files
.vs/**
# ignore IDEA files # ignore IDEA files
.idea .idea

View File

@ -68,7 +68,7 @@ gossip.setConfig(config)
Sets the configuration for gossip. The available options are: Sets the configuration for gossip. The available options are:
`seedList` : the list of seeds gossip will start with; this will be updated as new nodes are discovered. Note that it's enough for all nodes to start with the same IP in the seedList, as once they have one seed in common, the data will propagate `seedList` : the list of seeds gossip will start with; this will be updated as new nodes are discovered. Note that it's enough for all nodes to start with the same IP in the seedList, as once they have one seed in common, the data will propagate. If the seedList is empty a broadcast is sent, so this can be used for automatic discovery of nodes.
`roundInterval`: interval in milliseconds at which gossip will pick a random node from the seed list and send a `SYN` request `roundInterval`: interval in milliseconds at which gossip will pick a random node from the seed list and send a `SYN` request

View File

@ -15,11 +15,7 @@ end
utils.debug = function(message) utils.debug = function(message)
if gossip.config.debug then if gossip.config.debug then
if gossip.config.debugOutput then
gossip.config.debugOutput(message); gossip.config.debugOutput(message);
else
print(message);
end
end end
end end
@ -105,15 +101,23 @@ state.start = function()
return; return;
end end
-- sending to own IP makes no sense
for index, value in ipairs(gossip.config.seedList) do
if value == gossip.ip then
table.remove(gossip.config.seedList, index)
utils.debug('removing own ip from seed list')
end
end
gossip.networkState[gossip.ip] = {}; gossip.networkState[gossip.ip] = {};
local localState = gossip.networkState[gossip.ip]; local localState = gossip.networkState[gossip.ip];
localState.revision = state.setRev(); localState.revision = state.setRev();
localState.heartbeat = tmr.time(); localState.heartbeat = tmr.time();
localState.state = constants.nodeState.UP; localState.state = constants.nodeState.UP;
gossip.inboundSocket = net.createUDPSocket(); gossip.socket = net.createUDPSocket();
gossip.inboundSocket:listen(gossip.config.comPort); gossip.socket:listen(gossip.config.comPort);
gossip.inboundSocket:on('receive', network.receiveData); gossip.socket:on('receive', network.receiveData);
gossip.started = true; gossip.started = true;
@ -128,16 +132,25 @@ end
state.tickNodeState = function(ip) state.tickNodeState = function(ip)
if gossip.networkState[ip] then if gossip.networkState[ip] then
local nodeState = gossip.networkState[ip].state; local nodeState = gossip.networkState[ip].state;
local oldNodeState = nodeState;
if nodeState < constants.nodeState.REMOVE then if nodeState < constants.nodeState.REMOVE then
nodeState = nodeState + constants.nodeState.TICK; nodeState = nodeState + constants.nodeState.TICK;
gossip.networkState[ip].state = nodeState; gossip.networkState[ip].state = nodeState;
end end
if oldNodeState == constants.nodeState.DOWN then
if gossip.updateCallback then gossip.updateCallback(gossip.networkState[ip]); end
end
end end
end end
-- Network -- Network
network.broadcastIp = "255.255.255.255"
network.pushGossip = function(data, ip) network.pushGossip = function(data, ip)
if not gossip.started then
utils.debug('Gossip not started.');
return;
end
gossip.networkState[gossip.ip].data = data; gossip.networkState[gossip.ip].data = data;
network.sendSyn(nil, ip); network.sendSyn(nil, ip);
end end
@ -145,12 +158,14 @@ end
network.updateNetworkState = function(updateData) network.updateNetworkState = function(updateData)
if gossip.updateCallback then gossip.updateCallback(updateData); end if gossip.updateCallback then gossip.updateCallback(updateData); end
for ip, data in pairs(updateData) do for ip, data in pairs(updateData) do
if not utils.contains(gossip.config.seedList, ip) then if not utils.contains(gossip.config.seedList, ip) and ip ~= network.broadcastIp and ip ~= gossip.ip then
table.insert(gossip.config.seedList, ip); table.insert(gossip.config.seedList, ip);
end end
if ip ~= gossip.ip then
gossip.networkState[ip] = data; gossip.networkState[ip] = data;
end end
end end
end
-- luacheck: push no unused -- luacheck: push no unused
network.sendSyn = function(t, ip) network.sendSyn = function(t, ip)
@ -170,17 +185,16 @@ network.pickRandomNode = function()
return gossip.config.seedList[randomListPick]; return gossip.config.seedList[randomListPick];
end end
utils.debug( utils.debug(
'Seedlist is empty. Please provide one or wait for node to be contacted.'); 'Seedlist is empty. Using broadcast IP '..network.broadcastIp..' to discover.');
return nil; return network.broadcastIp;
end end
network.sendData = function(ip, data, sendType) network.sendData = function(ip, data, sendType)
local outboundSocket = net.createUDPSocket(); data.type = sendType
data.type = sendType; local dataToSend = sjson.encode(data)
local dataToSend = sjson.encode(data); data.type = nil
data.type = nil; gossip.socket:send(gossip.config.comPort, ip, dataToSend)
outboundSocket:send(gossip.config.comPort, ip, dataToSend); utils.debug("Sent "..#dataToSend.." bytes")
outboundSocket:close();
end end
network.receiveSyn = function(ip, synData) network.receiveSyn = function(ip, synData)
@ -235,7 +249,8 @@ constants.defaultConfig = {
seedList = {}, seedList = {},
roundInterval = 15000, roundInterval = 15000,
comPort = 5000, comPort = 5000,
debug = false debug = false,
debugOutput = print
}; };
constants.comparisonFields = {'revision', 'heartbeat', 'state'}; constants.comparisonFields = {'revision', 'heartbeat', 'state'};

View File

@ -24,6 +24,7 @@ file.putcontents = dummy
local Ip_1 = '192.168.0.1'; local Ip_1 = '192.168.0.1';
local Ip_2 = '192.168.0.2'; local Ip_2 = '192.168.0.2';
local Ip_own = '192.168.0.3';
-- test runner -- test runner
@ -186,20 +187,19 @@ function Test.utils_getMinus()
state = constants.nodeState.SUSPECT; state = constants.nodeState.SUSPECT;
}; };
--local diff1 = utils.getMinus(data1, data2); local diff1 = utils.getMinus(data1, data2);
local diff2 = utils.getMinus(data2, data1); local diff2 = utils.getMinus(data2, data1);
--assert(diff1[Ip_1] ~= nil and diff1[Ip_2] == nil); assert(diff1[Ip_1] ~= nil and diff1[Ip_2] == nil);
assert(diff2[Ip_1] == nil and diff2[Ip_2] ~= nil); assert(diff2[Ip_1] == nil and diff2[Ip_2] ~= nil);
end end
-- state -- state
function Test.state_setRev() function Test.state_setRev()
gossip.ip = Ip_1; gossip.ip = Ip_own;
gossip.networkState[Ip_1] = {}; gossip.networkState[Ip_own] = {};
gossip.networkState[Ip_1].revision = -1; gossip.networkState[Ip_own].revision = -1;
assert(state.setRev() == 0, 'Revision not initialized to 0.'); assert(state.setRev() == 0, 'Revision not initialized to 0.');
end end
@ -223,6 +223,7 @@ end
-- network -- network
function Test.network_updateNetworkState_no_callback() function Test.network_updateNetworkState_no_callback()
gossip.ip = Ip_own;
local updateData = {} local updateData = {}
updateData[Ip_1] = { updateData[Ip_1] = {
revision = 1, revision = 1,
@ -232,7 +233,12 @@ function Test.network_updateNetworkState_no_callback()
updateData[Ip_2] = { updateData[Ip_2] = {
revision = 1, revision = 1,
heartbeat = 700, heartbeat = 700,
state = constants.nodeState.UP state = constants.nodeState.DOWN
};
updateData[Ip_own] = {
revision = 1,
heartbeat = 800,
state = constants.nodeState.DOWN
}; };
network.updateNetworkState(updateData); network.updateNetworkState(updateData);
-- send duplicate data -- send duplicate data

View File

@ -54,10 +54,10 @@ end)
-- apikey must be -> string <- -- apikey must be -> string <-
-- e.g. xxx.init(00000,00000,"123j12b3jkb12k4b23bv54i2b5b3o4") -- e.g. xxx.init(00000,00000,"123j12b3jkb12k4b23bv54i2b5b3o4")
--======================================== --========================================
function M.init(_device, _sensor, _apikey) function M.init(device_, sensor_, apikey_)
device = tostring(_device) device = tostring(device_)
sensor = tostring(_sensor) sensor = tostring(sensor_)
apikey = _apikey apikey = apikey_
if dns == "0.0.0.0" then if dns == "0.0.0.0" then
tmr.create():alarm(5000,tmr.ALARM_AUTO,function () tmr.create():alarm(5000,tmr.ALARM_AUTO,function ()
if dns == "0.0.0.0" then if dns == "0.0.0.0" then
@ -90,9 +90,9 @@ end
-- --
--e.g. xxx.update(233.333) --e.g. xxx.update(233.333)
--============================================================ --============================================================
function M.update(_datapoint) function M.update(datapoint_)
datapoint = tostring(_datapoint) datapoint = tostring(datapoint_)
sk:on("connection", function() sk:on("connection", function()