From 4bb51520521d9ab612d50dd2bcf904d70cf15735 Mon Sep 17 00:00:00 2001 From: onefang Date: Tue, 10 Dec 2019 15:45:11 +1000 Subject: Move common code to it's own module. Some minor clean ups and test tweaks likely came for the ride. --- apt-panopticommon.lua | 563 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 563 insertions(+) create mode 100644 apt-panopticommon.lua (limited to 'apt-panopticommon.lua') diff --git a/apt-panopticommon.lua b/apt-panopticommon.lua new file mode 100644 index 0000000..b38595c --- /dev/null +++ b/apt-panopticommon.lua @@ -0,0 +1,563 @@ +local APT = {} + +-- https://oss.oetiker.ch/rrdtool/prog/rrdlua.en.html +APT.rrd = require 'rrd' + +verbosity = -1 +APT.origin = false +APT.keep = false +-- TODO - Should actually implement this. +APT.fork = true + +APT.options = +{ + referenceSite = + { + typ = "string", + help = "", + value = "pkgmaster.devuan.org", + }, + roundRobin = + { + typ = "string", + help = "", + value = "deb.devuan.org", + }, + tests = + { + typ = "table", + help = "", + value = + { + "IPv4", + "IPv6", +-- "ftp", + "http", + "https", +-- "rsync", + "DNSRR", + "Protocol", + "URLSanity", + "Integrity", + "Updated", + }, + }, + maxtime = + { + typ = "number", + help = "", + value = 300, + }, + timeout = + { + typ = "number", + help = "", + value = 15, + }, + reports = + { + typ = "table", + help = "", + value = + { + "RRD", -- RRD has to be before web, coz web creates a graph from the RRD data. + "email-web", +-- "Nagios", +-- "Prometheus", + }, + }, +} + +APT.parseArgs = function(args) + local arg = {} + local sendArgs = "" + if 0 ~= #(args) then + local option = "" + for i, a in pairs(args) do + if ("--help" == a) or ("-h" == a) then + print("I should write some docs, huh? Read README.md for instructions.") + elseif "--version" == a then + print("apt-panopticon version 0.1 WIP development version") + elseif "-v" == a then + verbosity = verbosity + 1 + sendArgs = sendArgs .. a .. " " + elseif "-q" == a then + verbosity = -1 + sendArgs = sendArgs .. a .. " " + elseif "-k" == a then + APT.keep = true + elseif "-n" == a then + APT.fork = false + elseif "-o" == a then + APT.origin = true + elseif "--" == a:sub(1, 2) then + local s, e = a:find("=") + if nil == s then e = -1 end + option = a:sub(3, e - 1) + local o = APT.options[option] + if nil == o then + print("Unknown option --" .. option) + option = "" + else + option = a + sendArgs = sendArgs .. a .. " " + local s, e = a:find("=") + if nil == s then e = 0 end + option = a:sub(3, e - 1) + if "table" == APT.options[option].typ then + local result = {} + for t in (a:sub(e + 1) .. ","):gmatch("([+%-]?%w*),") do + local f = t:sub(1, 1) + local n = t:sub(2, -1) + if ("+" ~= f) and ("-" ~= f) then + table.insert(result, t) + end + end + if 0 ~= #result then + APT.options[option].value = result + else + for t in (a:sub(e + 1) .. ","):gmatch("([+%-]?%w*),") do + local f = t:sub(1, 1) + local n = t:sub(2, -1) + if "+" == f then + table.insert(APT.options[option].value, n) + elseif "-" == f then + local r = {} + for i, k in pairs(APT.options[option].value) do + if k ~= n then table.insert(r, k) end + end + APT.options[option].value = r + end + end + end + else + APT.options[option].value = a:sub(e + 1, -1) + end + option = "" + end + elseif "-" == a:sub(1, 1) then + print("Unknown option " .. a) + else + table.insert(arg, a) + end + end + end + return arg, sendArgs +end + +--print(APT.dumpTable(APT.options, "", "options")) + + + +--[[ Ordered table iterator, allow to iterate on the natural order of the keys of a table. + From http://lua-users.org/wiki/SortedIteration + ]] +function __genOrderedIndex( t ) + local orderedIndex = {} + for key in pairs(t) do + table.insert( orderedIndex, key ) + end + table.sort( orderedIndex ) + return orderedIndex +end +function orderedNext(t, state) + -- Equivalent of the next function, but returns the keys in the alphabetic + -- order. We use a temporary ordered key table that is stored in the + -- table being iterated. + + local key = nil + --print("orderedNext: state = "..tostring(state) ) + if state == nil then + -- the first time, generate the index + t.__orderedIndex = __genOrderedIndex( t ) + key = t.__orderedIndex[1] + else + -- fetch the next value + for i = 1,table.getn(t.__orderedIndex) do + if t.__orderedIndex[i] == state then + key = t.__orderedIndex[i+1] + end + end + end + + if key then + return key, t[key] + end + + -- no more value to return, cleanup + t.__orderedIndex = nil + return +end +function APT.orderedPairs(t) + -- Equivalent of the pairs() function on tables. Allows to iterate + -- in order + return orderedNext, t, nil +end + +-- Use this to dump a table to a string, with HTML. +APT.dumpTableHTML = function (table, space, name) + local r = name .. "\n" + r = r .. dumpTableHTMLSub(table, space .. " ") + r = r .. space .. "" + return r +end +dumpTableHTMLSub = function (table, space) + local r = "" + for k, v in APT.orderedPairs(table) do + if type(v) == "table" then + if " " == space then + r = r .. space .. APT.dumpTableHTML(v, space, k .. "\n" + else + r = r .. "
  • " .. space .. APT.dumpTableHTML(v, space, k .. "
  • \n" + end + else + r = r .. space .. "
  • " .. k .. "
  • \n" + end + end + return r +end + +-- Use this to dump a table to a string. +APT.dumpTable = function (table, space, name) + local r = "" + if "" == space then r = r .. space .. name .. " =\n" else r = r .. space .. "[" .. name .. "] =\n" end + r = r .. space .. "{\n" + r = r .. dumpTableSub(table, space .. " ") + if "" == space then r = r .. space .. "}\n" else r = r .. space .. "},\n" end + return r +end +dumpTableSub = function (table, space) + local r = "" + for k, v in pairs(table) do + if type(k) == "string" then k = '"' .. k .. '"' end + if type(v) == "table" then + r = r .. APT.dumpTable(v, space, k) + elseif type(v) == "string" then + r = r .. space .. "[" .. k .. "] = '" .. v .. "';\n" + elseif type(v) == "function" then + r = r .. space .. "[" .. k .. "] = function ();\n" + elseif type(v) == "userdata" then + r = r .. space .. "userdata " .. "[" .. k .. "];\n" + elseif type(v) == "boolean" then + if (v) then + r = r .. space .. "[" .. k .. "] = true;\n" + else + r = r .. space .. "[" .. k .. "] = false;\n" + end + else + r = r .. space .. "[" .. k .. "] = " .. v .. ";\n" + end + end + return r +end + +APT.results = {} +APT.logFile = nil +APT.html = false + +APT.logPre = function() + if nil ~= APT.logFile then + APT.logFile:write("\n") + APT.logFile:write("\n") + end +end +APT.logPost = function() + if nil ~= APT.logFile then + APT.logFile:write(" \n") + end +end + +local log = function(v, t, s, prot, test, host) + local x = "" + if nil == prot then prot = "" end + if nil == test then test = "" end + x = x .. prot + if "" ~= test then + if #x > 0 then x = x .. " " end + x = x .. test + end + if nil ~= host then + if #x > 0 then x = x .. " " end + x = x .. host + end + if #x > 0 then + t = t .. "(" .. x .. ")" + if "" ~= prot then + if "" == test then + if nil == APT.results[prot] then APT.results[prot] = {errors = 0; warnings = 0} end + if v == 0 then APT.results[prot].errors = APT.results[prot].errors + 1 end + if v == 1 then APT.results[prot].warnings = APT.results[prot].warnings + 1 end + else + if nil == APT.results[prot] then APT.results[prot] = {errors = 0; warnings = 0} end + if nil == APT.results[prot][test] then APT.results[prot][test] = {errors = 0; warnings = 0} end + if v == 0 then APT.results[prot][test].errors = APT.results[prot][test].errors + 1 end + if v == 1 then APT.results[prot][test].warnings = APT.results[prot][test].warnings + 1 end + end + end + end + if v <= verbosity then + if 3 <= verbosity then t = os.date() .. " " .. t end + print(t .. ": " .. s) + end + if nil ~= APT.logFile then + if APT.html then + local colour = "white" + if -1 == v then colour = "fuchsia" end -- CRITICAL + if 0 == v then colour = "red " end -- ERROR + if 1 == v then colour = "yellow " end -- WARNING + if 2 == v then colour = "white " end -- INFO + if 3 == v then colour = "gray " end -- DEBUG + APT.logFile:write(os.date() .. " " .. t .. ": " .. s .. "
    \n") + else + APT.logFile:write(os.date() .. " " .. t .. ": " .. s .. "\n") + end + APT.logFile:flush() + end +end +APT.D = function(s) log(3, "DEBUG ", s) end +APT.I = function(s) log(2, "INFO ", s) end +APT.W = function(s, p, t, h) log(1, "WARNING ", s, p, t, h) end +APT.E = function(s, p, t, h) log(0, "ERROR ", s, p, t, h) end +APT.C = function(s) log(-1, "CRITICAL", s) end +local D = APT.D +local I = APT.I +local W = APT.W +local E = APT.E +local C = APT.C + + +APT.mirrors = {} + +APT.testing = function(t, host) + for i, v in pairs(APT.options.tests.value) do + if t == v then + local h = APT.mirrors[host] + if nil == h then return true end + if true == h["Protocols"][t] then return true else D("Skipping " .. t .. " checks for " .. host) end + end + end + return false +end + +APT.execute = function (s) + D(" executing
    " .. s .. "
    ") + --[[ Damn os.execute() + Lua 5.1 says it returns "a status code, which is system-dependent" + Lua 5.2 says it returns true/nil, "exit"/"signal", the status code. + I'm getting 7168 or 0. No idea what the fuck that is. + local ok, rslt, status = os.execute(s) + ]] + local f = io.popen(s .. ' ; echo "$?"', 'r') + local status = "" + local result = "" + -- The last line will be the command's returned status, collect everything else in result. + for l in f:lines() do + result = result .. status .. "\n" + status = l + end + return status, result +end + +APT.fork = function(s) + D(" forking
    " .. s .. "
    ") + os.execute(s .. " &") +end + +APT.checkExes = function (exe) + local count = io.popen('ps x | grep "' .. exe .. '" | grep -v " grep " | grep -v "flock -n apt-panopticon.lock " | wc -l'):read("*l") + D(count .. " " .. exe .. " commands still running.") + return tonumber(count) +end + +APT.checkFile = function(f) + local h, e = io.open(f, "r") + if nil == h then return false else h:close(); return true end +end + +APT.plurals = function(e, w) + local result = "" + if 1 == e then + result = e .. " error" + elseif e ~= 0 then + result = e .. " errors" + end + if ("" ~= result) and APT.html then result = "" .. result .. "" end +-- result = " " .. result + if 0 < w then + if 0 < e then result = result .. ", " end + if 1 == w then + result = result .. w .. " warning" + else + result = result .. w .. " warnings" + end + if ("" ~= result) and APT.html then result = "" .. result .. "" end +-- result = " " .. result + end + if "" ~= result then result = " (" .. result .. ")" end + return result +end + +APT.collate = function(host, ip, results) + local f = "results/" .. host .. "_" .. ip .. ".lua" + local rfile, e = io.open(f, "r") + if nil == rfile then I("opening " .. f .. " file - " .. e) else + rfile:close() + local rs = loadfile(f)() + for k, v in pairs(rs) do + if "table" == type(v) then + if ("speed" == k) and (nil ~= results.speed) then + if v.min < results.speed.min then results.speed.min = v.min end + if v.max > results.speed.max then results.speed.max = v.max end + else + for i, u in pairs(v) do + if "table" == type(u) then + for h, t in pairs(u) do + local a = results[k][i][h] + if nil == a then a = 0 end + results[k][i][h] = a + t + end + else + local a = results[k] + if nil == a then a = 0; results[k] = {} else a = a[i] end + if nil == a then a = 0 end + results[k][i] = a + u + end + end + end + elseif "timeout" ~= k then + local a = results[k] + if nil == a then a = 0 end + results[k] = a + v + end + end + end + return results +end + + + + +APT.now = 0 +local status +status, APT.now = APT.execute('TZ="GMT" ls -l --time-style="+%s" results/stamp | cut -d " " -f 6-6') +APT.now = tonumber(APT.now) +APT.protocols = {'ftp', 'http', 'https', 'rsync'} +APT.tests = {'raw', 'Integrity', 'Protocol', 'Updated', 'URLSanity', 'Speed'} +local start = 'now-2week' +local step = '10min' +local hb = '150min' +local DSIe = 'DS:IntegrityErrors:GAUGE:' .. hb .. ':0:U' +local DSIw = 'DS:IntegrityWarnings:GAUGE:' .. hb .. ':0:U' +local DSPe = 'DS:ProtocolErrors:GAUGE:' .. hb .. ':0:U' +local DSPw = 'DS:ProtocolWarnings:GAUGE:' .. hb .. ':0:U' +local DSUe = 'DS:UpdatedErrors:GAUGE:' .. hb .. ':0:U' +local DSUw = 'DS:UpdatedWarnings:GAUGE:' .. hb .. ':0:U' +local DSSe = 'DS:URLSanityErrors:GAUGE:' .. hb .. ':0:U' +local DSSw = 'DS:URLSanityWarnings:GAUGE:' .. hb .. ':0:U' +local DSx = 'DS:max:GAUGE:' .. hb .. ':0:U' +local DSn = 'DS:min:GAUGE:' .. hb .. ':0:U' + +-- What Collectd uses. +local RRAc0 = 'RRA:AVERAGE:0.9:1:1200' +local RRAc1 = 'RRA:MIN:0.9:1:1200' +local RRAc2 = 'RRA:MAX:0.9:1:1200' +local RRAc3 = 'RRA:AVERAGE:0.9:7:1235' +local RRAc4 = 'RRA:MIN:0.9:7:1235' +local RRAc5 = 'RRA:MAX:0.9:7:1235' +local RRAc6 = 'RRA:AVERAGE:0.9:50:1210' +local RRAc7 = 'RRA:MIN:0.9:50:1210' +local RRAc8 = 'RRA:MAX:0.9:50:1210' +local RRAc9 = 'RRA:AVERAGE:0.9:223:1202' +local RRAc10 = 'RRA:MIN:0.9:223:1202' +local RRAc11 = 'RRA:MAX:0.9:223:1202' +local RRAc12 = 'RRA:AVERAGE:0.9:2635:1201' +local RRAc13 = 'RRA:MIN:0.9:2635:1201' +local RRAc14 = 'RRA:MAX:0.9:2635:1201' + +-- Try LAST. +local RRAl0 = 'RRA:LAST:0.9:1:1200' +local RRAl1 = 'RRA:LAST:0.9:7:1235' +local RRAl2 = 'RRA:LAST:0.9:50:1210' +local RRAl3 = 'RRA:LAST:0.9:223:1202' +local RRAl4 = 'RRA:LAST:0.9:2635:1201' + +--[[ +/var/lib/collectd/rrd/onefang/interface-eth0/if_packets.rrd + + rrd/host.IP/protocol.rrd test-error count + rrd/host.IP/protocol.rrd test-warning count + + rrh/host.IP/protocol-speed.rrd min + rrh/host.IP/protocol-speed.rrd max +]] + +APT.createRRD = function(host, ip) + if nil ~= ip then host = host .. '_' .. ip end + for i, p in pairs(APT.protocols) do +-- for j, t in pairs(tests) do + os.execute( 'mkdir -p rrd/' .. host .. '/' .. p:upper()) + if not APT.checkFile('rrd/' .. host .. '/' .. p:upper() .. '/Speed.rrd') then + D('Creating ' .. 'rrd/' .. host .. '/' .. p:upper() .. '/Speed.rrd') +-- if 'Speed' == t then + APT.rrd.create( 'rrd/' .. host .. '/' .. p:upper() .. '/Speed.rrd', '--start', start, '--step', step, DSx, DSn, + RRAc0, RRAc1, RRAc2, RRAl0, RRAc3, RRAc4, RRAc5, RRAl1, RRAc6, RRAc7, RRAc8, RRAl2, RRAc9, RRAc10, RRAc11, RRAl3, RRAc12, RRAc13, RRAc14, RRAl4) +-- else + end + if not APT.checkFile('rrd/' .. host .. '/' .. p:upper() .. '/Tests.rrd') then + D('Creating ' .. 'rrd/' .. host .. '/' .. p:upper() .. '/Tests.rrd') + APT.rrd.create( 'rrd/' .. host .. '/' .. p:upper() .. '/Tests.rrd', '--start', start, '--step', step, DSIe, DSIw, DSPe, DSPw, DSUe, DSUw, DSSe, DSSw, + RRAc0, RRAc1, RRAc2, RRAl0, RRAc3, RRAc4, RRAc5, RRAl1, RRAc6, RRAc7, RRAc8, RRAl2, RRAc9, RRAc10, RRAc11, RRAl3, RRAc12, RRAc13, RRAc14, RRAl4) +-- end + -- Start them at 0 so the average has something to work on. + APT.rrd.update( 'rrd/' .. host .. '/' .. p:upper() .. '/Speed.rrd', (APT.now - 600) .. ':0:0') + APT.rrd.update( 'rrd/' .. host .. '/' .. p:upper() .. '/Tests.rrd', (APT.now - 600) .. ':0:0:0:0:0:0:0:0') + end +-- end + end +end + +APT.updateRRD = function(results, host, ip) + if nil ~= ip then host = host .. '_' .. ip end + for i, p in pairs(APT.protocols) do + if nil ~= results.speed then + APT.rrd.update( 'rrd/' .. host .. '/' .. p:upper() .. '/Speed.rrd', APT.now .. ':' .. results.speed.max .. ':' .. results.speed.min) + end + if nil ~= results[p] then + APT.rrd.update('rrd/' .. host .. '/' .. p:upper() .. '/Tests.rrd', APT.now .. ':' .. + results[p]['Integrity'].errors .. ':' .. results[p]['Integrity'].warnings .. ':' .. + results[p]['Protocol'].errors .. ':' .. results[p]['Protocol'].warnings .. ':' .. + results[p]['Updated'].errors .. ':' .. results[p]['Updated'].warnings .. ':' .. + results[p]['URLSanity'].errors .. ':' .. results[p]['URLSanity'].warnings) + else + APT.rrd.update('rrd/' .. host .. '/' .. p:upper() .. '/Tests.rrd', APT.now .. ':U:U:U:U:U:U:U:U') + end + end +end + + +APT.doRRD = function(l, k, v) + if APT.checkFile(l .. "/" .. k .. ".lua") then + local results = loadfile(l .. "/" .. k .. ".lua")() + APT.createRRD(k) + APT.updateRRD(results, k) + if til ~= v then + local IPs = v.IPs + for i, u in pairs(IPs) do + if "table" == type(u) then + for h, t in pairs(u) do + APT.createRRD(k, h) + results = APT.collate(k, h, results) + APT.updateRRD(results, k, h) + end + else + APT.createRRD(k, i) + results = APT.collate(k, i, results) + APT.updateRRD(results, k, i) + end + end + end + end +end + + +return APT -- cgit v1.1