From c1b9ea2d9cfadf9179aa628c53074b3d3a3ac78d Mon Sep 17 00:00:00 2001 From: David Bauer Date: Tue, 12 Feb 2019 11:00:29 +0100 Subject: [PATCH] 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. --- .../package/gluon-scheduled-domain-switch.rst | 38 +++++++++++ package/gluon-core/check_site.lua | 9 --- .../luasrc/usr/lib/lua/gluon/util.lua | 9 +++ .../gluon-scheduled-domain-switch/Makefile | 13 ++++ .../check_site.lua | 6 ++ .../upgrade/950-gluon-scheduled-domain-switch | 20 ++++++ .../luasrc/usr/bin/gluon-check-connection | 36 ++++++++++ .../luasrc/usr/bin/gluon-switch-domain | 67 +++++++++++++++++++ scripts/check_site.lua | 9 +++ 9 files changed, 198 insertions(+), 9 deletions(-) create mode 100644 docs/package/gluon-scheduled-domain-switch.rst create mode 100644 package/gluon-scheduled-domain-switch/Makefile create mode 100644 package/gluon-scheduled-domain-switch/check_site.lua create mode 100755 package/gluon-scheduled-domain-switch/luasrc/lib/gluon/upgrade/950-gluon-scheduled-domain-switch create mode 100755 package/gluon-scheduled-domain-switch/luasrc/usr/bin/gluon-check-connection create mode 100755 package/gluon-scheduled-domain-switch/luasrc/usr/bin/gluon-switch-domain diff --git a/docs/package/gluon-scheduled-domain-switch.rst b/docs/package/gluon-scheduled-domain-switch.rst new file mode 100644 index 00000000..aeb0d967 --- /dev/null +++ b/docs/package/gluon-scheduled-domain-switch.rst @@ -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', + }, + }, diff --git a/package/gluon-core/check_site.lua b/package/gluon-core/check_site.lua index 7017af7e..ede64cd0 100644 --- a/package/gluon-core/check_site.lua +++ b/package/gluon-core/check_site.lua @@ -3,15 +3,6 @@ need_string(in_site({'site_name'})) -- this_domain() returns nil when multidomain support is disabled 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_table(in_domain({'domain_names'}), function(domain) diff --git a/package/gluon-core/luasrc/usr/lib/lua/gluon/util.lua b/package/gluon-core/luasrc/usr/lib/lua/gluon/util.lua index 4b963283..c7e00d3c 100644 --- a/package/gluon-core/luasrc/usr/lib/lua/gluon/util.lua +++ b/package/gluon-core/luasrc/usr/lib/lua/gluon/util.lua @@ -260,3 +260,12 @@ function foreach_radio(uci, f) 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 diff --git a/package/gluon-scheduled-domain-switch/Makefile b/package/gluon-scheduled-domain-switch/Makefile new file mode 100644 index 00000000..b1d7f786 --- /dev/null +++ b/package/gluon-scheduled-domain-switch/Makefile @@ -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)) diff --git a/package/gluon-scheduled-domain-switch/check_site.lua b/package/gluon-scheduled-domain-switch/check_site.lua new file mode 100644 index 00000000..d1a2fa21 --- /dev/null +++ b/package/gluon-scheduled-domain-switch/check_site.lua @@ -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 diff --git a/package/gluon-scheduled-domain-switch/luasrc/lib/gluon/upgrade/950-gluon-scheduled-domain-switch b/package/gluon-scheduled-domain-switch/luasrc/lib/gluon/upgrade/950-gluon-scheduled-domain-switch new file mode 100755 index 00000000..a15ed682 --- /dev/null +++ b/package/gluon-scheduled-domain-switch/luasrc/lib/gluon/upgrade/950-gluon-scheduled-domain-switch @@ -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() diff --git a/package/gluon-scheduled-domain-switch/luasrc/usr/bin/gluon-check-connection b/package/gluon-scheduled-domain-switch/luasrc/usr/bin/gluon-check-connection new file mode 100755 index 00000000..508cd17a --- /dev/null +++ b/package/gluon-scheduled-domain-switch/luasrc/usr/bin/gluon-check-connection @@ -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 diff --git a/package/gluon-scheduled-domain-switch/luasrc/usr/bin/gluon-switch-domain b/package/gluon-scheduled-domain-switch/luasrc/usr/bin/gluon-switch-domain new file mode 100755 index 00000000..57fed15a --- /dev/null +++ b/package/gluon-scheduled-domain-switch/luasrc/usr/bin/gluon-switch-domain @@ -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") diff --git a/scripts/check_site.lua b/scripts/check_site.lua index 41944cb0..6db3b1c0 100644 --- a/scripts/check_site.lua +++ b/scripts/check_site.lua @@ -305,6 +305,15 @@ function need_array_of(path, array, required) return need_array(path, function(e) need_one_of(e, array) end, required) 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())