gluon-scheduled-domain-switch: add package (#1555)

This package allows to automatically switch to another domain, either
at a given point in time or after the node was offline long enough.
This commit is contained in:
David Bauer 2019-02-12 11:00:29 +01:00 committed by Andreas Ziegler
parent 131548580e
commit c1b9ea2d9c
9 changed files with 198 additions and 9 deletions

View File

@ -0,0 +1,38 @@
gluon-scheduled-domain-switch
=============================
This package allows to switch a routers domain at a given point
in time. This is needed for switching between incompatible transport
protocols (e.g. 802.11s and IBSS or VXLAN).
Nodes will switch when the defined *switch-time* has passed. In case the node was
powered off while this was supposed to happen, it might not be able to aquire the
correct time. In this case, the node will switch after it has not seen any gateway
for a given period of time.
site.conf
---------
All those settings have to be defined exclusively in the domain, not the site.
domain_switch : optional (needed for domains to switch)
target_domain :
- target domain to switch to
switch_after_offline_mins :
- amount of time without reachable gateway to switch unconditionally
switch_time :
- UNIX epoch after which domain will be switched
connection_check_targets :
- array of IPv6 addresses which are probed to determine if the node is
connected to the mesh
Example::
domain_switch = {
target_domain = 'new_domain',
switch_after_offline_mins = 120,
switch_time = 1546344000, -- 01.01.2019 - 12:00 UTC
connection_check_targets = {
'2001:4860:4860::8888',
'2001:4860:4860::8844',
},
},

View File

@ -3,15 +3,6 @@ need_string(in_site({'site_name'}))
-- this_domain() returns nil when multidomain support is disabled -- this_domain() returns nil when multidomain support is disabled
if this_domain() then if this_domain() then
function need_domain_name(path)
need_string(path)
need(path, function(default_domain)
local f = io.open(os.getenv('IPKG_INSTROOT') .. '/lib/gluon/domains/' .. default_domain .. '.json')
if not f then return false end
f:close()
return true
end, nil, 'be a valid domain name')
end
need_domain_name(in_site({'default_domain'})) need_domain_name(in_site({'default_domain'}))
need_table(in_domain({'domain_names'}), function(domain) need_table(in_domain({'domain_names'}), function(domain)

View File

@ -260,3 +260,12 @@ function foreach_radio(uci, f)
end end
end end
end end
function get_uptime()
local uptime_file = readfile("/proc/uptime")
if uptime_file == nil then
-- Something went wrong reading "/proc/uptime"
return nil
end
return tonumber(uptime_file:match('^[^ ]+'))
end

View File

@ -0,0 +1,13 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=gluon-scheduled-domain-switch
PKG_VERSION:=1
include ../gluon.mk
define Package/gluon-scheduled-domain-switch
TITLE:=Allows scheduled migrations between domains
DEPENDS:=+gluon-core @GLUON_MULTIDOMAIN
endef
$(eval $(call BuildPackageGluon,gluon-scheduled-domain-switch))

View File

@ -0,0 +1,6 @@
if need_table(in_domain({'domain_switch'}), check_domain_switch, false) then
need_domain_name(in_domain({'domain_switch', 'target_domain'}))
need_number(in_domain({'domain_switch', 'switch_after_offline_mins'}))
need_number(in_domain({'domain_switch', 'switch_time'}))
need_string_array_match(in_domain({'domain_switch', 'connection_check_targets'}), '^[%x:]+$')
end

View File

@ -0,0 +1,20 @@
#!/usr/bin/lua
local json = require 'jsonc'
local site = require 'gluon.site'
local unistd = require 'posix.unistd'
local cronfile = "/usr/lib/micron.d/gluon-scheduled-domain-switch"
-- Check if domain switch is scheduled
if site.domain_switch() == nil then
-- In case no domain switch is scheduled, remove cronfile
os.remove(cronfile)
os.exit(0)
end
-- Only in case domain switch is scheduled
local f = io.open(cronfile, "w")
f:write("* * * * * /usr/bin/gluon-check-connection\n")
f:write("*/5 * * * * /usr/bin/gluon-switch-domain\n")
f:close()

View File

@ -0,0 +1,36 @@
#!/usr/bin/lua
local unistd = require 'posix.unistd'
local util = require 'gluon.util'
local site = require 'gluon.site'
local offline_flag_file = "/tmp/gluon_offline"
local is_offline = true
-- Check if domain-switch is scheduled
if site.domain_switch() == nil then
-- Switch not applicable for current domain
os.exit(0)
end
-- Check reachability of pre-defined targets
for _, ip in ipairs(site.domain_switch.connection_check_targets()) do
local exit_code = os.execute("ping -c 1 -w 10 " .. ip)
if exit_code == 0 then
is_offline = false
break
end
end
if is_offline then
-- Check if we were previously offline
if unistd.access(offline_flag_file) then
os.exit(0)
end
-- Create offline flag
local f = io.open(offline_flag_file, "w")
f:write(tostring(util.get_uptime()))
f:close()
else
os.remove(offline_flag_file)
end

View File

@ -0,0 +1,67 @@
#!/usr/bin/lua
local uci = require('simple-uci').cursor()
local unistd = require 'posix.unistd'
local util = require 'gluon.util'
local site = require 'gluon.site'
-- Returns true if node was offline long enough to perform domain switch
function switch_after_min_reached()
if not unistd.access("/tmp/gluon_offline") then
return false
end
local switch_after_sec = site.domain_switch.switch_after_offline_mins() * 60
local current_uptime = util.get_uptime()
if current_uptime == nil then
return false
end
local f = util.readfile("/tmp/gluon_offline")
if f == nil then
return false
end
local offline_since = tonumber(f)
local offline_time_sec = current_uptime - offline_since
if offline_time_sec > switch_after_sec then
return true
end
return false
end
-- Returns true in case switch time has passed
function switch_time_passed()
local current_time = os.time()
local switch_time = site.domain_switch.switch_time()
return switch_time < current_time
end
if site.domain_switch() == nil then
-- Switch not applicable for current domain
print("No domain switch defined for the current domain.")
os.exit(0)
end
local current_domain = uci:get("gluon", "core", "domain")
local target_domain = site.domain_switch.target_domain()
if target_domain == current_domain then
-- Current and target domain are equal
print("Domain '" .. target_domain .. "' equals current domain.")
os.exit(1)
end
if not switch_after_min_reached() and not switch_time_passed() then
-- Neither switch-time passed nor switch_after_min reached
os.exit(0)
end
uci:set("gluon", "core", "domain", target_domain)
uci:commit("gluon")
os.execute("gluon-reconfigure")
os.execute("reboot")

View File

@ -305,6 +305,15 @@ function need_array_of(path, array, required)
return need_array(path, function(e) need_one_of(e, array) end, required) return need_array(path, function(e) need_one_of(e, array) end, required)
end end
function need_domain_name(path)
need_string(path)
need(path, function(domain_name)
local f = io.open(os.getenv('IPKG_INSTROOT') .. '/lib/gluon/domains/' .. domain_name .. '.json')
if not f then return false end
f:close()
return true
end, nil, 'be a valid domain name')
end
local check = assert(loadfile()) local check = assert(loadfile())