diff --git a/packages/eupgrade/files/usr/lib/lua/eupgrade.lua b/packages/eupgrade/files/usr/lib/lua/eupgrade.lua index 8fa86e1b9..fcee623a1 100644 --- a/packages/eupgrade/files/usr/lib/lua/eupgrade.lua +++ b/packages/eupgrade/files/usr/lib/lua/eupgrade.lua @@ -2,6 +2,7 @@ local utils = require "lime.utils" local json = require "luci.jsonc" local libuci = require "uci" local fs = require("nixio.fs") +local config = require "lime.config" local eup = {} @@ -32,8 +33,30 @@ function eup.is_enabled() return uci:get('eupgrade', 'main', 'enabled') == '1' end +function eup.is_meshupgrade_enabled() + if uci:get('eupgrade', 'main', 'custom_api_url') == nil then + return false + else + return true + end +end + function eup.get_upgrade_api_url() - return uci:get('eupgrade', 'main', 'api_url') or '' + return uci:get('eupgrade', 'main', 'custom_api_url') or uci:get('eupgrade', 'main', 'api_url') or '' +end + +function eup.set_custom_api_url(url) + status = uci:set('eupgrade', 'main', 'custom_api_url',url) + uci:save('eupgrade') + uci:commit('eupgrade') + return status +end + +function eup.remove_custom_api_url() + status = uci:delete('eupgrade', 'main', 'custom_api_url') + uci:save('eupgrade') + uci:commit('eupgrade') + return status end function eup._check_signature(file_path, signature_path) @@ -61,7 +84,7 @@ end function eup.is_new_version_available(cached_only) --! if 'latest' files are present is because there is a new version if utils.file_exists(eup.FIRMWARE_LATEST_JSON) and utils.file_exists(eup.FIRMWARE_LATEST_JSON_SIGNATURE) then - if eup._check_signature(eup.FIRMWARE_LATEST_JSON, eup.FIRMWARE_LATEST_JSON_SIGNATURE) then + if eup._check_signature(eup.FIRMWARE_LATEST_JSON, eup.FIRMWARE_LATEST_JSON_SIGNATURE) or eup.is_meshupgrade_enabled() then return json.parse(utils.read_file(eup.FIRMWARE_LATEST_JSON)) end end @@ -84,15 +107,12 @@ function eup.is_new_version_available(cached_only) local sig_url = url .. ".sig" if not utils.http_client_get(sig_url, 10, eup.FIRMWARE_LATEST_JSON_SIGNATURE) then message = "Can't download signature " .. sig_url - utils.log(message) end - - if eup._check_signature(eup.FIRMWARE_LATEST_JSON, eup.FIRMWARE_LATEST_JSON_SIGNATURE) then - utils.log("Good signature of firmware_latest.json") + -- this will skip json signature verification when altenative url is set + if eup._check_signature(eup.FIRMWARE_LATEST_JSON, eup.FIRMWARE_LATEST_JSON_SIGNATURE) or eup.is_meshupgrade_enabled() then return latest_data else message = "Bad signature of firmware_latest.json" - utils.log(message) end end end diff --git a/packages/eupgrade/tests/test_eupgrade.lua b/packages/eupgrade/tests/test_eupgrade.lua index 7a92961bf..0a1832bab 100644 --- a/packages/eupgrade/tests/test_eupgrade.lua +++ b/packages/eupgrade/tests/test_eupgrade.lua @@ -48,7 +48,7 @@ describe('eupgrade tests #eupgrade', function() assert.is.equal('LibreMesh 20.10', eup.is_new_version_available()['version']) end) - it('test is_new_version_available latest version is not the same as current version', function() + it('test is_new_version_available but signature fails', function() stub(eup, '_get_board_name', function () return 'test-board' end) stub(eup, '_get_current_fw_version', function () return 'LibreMesh 19.05' end) stub(eup, '_check_signature', function () return false end) @@ -58,6 +58,21 @@ describe('eupgrade tests #eupgrade', function() assert.is.equal("Bad signature of firmware_latest.json", message) end) + -- it('test is_new_version_available signature fails, but it does not mather since an alternative url has been set', function() + -- stub(eup, '_get_board_name', function () return 'test-board' end) + -- stub(eup, '_get_current_fw_version', function () return 'LibreMesh 19.05' end) + -- stub(eup, '_check_signature', function () return false end) + -- stub(utils, 'http_client_get', function () return latest_release_data end) + -- print("1",tostring(eup.get_upgrade_api_url())) + -- eup.set_custom_api_url("http://localhost") + -- assert.is_true(eup.is_meshupgrade_enabled()) + -- print("2",eup.get_upgrade_api_url()) + -- assert.is.equal('LibreMesh 20.10', eup.is_new_version_available()['version']) + -- eup.remove_custom_api_url() + -- print("3",eup.get_upgrade_api_url()) + + -- end) + it('test is_new_version_available unable to download info', function() stub(eup, '_get_board_name', function () return 'test-board' end) stub(eup, '_get_current_fw_version', function () return 'LibreMesh 19.05' end) diff --git a/packages/lime-mesh-upgrade/Makefile b/packages/lime-mesh-upgrade/Makefile new file mode 100644 index 000000000..2d63794df --- /dev/null +++ b/packages/lime-mesh-upgrade/Makefile @@ -0,0 +1,28 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=lime-mesh-upgrade +PKG_VERSION=$(GIT_COMMIT_DATE)-$(GIT_COMMIT_TSTAMP) +GIT_COMMIT_DATE:=$(shell git log -n 1 --pretty=%ad --date=short . ) +GIT_COMMIT_TSTAMP:=$(shell git log -n 1 --pretty=%at . ) + +include $(INCLUDE_DIR)/package.mk + +define Package/$(PKG_NAME) + CATEGORY:=LibreMesh + MAINTAINER:=selankon + TITLE:=LibreMesh mesh wide firmware upgrade + DEPENDS:= +lua +libubus-lua +safe-upgrade \ + +shared-state-mesh_wide_upgrade + + PKGARCH:=all +endef + +define Build/Compile +endef + +define Package/$(PKG_NAME)/install + $(INSTALL_DIR) $(1)/ + $(CP) ./files/* $(1)/ +endef + +$(eval $(call BuildPackage,$(PKG_NAME))) diff --git a/packages/lime-mesh-upgrade/README.md b/packages/lime-mesh-upgrade/README.md new file mode 100644 index 000000000..a1579a845 --- /dev/null +++ b/packages/lime-mesh-upgrade/README.md @@ -0,0 +1,13 @@ +# Mesh upgrade +This package can help you upgrade the firmware of all the routers in a network from a single node. + +## Description and steps +1- A node must become main node, the main node will fetch the firmware for all the others. And expose it in the local network. +2- The main node announces the new firmware over shared-state-async +3- Other nodes with this package will get the news and try to download the firmware. +4- Once all the nodes have the firmware in their tmp folder the main node user will be able to schedule the safe upgrade of all the nodes (this last step is done synchronously). +5- After the specified time (60s default) all the nodes will start the safe upgrade process and the nodes will reboot. +6- The nodes will report that the new firmware has to be confirmed. +7- The main node user will verify that everything is in place an press the confirm button. +8- If the firmware is not confirmed after 600 seconds the routers will go back to the previous firmware. + diff --git a/packages/lime-mesh-upgrade/files/etc/config/mesh-upgrade b/packages/lime-mesh-upgrade/files/etc/config/mesh-upgrade new file mode 100644 index 000000000..0a0bb9bd8 --- /dev/null +++ b/packages/lime-mesh-upgrade/files/etc/config/mesh-upgrade @@ -0,0 +1,13 @@ +config mesh-upgrade 'main' + option firmware_ver '' + option candidate_fw '' + option fw_path '' + option repo_url '' + option upgrade_state '' + option error '0' + option timestamp '0' + option main_node 'NO' + option retry_count '0' + option safeupgrade_start_mark '0' + option su_start_time_out '0' + \ No newline at end of file diff --git a/packages/lime-mesh-upgrade/files/lib/upgrade/keep.d/lime-mesh-upgrade b/packages/lime-mesh-upgrade/files/lib/upgrade/keep.d/lime-mesh-upgrade new file mode 100644 index 000000000..12034c27f --- /dev/null +++ b/packages/lime-mesh-upgrade/files/lib/upgrade/keep.d/lime-mesh-upgrade @@ -0,0 +1 @@ +/etc/config/mesh-upgrade diff --git a/packages/lime-mesh-upgrade/files/usr/lib/lua/lime-mesh-upgrade.lua b/packages/lime-mesh-upgrade/files/usr/lib/lua/lime-mesh-upgrade.lua new file mode 100644 index 000000000..9d71540de --- /dev/null +++ b/packages/lime-mesh-upgrade/files/usr/lib/lua/lime-mesh-upgrade.lua @@ -0,0 +1,680 @@ +#!/usr/bin/env lua + +local eupgrade = require 'eupgrade' +local config = require "lime.config" +local utils = require "lime.utils" +local network = require("lime.network") +local fs = require("nixio.fs") +local json = require 'luci.jsonc' + +local mesh_upgrade = { + -- possible transaction states are derived from upgrade states + transaction_states = { + NO_TRANSACTION = "NO_TRANSACTION", + STARTED = "STARTED", -- there is a transaction in progress + ABORTED = "ABORTED", + FINISHED = "FINISHED" + }, + -- possible upgrade states enumeration + upgrade_states = { + DEFAULT = "DEFAULT", -- When no upgrade has started, after reboot + DOWNLOADING = "DOWNLOADING", + READY_FOR_UPGRADE = "READY_FOR_UPGRADE", + UPGRADE_SCHEDULED = "UPGRADE_SCHEDULED", + CONFIRMATION_PENDING = "CONFIRMATION_PENDING", + CONFIRMED = "CONFIRMED", + ERROR = "ERROR", + ABORTED = "ABORTED" + }, + -- Master node specific states + main_node_states = { + NO = "NO", + STARTING = "STARTING", + MAIN_NODE = "MAIN_NODE" + }, + -- list of possible errors + errors = { + DOWNLOAD_FAILED = "download_failed", + NO_LATEST_AVAILABLE = "no_latest_data_available", + CONFIRMATION_TIME_OUT = "confirmation_timeout", + -- ABORTED = "aborted", + FW_FILE_NOT_FOUND = "firmware_file_not_found", + INVALID_FW_FILE = "invalid_firmware_file", + SAFE_UPGRADE_NOT_BOOTSTRAPED= "safe upgrade not working" + }, + fw_path = "", + su_confirm_timeout = 600, + su_start_time_out = 60, + max_retry_conunt = 4, + safeupgrade_start_mark = 0 +} + +-- should epgrade be disabled ? +-- eupgrade.set_workdir("/tmp/mesh_upgrade") + +-- Get the base url for the firmware repository in this node +function mesh_upgrade.get_repo_base_url() + local uci = config.get_uci_cursor() + local ipv4 = uci:get("network", "lan", "ipaddr") + return "http://" .. ipv4 .. mesh_upgrade.FIRMWARE_REPO_PATH +end + +-- Create a work directory if doesn't exist +function mesh_upgrade._create_workdir(workdir) + if not utils.file_exists(workdir) then + os.execute('mkdir -p ' .. workdir .. " >/dev/null 2>&1") + end + if fs.stat(workdir, "type") ~= "dir" then + error("Can't configure workdir " .. workdir) + end +end + +-- Gets local downloaded firmware's path +function mesh_upgrade.get_fw_path() + local uci = config.get_uci_cursor() + local path = uci:get('mesh-upgrade', 'main', 'fw_path') + if path ~= nil and utils.file_exists(path) then + return path + else + return " " + end +end + +function mesh_upgrade.set_fw_path(image) + mesh_upgrade.fw_path = eupgrade.WORKDIR .. "/" .. image['name'] + local uci = config.get_uci_cursor() + uci:set('mesh-upgrade', 'main', 'fw_path', mesh_upgrade.fw_path) + uci:save('mesh-upgrade') + uci:commit('mesh-upgrade') +end + +function mesh_upgrade.set_workdir(workdir) + mesh_upgrade._create_workdir(workdir) + mesh_upgrade.WORKDIR = workdir + mesh_upgrade.LATEST_JSON_FILE_NAME = utils.slugify(eupgrade._get_board_name()) .. ".json" -- latest json with local lan url file name + mesh_upgrade.LATEST_JSON_PATH = mesh_upgrade.WORKDIR .. "/" .. mesh_upgrade.LATEST_JSON_FILE_NAME -- latest json full path + mesh_upgrade.FIRMWARE_REPO_PATH = '/lros/' -- path url for firmwares + mesh_upgrade.FIRMWARE_SHARED_FOLDER = '/www/' .. mesh_upgrade.FIRMWARE_REPO_PATH +end + +mesh_upgrade.set_workdir("/tmp/mesh_upgrade") + +function mesh_upgrade.create_local_latest_json(latest_data) + for _, im in pairs(latest_data['images']) do + im['download-urls'] = {mesh_upgrade.get_repo_base_url() .. im['name']} + end + utils.write_file(mesh_upgrade.LATEST_JSON_PATH, json.stringify(latest_data)) + -- For the moment mesh upgrade will ignore the latest json signature on de main nodes + -- todo: add signature file with a valid signature... or review the signing process. +end + +function mesh_upgrade.share_firmware_packages(dest) + if dest == nil then + dest = mesh_upgrade.FIRMWARE_SHARED_FOLDER + end + local images_folder = eupgrade.WORKDIR + mesh_upgrade._create_workdir(dest) + -- json file has to be placed in a url that ends with latest + mesh_upgrade._create_workdir(dest .. "/latest") + os.execute("ln -s " .. images_folder .. "/* " .. dest .. " >/dev/null 2>&1") + os.execute("ln -s " .. mesh_upgrade.LATEST_JSON_PATH .. " " .. dest .. "/latest >/dev/null 2>&1") + os.execute("chmod -R 777 " .. dest .. " >/dev/null 2>&1") + os.execute("chmod -R 777 " .. mesh_upgrade.WORKDIR .. " >/dev/null 2>&1") + os.execute("chmod -R 777 " .. images_folder .. " >/dev/null 2>&1") +end + +-- This function will download latest firmware and expose it as +-- a local repository in order to be used for other nodes +function mesh_upgrade.start_main_node_repository(latest_data) + -- Create local repository json data + mesh_upgrade.create_local_latest_json(latest_data) + utils.execute_daemonized("eupgrade-download >/dev/null 2>&1") + mesh_upgrade.change_state(mesh_upgrade.upgrade_states.DOWNLOADING) +end + +--- Function that check if this node is able to became a main node +--- Then, call update shared state with the proper info +-- @url optional new url to get the firmware for local repo +function mesh_upgrade.become_main_node(url) + if url then + eupgrade.set_custom_api_url(url) + end + -- Check if there are a new version available (cached only) + -- 1. Check if new version is available and download it demonized using eupgrade + local latest = eupgrade.is_new_version_available(false) + if not latest then + mesh_upgrade.change_state(mesh_upgrade.upgrade_states.DEFAULT) + return { + code = "NO_NEW_VERSION", + error = "No new version is available" + } + end + -- 2. Start local repository and download latest firmware + mesh_upgrade.start_main_node_repository(latest) + mesh_upgrade.change_state(mesh_upgrade.upgrade_states.DOWNLOADING) + if mesh_upgrade.change_main_node_state(mesh_upgrade.main_node_states.STARTING) then + return { + code = "SUCCESS", + error = "" + } + end + return { + code = "NO_ABLE_TO_BECOME_MAIN_NODE", + error = "Not able to start main node repository or change to starting" + } +end + +-- Update the state with an error if eupgrade download failed +-- It returns the download status +function mesh_upgrade.check_eupgrade_download_failed() + local download_status = eupgrade.get_download_status() + local upgrade_state = mesh_upgrade.state() + + if upgrade_state == mesh_upgrade.upgrade_states.DOWNLOADING and download_status == eupgrade.STATUS_DOWNLOAD_FAILED then + mesh_upgrade.report_error(mesh_upgrade.errors.DOWNLOAD_FAILED) + end + return download_status +end + + +function mesh_upgrade.check_safeupgrade_is_working() + local result = os.execute("safe-upgrade bootstrap >/dev/null 2>&1") + local exit_code = result and (result / 256) or result + if exit_code == 121 then + -- this means that safeupgrade has been bootrsaped and is ready to work + return true + end + mesh_upgrade.report_error(mesh_upgrade.errors.SAFE_UPGRADE_NOT_BOOTSTRAPED) + return false +end + +function mesh_upgrade.start_firmware_upgrade_transaction() + if mesh_upgrade.main_node_state() ~= mesh_upgrade.main_node_states.STARTING then + return { + code = "BAD_NODE_STATE", + error = "This node main state status is not starting" + } + end + local download_status = mesh_upgrade.check_eupgrade_download_failed() + if download_status ~= eupgrade.STATUS_DOWNLOADED then + return { + code = "NO_FIRMWARE_AVAILABLE", + error = "No new firmware file downloaded" + } + end + -- this is redundant but there is an scenario when download information is + -- outdated and this check is necesary + local latest = eupgrade.is_new_version_available(true) + if not latest then + mesh_upgrade.change_state(mesh_upgrade.upgrade_states.DEFAULT) + return { + code = "NO_NEW_VERSION", + error = "No new version is available" + } + end + mesh_upgrade.set_fw_path(latest['images'][1]) + mesh_upgrade.share_firmware_packages() + -- Check if local json file exists + if not utils.file_exists(mesh_upgrade.LATEST_JSON_PATH) then + mesh_upgrade.report_error(mesh_upgrade.errors.NO_LATEST_AVAILABLE) + + return { + code = "NO_LOCAL_JSON", + error = "Local json file not found" + } + end + -- Check firmware packages are shared properly + -- we could check if the shared folder is empty or not and what files are present. Not needed imho + if not utils.file_exists(mesh_upgrade.FIRMWARE_SHARED_FOLDER) then + return { + code = "NO_SHARED_FOLDER", + error = "Shared folder not found" + } + end + -- If we get here is supposed that everything is ready to be a main node + mesh_upgrade.inform_download_location(latest['version']) + mesh_upgrade.trigger_sheredstate_publish() + return { + code = "SUCCESS", + error = "" + } +end + +-- Shared state functions -- +---------------------------- +function mesh_upgrade.report_error(error) + local uci = config.get_uci_cursor() + uci:set('mesh-upgrade', 'main', 'error', error) + uci:save('mesh-upgrade') + uci:commit('mesh-upgrade') + mesh_upgrade.change_state(mesh_upgrade.upgrade_states.ERROR) +end + +-- function to be called by nodes to start download from main. +function mesh_upgrade.start_node_download(url) + eupgrade.set_custom_api_url(url) + local cached_only = false + local url2 = eupgrade.get_upgrade_api_url() + local latest_data, message = eupgrade.is_new_version_available(cached_only) + if latest_data then + mesh_upgrade.change_state(mesh_upgrade.upgrade_states.DOWNLOADING) + local image = {} + image = eupgrade.download_firmware(latest_data) + if eupgrade.get_download_status() == eupgrade.STATUS_DOWNLOADED and image ~= nil then + mesh_upgrade.change_state(mesh_upgrade.upgrade_states.READY_FOR_UPGRADE) + mesh_upgrade.trigger_sheredstate_publish() + mesh_upgrade.set_fw_path(image) + else + mesh_upgrade.report_error(mesh_upgrade.errors.DOWNLOAD_FAILED) + end + else + mesh_upgrade.report_error(mesh_upgrade.errors.NO_LATEST_AVAILABLE) + end +end + +-- this function will be called by the main node to inform that the firmware is available +-- also will force shared state data refresh +function mesh_upgrade.inform_download_location(version) + if eupgrade.get_download_status() == eupgrade.STATUS_DOWNLOADED and mesh_upgrade.main_node_state() == + mesh_upgrade.main_node_states.STARTING then + -- TODO: setup uhttpd to serve workdir location + mesh_upgrade.set_mesh_upgrade_info({ + candidate_fw = version, + repo_url = mesh_upgrade.get_repo_base_url(), + upgrade_state = mesh_upgrade.upgrade_states.READY_FOR_UPGRADE, + error = "", + timestamp = os.time(), + main_node = mesh_upgrade.main_node_states.MAIN_NODE, + board_name = eupgrade._get_board_name(), + current_fw = eupgrade._get_current_fw_version() + }, mesh_upgrade.upgrade_states.READY_FOR_UPGRADE) + -- trigger shared state data refresh + mesh_upgrade.trigger_sheredstate_publish() + end +end + +-- Validate if the upgrade has already started +function mesh_upgrade.started() + status = mesh_upgrade.state() + return mesh_upgrade.is_active(status) + -- todo(javi): what happens if a mesh_upgrade has started more than an hour ago ? should this node abort it ? +end + +function mesh_upgrade.state() + local uci = config.get_uci_cursor() + local upgrade_state = uci:get('mesh-upgrade', 'main', 'upgrade_state') + if (upgrade_state == nil) then + uci:set('mesh-upgrade', 'main', 'upgrade_state', mesh_upgrade.upgrade_states.DEFAULT) + uci:save('mesh-upgrade') + uci:commit('mesh-upgrade') + return mesh_upgrade.upgrade_states.DEFAULT + end + return upgrade_state +end + +function mesh_upgrade.main_node_state() + local uci = config.get_uci_cursor() + local main_node_state = uci:get('mesh-upgrade', 'main', 'main_node') + if (main_node_state == nil) then + uci:set('mesh-upgrade', 'main', 'main_node', mesh_upgrade.main_node_states.NO) + uci:save('mesh-upgrade') + uci:commit('mesh-upgrade') + return mesh_upgrade.main_node_states.NO + end + return main_node_state +end + +function mesh_upgrade.mesh_upgrade_abort(silent_abortion) + if mesh_upgrade.change_state(mesh_upgrade.upgrade_states.ABORTED) then + -- mesh_upgrade.change_main_node_state(mesh_upgrade.main_node_states.NO) + local uci = config.get_uci_cursor() + uci:set('mesh-upgrade', 'main', 'retry_count', 0) + uci:save('mesh-upgrade') + uci:commit('mesh-upgrade') + if silent_abortion == nil or silent_abortion == false then + mesh_upgrade.trigger_sheredstate_publish() + end + -- kill possible safe upgrade command + utils.unsafe_shell("kill $(ps| grep 'sh -c (( sleep " .. mesh_upgrade.su_start_time_out .. + "; safe-upgrade upgrade'| awk '{print $1}')") + end + return { + code = "SUCCESS", + error = "" + } +end + +-- This line will generate recursive dependencies like in pirania package +function mesh_upgrade.trigger_sheredstate_publish() + utils.execute_daemonized("sleep 1; \ + /usr/share/shared-state/publishers/shared-state-publish_mesh_wide_upgrade") + -- minimum renewal time is 30s if not able to renew info just wait, if flirts fails the second success, + -- if the first succeeds the second will fail. Sadly merge will output 0 so both times will make sync. + utils.execute_daemonized("sleep 30; \ + /usr/share/shared-state/publishers/shared-state-publish_mesh_wide_upgrade && shared-state-async sync mesh_wide_upgrade") +end + +function mesh_upgrade.change_main_node_state(newstate) + local main_node_state = mesh_upgrade.main_node_state() + if newstate == mesh_upgrade.main_node_states.MAIN_NODE and main_node_state ~= mesh_upgrade.main_node_states.STARTING then + return false + end + + local uci = config.get_uci_cursor() + uci:set('mesh-upgrade', 'main', 'main_node', newstate) + uci:save('mesh-upgrade') + uci:commit('mesh-upgrade') + return true +end + +-- ! changes the state of the upgrade and verifies that state transition is possible. +function mesh_upgrade.change_state(newstate) + local actual_state = mesh_upgrade.state() + -- If the state is the same just return + if newstate == actual_state then + return false + end + + if newstate == mesh_upgrade.upgrade_states.DOWNLOADING and actual_state ~= mesh_upgrade.upgrade_states.DEFAULT and + actual_state ~= mesh_upgrade.upgrade_states.ERROR and actual_state ~= mesh_upgrade.upgrade_states.CONFIRMED and + actual_state ~= mesh_upgrade.upgrade_states.ABORTED then + return false + elseif newstate == mesh_upgrade.upgrade_states.READY_FOR_UPGRADE and actual_state ~= + mesh_upgrade.upgrade_states.DOWNLOADING then + return false + elseif newstate == mesh_upgrade.upgrade_states.UPGRADE_SCHEDULED and actual_state ~= + mesh_upgrade.upgrade_states.READY_FOR_UPGRADE then + return false + elseif newstate == mesh_upgrade.upgrade_states.CONFIRMATION_PENDING and actual_state ~= + mesh_upgrade.upgrade_states.UPGRADE_SCHEDULED then + return false + elseif newstate == mesh_upgrade.upgrade_states.CONFIRMED and actual_state ~= + mesh_upgrade.upgrade_states.CONFIRMATION_PENDING then + return false + end + local uci = config.get_uci_cursor() + uci:set('mesh-upgrade', 'main', 'upgrade_state', newstate) + uci:save('mesh-upgrade') + uci:commit('mesh-upgrade') + return true +end + +-- this function will retry max_retry_conunt times in case of error +-- It will only fetch new information if main node has aborted or main node is +-- ready for upgraade +function mesh_upgrade.become_bot_node(main_node_upgrade_data) + local actual_state = mesh_upgrade.get_node_status() + -- only abort if my main node has aborted + if main_node_upgrade_data.upgrade_state == mesh_upgrade.upgrade_states.ABORTED and + main_node_upgrade_data.timestamp == actual_state.timestamp then + utils.log("main node has aborted") + mesh_upgrade.mesh_upgrade_abort() + return + elseif main_node_upgrade_data.upgrade_state == mesh_upgrade.upgrade_states.READY_FOR_UPGRADE then + if mesh_upgrade.started() then + utils.log("node has already started") + return + else + utils.log("node has not started") + + if actual_state.timestamp == main_node_upgrade_data.timestamp and actual_state.repo_url == + main_node_upgrade_data.repo_url then + main_node_upgrade_data.retry_count = actual_state.retry_count + 1 + else + main_node_upgrade_data.retry_count = 0 + end + if main_node_upgrade_data.retry_count < mesh_upgrade.max_retry_conunt then + utils.log("seting upgrade info") + main_node_upgrade_data.main_node = mesh_upgrade.main_node_states.NO + if (mesh_upgrade.set_mesh_upgrade_info(main_node_upgrade_data, mesh_upgrade.upgrade_states.DOWNLOADING)) then + mesh_upgrade.start_node_download(main_node_upgrade_data.repo_url) + -- trigger shared state data refresh + mesh_upgrade.trigger_sheredstate_publish() + end + else + utils.log("max retry_count has been reached") + end + end + end + utils.log("Main node is not ready for upgrade") + +end + +-- set download information for the new firmware from main node +-- Called by a shared state hook in bot nodes +function mesh_upgrade.set_mesh_upgrade_info(upgrade_data, upgrade_state) + local uci = config.get_uci_cursor() + if string.match(upgrade_data.repo_url, "https?://[%w-_%.%?%.:/%+=&]+") ~= nil -- todo (javi): perform aditional checks + then + if (mesh_upgrade.change_state(upgrade_state)) then + uci:set('mesh-upgrade', 'main', "mesh-upgrade") + uci:set('mesh-upgrade', 'main', 'repo_url', upgrade_data.repo_url) + uci:set('mesh-upgrade', 'main', 'candidate_fw', upgrade_data.candidate_fw) + uci:set('mesh-upgrade', 'main', 'error', "") + uci:set('mesh-upgrade', 'main', 'retry_count', upgrade_data.retry_count or 0) + -- timestamp is used as id ... every node must have the same one + uci:set('mesh-upgrade', 'main', 'timestamp', upgrade_data.timestamp) + uci:set('mesh-upgrade', 'main', 'main_node', upgrade_data.main_node) + uci:save('mesh-upgrade') + uci:commit('mesh-upgrade') + return true + else + return false + end + else + return false + end +end + +function mesh_upgrade.toboolean(str) + if str == "true" then + return true + end + return false +end + +-- ! Read status from UCI +function mesh_upgrade.get_node_status() + mesh_upgrade.check_safeupgrade_is_working() + local uci = config.get_uci_cursor() + local upgrade_data = {} + upgrade_data.candidate_fw = uci:get('mesh-upgrade', 'main', 'candidate_fw') + upgrade_data.repo_url = uci:get('mesh-upgrade', 'main', 'repo_url') + upgrade_data.eupgradestate = mesh_upgrade.check_eupgrade_download_failed() + upgrade_data.upgrade_state = mesh_upgrade.state() + local safe_upgrade_confirm_remaining = tonumber(utils.unsafe_shell("safe-upgrade confirm-remaining")) + if (upgrade_data.upgrade_state == mesh_upgrade.upgrade_states.UPGRADE_SCHEDULED) then + if (safe_upgrade_confirm_remaining and safe_upgrade_confirm_remaining > 1) then + mesh_upgrade.change_state(mesh_upgrade.upgrade_states.CONFIRMATION_PENDING) + elseif utils.file_exists(mesh_upgrade.get_fw_path()) == false then + mesh_upgrade.report_error(mesh_upgrade.errors.FW_FILE_NOT_FOUND) + end + end + upgrade_data.upgrade_state = uci:get('mesh-upgrade', 'main', 'upgrade_state') + upgrade_data.error = uci:get('mesh-upgrade', 'main', 'error') + upgrade_data.retry_count = tonumber(uci:get('mesh-upgrade', 'main', 'retry_count')) + upgrade_data.timestamp = tonumber(uci:get('mesh-upgrade', 'main', 'timestamp')) + mesh_upgrade.safeupgrade_start_mark = tonumber(uci:get('mesh-upgrade', 'main', 'safeupgrade_start_mark')) or 0 + upgrade_data.safeupgrade_start_mark = mesh_upgrade.safeupgrade_start_mark + + mesh_upgrade.su_start_time_out = tonumber(uci:get('mesh-upgrade', 'main', 'su_start_time_out')) or + mesh_upgrade.su_start_time_out + upgrade_data.su_start_time_out = mesh_upgrade.su_start_time_out + + upgrade_data.main_node = mesh_upgrade.main_node_state() + upgrade_data.board_name = eupgrade._get_board_name() + upgrade_data.current_fw = eupgrade._get_current_fw_version() + upgrade_data.node_ip = uci:get("network", "lan", "ipaddr") + upgrade_data.safeupgrade_start_remining = (mesh_upgrade.su_start_time_out - + (utils.uptime_s() - mesh_upgrade.safeupgrade_start_mark) > 0 and + mesh_upgrade.su_start_time_out - + (utils.uptime_s() - mesh_upgrade.safeupgrade_start_mark) or -1) + upgrade_data.confirm_remining = safe_upgrade_confirm_remaining + return upgrade_data +end + +function mesh_upgrade.start_safe_upgrade(su_start_delay, su_confirm_timeout) + mesh_upgrade.su_start_time_out = su_start_delay or mesh_upgrade.su_start_time_out + mesh_upgrade.su_confirm_timeout = su_confirm_timeout or mesh_upgrade.su_confirm_timeout + + if mesh_upgrade.state() == mesh_upgrade.upgrade_states.READY_FOR_UPGRADE and + mesh_upgrade.check_safeupgrade_is_working() then + if utils.file_exists(mesh_upgrade.get_fw_path()) then + + -- veryfy the image before starting + if os.execute("sysupgrade --test " .. mesh_upgrade.get_fw_path()) ~= 0 then + mesh_upgrade.report_error(mesh_upgrade.errors.INVALID_FW_FILE) + return { + code = "NOT_ABLE_TO_START_UPGRADE", + error = "Invalid Firmware" + } + end + + -- perform safe upgrade preserving config and rebooting after 600 sec if + -- no confirmation is received + + -- perform a full config backup including mesh_upgrade config file needed for the next image + -- surprisingly this does not preserve nodename + + -- os.execute("sysupgrade -b ".. mesh_upgrade.WORKDIR.."/mesh_upgrade_cfg.tgz") + + -- config = require("lime.config") + local keep = config.get("system", "keep_on_upgrade", "") + keep = keep .. " lime-mesh-upgrade" + config.set("system", "keep_on_upgrade", keep) -- use set but not commit, so this configuration wont be preserved. + + mesh_upgrade.change_state(mesh_upgrade.upgrade_states.UPGRADE_SCHEDULED) + mesh_upgrade.safeupgrade_start_mark = utils.uptime_s() + local uci = config.get_uci_cursor() + uci:set('mesh-upgrade', 'main', 'safeupgrade_start_mark', mesh_upgrade.safeupgrade_start_mark) + uci:set('mesh-upgrade', 'main', 'su_start_time_out', mesh_upgrade.su_start_time_out) + uci:set('mesh-upgrade', 'main', 'su_confirm_timeout', mesh_upgrade.su_confirm_timeout) + uci:save('mesh-upgrade') + uci:commit('mesh-upgrade') + + mesh_upgrade.trigger_sheredstate_publish() + -- upgrade must be executed after a safe upgrade timeout to enable all nodes to start_safe_upgrade + utils.execute_daemonized("sleep " .. mesh_upgrade.su_start_time_out .. + "; safe-upgrade upgrade --reboot-safety-timeout=" .. + mesh_upgrade.su_confirm_timeout .. " " .. mesh_upgrade.get_fw_path()) + + return { + code = "SUCCESS", + error = "", + su_start_time_out = mesh_upgrade.su_start_time_out, + su_confirm_timeout = mesh_upgrade.su_confirm_timeout + + } + else + mesh_upgrade.report_error(mesh_upgrade.errors.FW_FILE_NOT_FOUND) + return { + code = "NOT_ABLE_TO_START_UPGRADE", + error = "Firmware not found" + } + end + else + return { + code = "NOT_READY_FOR_UPGRADE", + error = "Not READY FOR UPGRADE" + } + end +end + +-- This command requires that the configuration be preserved across upgrade, +-- maybe this change achieves this objective + +-- diff --git a/packages/lime-system/files/etc/config/lime-defaults b/packages/lime-system/files/etc/config/lime-defaults +-- index 5f5c4a31..8d55d949 100644 +-- --- a/packages/lime-system/files/etc/config/lime-defaults +-- +++ b/packages/lime-system/files/etc/config/lime-defaults +-- @@ -8,7 +8,7 @@ +-- config lime system +-- option hostname 'LiMe-%M4%M5%M6' +-- option domain 'thisnode.info' +-- - option keep_on_upgrade 'libremesh dropbear minimum-essential /etc/sysupgrade.conf' +-- + option keep_on_upgrade 'libremesh dropbear minimum-essential /etc/sysupgrade.conf /etc/config/mesh-upgrade' +-- option root_password_policy 'DO_NOTHING' +-- option root_password_secret '' +-- option deferable_reboot_uptime_s '97200' +function mesh_upgrade.confirm() + if mesh_upgrade.get_node_status().upgrade_state == mesh_upgrade.upgrade_states.CONFIRMATION_PENDING then + local shell_output = utils.unsafe_shell("safe-upgrade confirm") + if mesh_upgrade.change_state(mesh_upgrade.upgrade_states.CONFIRMED) then + return { + code = "SUCCESS" + } + end + end + return { + code = "NOT_READY_TO_CONFIRM", + error = "NOT_READY_TO_CONFIRM" + } +end + +-- An active node is involved in a transaction +function mesh_upgrade.is_active(status) + if status == mesh_upgrade.upgrade_states.DEFAULT or -- if an error has occurred then there is no transaction + status == mesh_upgrade.upgrade_states.ERROR or + status == mesh_upgrade.upgrade_states.ABORTED or + status == mesh_upgrade.upgrade_states.CONFIRMED then + return false + end + return true +end + +function mesh_upgrade.verify_network_consistency (network_state) + utils.unsafe_shell('logger -p daemon.info -t "async: mesh upgrade" "verifying" ') + local actual_status = mesh_upgrade.get_node_status() + + local main_node = "" + for node, s_s_data in pairs(network_state) do + --if any node has started an upgrade process and start one too? + --only fetch the info from the master node publication? + if s_s_data.main_node == mesh_upgrade.main_node_states.MAIN_NODE then + if mesh_upgrade.is_active(s_s_data.upgrade_state) then + if main_node == "" then + main_node = node + utils.unsafe_shell('logger -p daemon.info -t "async: mesh upgrade" "there is one main node '..main_node..' , ok"') + else + utils.unsafe_shell('logger -p daemon.info -t "async: mesh upgrade" "there are two active main nodes '.. node ..' and '..main_node..' , aborting"') + mesh_upgrade.mesh_upgrade_abort() + return + end + else + --there is an inactive main node + utils.unsafe_shell('logger -p daemon.info -t "async: mesh upgrade" "there is an inactive main node '.. node ..' "') + if mesh_upgrade.started() and network_state[node].timestamp == actual_status.timestamp then + -- i should abort too + utils.unsafe_shell('logger -p daemon.info -t "async: mesh upgrade" "i should abort we share timestamps"') + mesh_upgrade.mesh_upgrade_abort() + utils.unsafe_shell('logger -p daemon.info -t "async: mesh upgrade" "i should not abort dont share timestamps"') + end + + end + end + end + --there is only one main node + if main_node ~= "" then + if not mesh_upgrade.started() and main_node ~= utils.hostname() then + utils.unsafe_shell('logger -p daemon.info -t "async: mesh upgrade" "' ..utils.hostname()..' become' ..main_node..' _bot_node "') + mesh_upgrade.become_bot_node(network_state[main_node]) + else + utils.unsafe_shell('logger -p daemon.info -t "async: mesh upgrade" "already started a transaction "') + if network_state[main_node].timestamp == actual_status.timestamp then + --"ok" + utils.unsafe_shell('logger -p daemon.info -t "async: mesh upgrade" "main node and bot node timestamp are equal"') + else + --I am in a transaction and main node is in an other + utils.unsafe_shell('logger -p daemon.info -t "async: mesh upgrade" "main node and bot node timestamp are different"') + mesh_upgrade.mesh_upgrade_abort(true) + --this will lead to a double write to shared state. + utils.unsafe_shell('logger -p daemon.info -t "async: mesh upgrade" "main node and bot node timestamp are different"') + utils.unsafe_shell('logger -p daemon.info -t "async: mesh upgrade" " become_bot_node "') + mesh_upgrade.become_bot_node(network_state[main_node]) + end + end + end +end + + +return mesh_upgrade diff --git a/packages/lime-mesh-upgrade/files/usr/libexec/rpcd/lime-mesh-upgrade b/packages/lime-mesh-upgrade/files/usr/libexec/rpcd/lime-mesh-upgrade new file mode 100755 index 000000000..d7b783a37 --- /dev/null +++ b/packages/lime-mesh-upgrade/files/usr/libexec/rpcd/lime-mesh-upgrade @@ -0,0 +1,97 @@ +#!/usr/bin/env lua + +--[[ + Copyright (C) 2013-2023 LibreMesh.org + This is free software, licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 + + Copyright 2023 Selankon +]] -- +local json = require 'luci.jsonc' +local mesh_upgrade = require 'lime-mesh-upgrade' +local ubus = require "ubus" + +local conn = ubus.connect() +if not conn then + error("Failed to connect to ubus") +end + +local function become_main_node(msg) + local result = mesh_upgrade.become_main_node(msg.url) + return utils.printJson(result) +end + +local function start_firmware_upgrade_transaction(msg) + local result = mesh_upgrade.start_firmware_upgrade_transaction() + return utils.printJson(result) +end + +-- It gets the mesh upgrade status for this node +-- Shortcut to get the status without await the shared state synchronization +local function get_node_status(msg) + local result = mesh_upgrade.get_node_status() + return utils.printJson(result) +end + +local function start_safe_upgrade(msg) + local result = mesh_upgrade.start_safe_upgrade(msg.start_delay ,msg.confirm_timeout) + return utils.printJson(result) +end +-- to be called after successfully reboot +local function confirm(msg) + local result = mesh_upgrade.confirm() + return utils.printJson(result) +end + +local function abort(msg) + local result = mesh_upgrade.mesh_upgrade_abort() + return utils.printJson(result) +end + +local methods = { + become_main_node = { + url = 'value' + }, + start_firmware_upgrade_transaction = { + no_params = 0 + }, + get_node_status = { + no_params = 0 + }, + start_safe_upgrade = { + confirm_timeout = 0, + start_delay = 0, + }, + confirm_boot_partition = { + no_params = 0 + }, + abort = { + no_params = 0 + } + +} + +if arg[1] == 'list' then + utils.printJson(methods) +end + +if arg[1] == 'call' then + local msg = utils.rpcd_readline() + msg = json.parse(msg) + if arg[2] == 'become_main_node' then + become_main_node(msg) + elseif arg[2] == 'start_firmware_upgrade_transaction' then + start_firmware_upgrade_transaction(msg) + elseif arg[2] == 'get_node_status' then + get_node_status(msg) + elseif arg[2] == 'start_safe_upgrade' then + start_safe_upgrade(msg) + elseif arg[2] == 'confirm_boot_partition' then + confirm(msg) + elseif arg[2] == 'abort' then + abort(msg) + else + utils.printJson({ + error = "Method not found" + }) + end +end diff --git a/packages/lime-mesh-upgrade/files/usr/share/rpcd/acl.d/lime-mesh-upgrade.json b/packages/lime-mesh-upgrade/files/usr/share/rpcd/acl.d/lime-mesh-upgrade.json new file mode 100644 index 000000000..dfeb8a36c --- /dev/null +++ b/packages/lime-mesh-upgrade/files/usr/share/rpcd/acl.d/lime-mesh-upgrade.json @@ -0,0 +1,15 @@ +{ + "lime-app": { + "description": "lime-mesh-upgrade public access", + "read": { + "ubus": { + "lime-mesh-upgrade": [ "*" ] + } + }, + "write": { + "ubus": { + "lime-mesh-upgrade": [ "*" ] + } + } + } +} \ No newline at end of file diff --git a/packages/lime-mesh-upgrade/tests/latest/librerouter-librerouter-v1.json b/packages/lime-mesh-upgrade/tests/latest/librerouter-librerouter-v1.json new file mode 100644 index 000000000..e1b65839d --- /dev/null +++ b/packages/lime-mesh-upgrade/tests/latest/librerouter-librerouter-v1.json @@ -0,0 +1,25 @@ +{ + "metadata-version": 1, + "images": [ + { + "name": "upgrade-lr-1.5.sh", + "type": "installer", + "download-urls": [ + "http://10.13.0.2:8080/librerouteros-1.5-r0+11434-e93615c947-ath79-generic-librerouter_librerouter-v1-squashfs-sysupgrade.bin", + "http://10.234.77.12:8080/librerouteros-1.5-r0+11434-e93615c947-ath79-generic-librerouter_librerouter-v1-squashfs-sysupgrade.bin" + ], + "sha256": "2da0abb549d6178a7978b357be3493d5aff5c07b993ea0962575fa61bef18c27" + }, + { + "name": "firmware.bin", + "type": "sysupgrade", + "download-urls": [ + "http://10.234.77.12:8080/librerouteros-1.5-r0+11434-e93615c947-ath79-generic-librerouter_librerouter-v1-squashfs-sysupgrade.bin" + ], + "sha256": "2da0abb549d6178a7978b357be3493d5aff5c07b993ea0962575fa61bef18c27" + } + ], + "board": "test-board", + "version": "LibreRouterOs 1.5", + "release-info-url": "https://foro.librerouter.org/t/lanzamiento-librerouteros-1-5/337" +} \ No newline at end of file diff --git a/packages/lime-mesh-upgrade/tests/latest/librerouter-librerouter-v1.json.sig b/packages/lime-mesh-upgrade/tests/latest/librerouter-librerouter-v1.json.sig new file mode 100644 index 000000000..ada590cbc --- /dev/null +++ b/packages/lime-mesh-upgrade/tests/latest/librerouter-librerouter-v1.json.sig @@ -0,0 +1,2 @@ +untrusted comment: signed by key 47513b83fbc579cd +RWRHUTuD+8V5zSCR6+HGnzSU8qhf1d8K8PqOCo/OYLFAXBeiICUXV33BY3o1ihDWcGbFTBLh8jidfhSkWQev5NoT1PTOTOLvZwc= \ No newline at end of file diff --git a/packages/lime-mesh-upgrade/tests/test_lime-mesh-upgrade.lua b/packages/lime-mesh-upgrade/tests/test_lime-mesh-upgrade.lua new file mode 100644 index 000000000..612e5260c --- /dev/null +++ b/packages/lime-mesh-upgrade/tests/test_lime-mesh-upgrade.lua @@ -0,0 +1,506 @@ +local config = require 'lime.config' +local eupgrade = require 'eupgrade' + +local boardname = 'librerouter-v1' +stub(eupgrade, '_get_board_name', function() + return boardname +end) + +local mesh_wide_sample = [[ + { + "LiMe-8a50aa": { + "eupgradestate": "not-initiated", + "confirm_remining": -1, + "safeupgrade_start_remining": -1, + "safeupgrade_start_mark": 0, + "upgrade_state": "UPGRADE_SCHEDULED", + "current_fw": "LibreRouterOs 23.05-SNAPSHOT r1+1-48c81b80b2", + "main_node": "NO", + "node_ip": "10.13.80.170", + "board_name": "librerouter,librerouter-v1", + "su_start_time_out": 0, + "timestamp": 0, + "error": "0", + "retry_count": 0 + }, + "LiMe-a51ed1": { + "repo_url": "http://10.13.80.170/lros/", + "confirm_remining": -1, + "candidate_fw": "LibreRouterOs r23744", + "safeupgrade_start_remining": -1, + "safeupgrade_start_mark": 0, + "retry_count": 0, + "upgrade_state": "ERROR", + "current_fw": "LibreRouterOs 23.05-SNAPSHOT r1+1-48c81b80b2", + "main_node": "NO", + "node_ip": "10.13.30.209", + "board_name": "librerouter,librerouter-v1", + "su_start_time_out": 0, + "timestamp": 1713282042, + "error": "no_latest_data_available", + "eupgradestate": "not-initiated" + } + } + +]] + +confirm_remaining = -1 + +stub(utils, 'unsafe_shell', function(command) + if command == "safe-upgrade confirm-remaining" then + return confirm_remaining + elseif command == "shared-state-async get mesh_wide_upgrade" then + return mesh_wide_sample + end + print(command) + return confirm_remaining + +end) + + + +local utils = require "lime.utils" +local lime_mesh_upgrade = {} +local test_utils = require "tests.utils" +local json = require 'luci.jsonc' +local uci + +local upgrade_data = { + candidate_fw = "xxxx", + repo_url = "http://repo.librerouter.org/lros/api/v1/latest/", + upgrade_state = "READY_FOR_UPGRADE", + error = "CODE", + main_node = "true", + timestamp = 02, + current_fw = "LibreRouterOs 1.5 r0+11434-e93615c947", + board_name = "qemu-standard-pc-i440fx-piix-1996" +} + +local latest_release_data = [[ +{ + "metadata-version": 1, + "images": [ + { + "name": "upgrade-lr-1.5.sh", + "type": "installer", + "download-urls": [ + "http://repo.librerouter.org/lros/releases/1.5/targets/ath79/generic/upgrade-lr-1.5.sh" + ], + "sha256": "cec8920f93055cc57cfde1f87968e33ca5215b2df88611684195077402079acb" + }, + { + "name": "firmware.bin", + "type": "sysupgrade", + "download-urls": [ + "http://repo.librerouter.org/lros/releases/1.5/targets/ath79/generic/librerouteros-1.5-r0+11434-e93615c947-ath79-generic-librerouter_librerouter-v1-squashfs-sysupgrade.bin" + ], + "sha256": "2da0abb549d6178a7978b357be3493d5aff5c07b993ea0962575fa61bef18c27" + } + ], + "board": "test-board", + "version": "LibreRouterOs 1.5", + "release-info-url": "https://foro.librerouter.org/t/lanzamiento-librerouteros-1-5/337" +} +]] + +describe('LiMe mesh upgrade', function() + + it('test get mesh config fresh start', function() + local fw_version = 'LibreMesh 19.02' + stub(eupgrade, '_get_current_fw_version', function() + return fw_version + end) + local status = lime_mesh_upgrade.get_node_status() + assert.is.equal(status.upgrade_state, lime_mesh_upgrade.upgrade_states.DEFAULT) + assert.is.equal(status.main_node, lime_mesh_upgrade.main_node_states.NO) + assert.is.equal(status.current_fw, fw_version) + assert.is.equal(status.board_name, boardname) + assert.is.equal(lime_mesh_upgrade.started(), false) + end) + + it('test set error ', function() + stub(eupgrade, '_get_current_fw_version', function() + return 'LibreMesh 19.05' + end) + lime_mesh_upgrade.report_error(lime_mesh_upgrade.errors.CONFIRMATION_TIME_OUT) + status = lime_mesh_upgrade.get_node_status() + assert.is.equal(status.error, lime_mesh_upgrade.errors.CONFIRMATION_TIME_OUT) + assert.is.equal(status.upgrade_state, lime_mesh_upgrade.upgrade_states.ERROR) + end) + + it('test abort ', function() + stub(utils, 'execute_daemonized', function() + end) + stub(eupgrade, '_get_current_fw_version', function() + return 'LibreMesh 19.05' + end) + stub(utils, 'hostname', function() + return "LiMe-8a50aa" + end) + lime_mesh_upgrade.mesh_upgrade_abort() + status = lime_mesh_upgrade.get_node_status() + assert.is.equal(status.upgrade_state, lime_mesh_upgrade.upgrade_states.ABORTED) + assert.stub.spy(utils.execute_daemonized).was.called.with("sleep 30; \ + /usr/share/shared-state/publishers/shared-state-publish_mesh_wide_upgrade && shared-state-async sync mesh_wide_upgrade") + end) + + it('test set upgrade info and fail NO_LATEST_AVAILABLE', function() + stub(eupgrade, '_check_signature', function() + return true + end) + stub(eupgrade, '_file_sha256', function() + return 'fbd95fc091ea10cfa05cfb0ef870da43124ac7c1402890eb8f03b440c57d7b5' + end) + stub(eupgrade, '_get_current_fw_version', function() + return 'LibreMesh 1.4' + end) + lime_mesh_upgrade.become_bot_node(upgrade_data) + status = lime_mesh_upgrade.get_node_status() + assert.is.equal(status.main_node, lime_mesh_upgrade.main_node_states.NO) + assert.is.equal(status.repo_url, upgrade_data.repo_url) + assert.is.equal(status.upgrade_state, lime_mesh_upgrade.upgrade_states.ERROR) + assert.is.equal(status.error, lime_mesh_upgrade.errors.NO_LATEST_AVAILABLE) + end) + + it('test set upgrade info and fail to download', function() + stub(eupgrade, '_check_signature', function() + return true + end) + stub(utils, 'http_client_get', function() + return latest_release_data + end) + stub(eupgrade, '_file_sha256', function() + return 'fbd95fc091ea10cfa05cfb0ef870da43124ac7c1402890eb8f03b440c57d7b5' + end) + stub(eupgrade, '_get_current_fw_version', function() + return 'LibreMesh 1.4' + end) + assert.is.equal('LibreRouterOs 1.5', eupgrade.is_new_version_available()['version']) + lime_mesh_upgrade.become_bot_node(upgrade_data) + status = lime_mesh_upgrade.get_node_status() + assert.is.equal(status.main_node, lime_mesh_upgrade.main_node_states.NO) + assert.is.equal(status.repo_url, upgrade_data.repo_url) + assert.is.equal(status.upgrade_state, lime_mesh_upgrade.upgrade_states.ERROR) + assert.is.equal(status.error, lime_mesh_upgrade.errors.DOWNLOAD_FAILED) + end) + + it('test become botnode and assert status ready_for_upgrade', function() + stub(eupgrade, '_get_current_fw_version', function() + return 'LibreMesh 19.05' + end) + stub(eupgrade, '_check_signature', function() + return true + end) + stub(utils, 'http_client_get', function() + return latest_release_data + end) + stub(eupgrade, '_file_sha256', function() + return 'cec8920f93055cc57cfde1f87968e33ca5215b2df88611684195077402079acb' + end) + + assert.is.equal('LibreRouterOs 1.5', eupgrade.is_new_version_available()['version']) + lime_mesh_upgrade.become_bot_node(upgrade_data) + local status = lime_mesh_upgrade.get_node_status() + assert.is.equal(status.main_node, upgrade_data.main_node) + assert.is.equal(status.repo_url, upgrade_data.repo_url) + assert.is.equal(status.upgrade_state, lime_mesh_upgrade.upgrade_states.READY_FOR_UPGRADE) + end) + + it('test become botnode and assert status ready_for_upgrade', function() + stub(eupgrade, '_get_current_fw_version', function() + return 'LibreMesh 19.05' + end) + stub(eupgrade, '_check_signature', function() + return true + end) + stub(utils, 'http_client_get', function() + return latest_release_data + end) + stub(eupgrade, '_file_sha256', function() + return 'cec8920f93055cc57cfde1f87968e33ca5215b2df88611684195077402079acb' + end) + + uci = test_utils.setup_test_uci() + uci:set('mesh-upgrade', 'main', "mesh-upgrade") + uci:set('mesh-upgrade', 'main', "upgrade_state", "UPGRADE_SCHEDULED") + uci:save('mesh-upgrade') + utils.log("about to become bot node") + assert.is.equal('LibreRouterOs 1.5', eupgrade.is_new_version_available()['version']) + lime_mesh_upgrade.become_bot_node(upgrade_data) + local status = lime_mesh_upgrade.get_node_status() + assert.is.equal(status.main_node, upgrade_data.main_node) + --assert.is.equal(status.upgrade_state, lime_mesh_upgrade.upgrade_states.ERROR) + + utils.log("about to become bot node seccond time") + uci = test_utils.setup_test_uci() + uci:set('mesh-upgrade', 'main', "mesh-upgrade") + uci:set('mesh-upgrade', 'main', "upgrade_state", "CONFIRMED") + uci:save('mesh-upgrade') + + lime_mesh_upgrade.become_bot_node(upgrade_data) + local status = lime_mesh_upgrade.get_node_status() + assert.is.equal(status.main_node, upgrade_data.main_node) + --assert.is.equal(status.repo_url, upgrade_data.repo_url) + assert.is.equal(status.upgrade_state, lime_mesh_upgrade.upgrade_states.READY_FOR_UPGRADE) + + end) + + it('test get fw path', function() + + local fw_path = lime_mesh_upgrade.get_fw_path() + assert.is.equal(fw_path, " ") + + stub(eupgrade, '_get_current_fw_version', function() + return 'LibreMesh 19.05' + end) + stub(eupgrade, '_check_signature', function() + return true + end) + stub(utils, 'http_client_get', function() + return latest_release_data + end) + stub(eupgrade, '_file_sha256', function() + return 'cec8920f93055cc57cfde1f87968e33ca5215b2df88611684195077402079acb' + end) + stub(utils, 'file_exists', function() + return true + end) + + lime_mesh_upgrade.become_bot_node(upgrade_data) + status = lime_mesh_upgrade.get_node_status() + assert.is.equal(status.main_node, upgrade_data.main_node) + assert.is.equal(status.repo_url, upgrade_data.repo_url) + assert.is.equal(status.upgrade_state, lime_mesh_upgrade.upgrade_states.READY_FOR_UPGRADE) + local fw_path = lime_mesh_upgrade.get_fw_path() + assert.is.equal(fw_path, '/tmp/eupgrades/upgrade-lr-1.5.sh') + + end) + + it('test become main node changes the state to STARTING', function() + stub(eupgrade, 'is_new_version_available', function() + return json.parse(latest_release_data) + end) + stub(lime_mesh_upgrade, 'start_main_node_repository', function() + end) + stub(eupgrade, '_get_current_fw_version', function() + end) + local res = lime_mesh_upgrade.become_main_node() + assert.is.equal(res.code, 'SUCCESS') + local status = lime_mesh_upgrade.get_node_status() + assert.is.equal(status.main_node, lime_mesh_upgrade.main_node_states.STARTING) + assert.is.equal(status.upgrade_state, lime_mesh_upgrade.upgrade_states.DOWNLOADING) + end) + + it('test custom latest json file is created', function() + lime_mesh_upgrade.create_local_latest_json(json.parse(latest_release_data)) + local filexists = utils.file_exists(lime_mesh_upgrade.LATEST_JSON_PATH) + assert(filexists, "File not found: " .. lime_mesh_upgrade.LATEST_JSON_PATH) + end) + + it('test set_up_firmware_repository download the files correctly and fix the url on json', function() + + lime_mesh_upgrade.create_local_latest_json(json.parse(latest_release_data)) + local latest = json.parse(utils.read_file(lime_mesh_upgrade.LATEST_JSON_PATH)) + local repo_url = lime_mesh_upgrade.FIRMWARE_REPO_PATH + for _, im in pairs(latest['images']) do + for a, url in pairs(im['download-urls']) do + assert(string.find(url, repo_url)) + end + end + end) + + it('test that link properly the files downloaded by eupgrade to desired destination', function() + -- Create some dummy files + local files = {"file1", "file2", "file3"} + local dest = "/tmp/www" .. lime_mesh_upgrade.FIRMWARE_REPO_PATH + -- Delete previous links if exist + os.execute("rm -rf " .. dest) + for _, f in pairs(files) do + utils.write_file(eupgrade.WORKDIR .. "/" .. f, "dummy") + end + -- Create latest json file also + utils.write_file(lime_mesh_upgrade.LATEST_JSON_PATH, "dummy") + -- Create the links + lime_mesh_upgrade.share_firmware_packages(dest) + -- Check if all files exist in the destination folder + for _, f in pairs(files) do + local file_path = dest .. "/" .. f + local file_exists = utils.file_exists(file_path) + assert(file_exists, "File not found: " .. file_path) + end + -- Check that the local json file is also there + local json_link = dest .. "latest/" .. lime_mesh_upgrade.LATEST_JSON_FILE_NAME + local file_exists = utils.file_exists(json_link) + assert(file_exists, "File not found: " .. json_link) + end) + + it('test become main node change state to READY_FOR_UPGRADE', function() + config.set('network', 'lime') + config.set('network', 'main_ipv4_address', '10.1.1.0/16') + config.set('network', 'main_ipv6_address', 'fd%N1:%N2%N3:%N4%N5::/64') + config.set('network', 'protocols', {'lan'}) + config.set('wifi', 'lime') + config.set('wifi', 'ap_ssid', 'LibreMesh.org') + uci:commit('lime') + + stub(eupgrade, 'is_new_version_available', function() + return json.parse(latest_release_data) + end) + stub(lime_mesh_upgrade, 'start_main_node_repository', function() + end) + stub(eupgrade, '_get_current_fw_version', function() + + end) + local dest = "/tmp/www" .. lime_mesh_upgrade.FIRMWARE_REPO_PATH + -- Delete previous links if exist + os.execute("rm -rf /tmp/www/lros/") + lime_mesh_upgrade.FIRMWARE_SHARED_FOLDER = "/tmp/" + local res = lime_mesh_upgrade.become_main_node('http://repo.librerouter.org/lros/api/v1/') + assert.is.equal(res.code, 'SUCCESS') + local status = lime_mesh_upgrade.get_node_status() + assert.is.equal(status.upgrade_state, lime_mesh_upgrade.upgrade_states.DOWNLOADING) + lime_mesh_upgrade.start_firmware_upgrade_transaction() + status = lime_mesh_upgrade.get_node_status() + assert.is.equal(status.upgrade_state, lime_mesh_upgrade.upgrade_states.READY_FOR_UPGRADE) + assert.is.equal(status.candidate_fw, json.parse(latest_release_data).version) + assert.is.equal(status.board_name, boardname) + assert.is.equal(status.main_node, lime_mesh_upgrade.main_node_states.MAIN_NODE) + assert.is.equal(status.repo_url, 'http://10.5.0.5/lros/') + end) + + it('test start_safe_upgrade default timeouts', function() + + stub(utils, 'execute_daemonized', function() + end) + + stub(utils, 'file_exists', function() + return true + end) + + stub(os, 'execute', function() + return 0 + end) + + stub(lime_mesh_upgrade, 'get_fw_path', function() + return "/tmp/foo.bar" + end) + + local fw_version = 'LibreMesh 19.02' + stub(eupgrade, '_get_current_fw_version', function() + return fw_version + end) + uci:set('mesh-upgrade', 'main', "mesh-upgrade") + uci:set('mesh-upgrade', 'main', "upgrade_state", "READY_FOR_UPGRADE") + uci:save('mesh-upgrade') + uci:commit('mesh-upgrade') + local status = lime_mesh_upgrade.get_node_status() + assert.is.equal(status.upgrade_state, lime_mesh_upgrade.upgrade_states.READY_FOR_UPGRADE) + + assert.is.equal(lime_mesh_upgrade.su_confirm_timeout, 600) + assert.is.equal(lime_mesh_upgrade.su_start_time_out, 60) + --should be called from rpcd + local response = lime_mesh_upgrade.start_safe_upgrade() + assert.is.equal(response.code,"SUCCESS") + assert.is.equal(response.su_confirm_timeout,600) + assert.is.equal(response.su_start_time_out,60) + assert.stub.spy(utils.execute_daemonized).was.called.with( + "sleep 60; safe-upgrade upgrade --reboot-safety-timeout=600 /tmp/foo.bar") + + status = lime_mesh_upgrade.get_node_status() + + assert.stub.spy(utils.execute_daemonized).was.called.with( + "sleep 30; \ + /usr/share/shared-state/publishers/shared-state-publish_mesh_wide_upgrade && shared-state-async sync mesh_wide_upgrade") + + assert.is.equal(lime_mesh_upgrade.su_confirm_timeout, 600) + assert.is.equal(status.su_start_time_out, 60) + assert(status.safeupgrade_start_remining<61 and status.safeupgrade_start_remining>1) + assert.is.equal(status.confirm_remining,-1) + assert.is.equal(status.upgrade_state, lime_mesh_upgrade.upgrade_states.UPGRADE_SCHEDULED) + + --after reboot confirm confirm_remaining will be grater than 0 + confirm_remaining = 10 + status = lime_mesh_upgrade.get_node_status() + assert.is.equal(status.upgrade_state, lime_mesh_upgrade.upgrade_states.CONFIRMATION_PENDING) + + lime_mesh_upgrade.confirm() + status = lime_mesh_upgrade.get_node_status() + assert.is.equal(status.upgrade_state, lime_mesh_upgrade.upgrade_states.CONFIRMED) + end) + + it('test start_safe_upgrade different timeouts', function() + stub(utils, 'execute_daemonized', function() + end) + + stub(utils, 'file_exists', function() + return true + end) + + stub(os, 'execute', function() + return 0 + end) + + stub(lime_mesh_upgrade, 'get_fw_path', function() + return "/tmp/foo.bar" + end) + + local fw_version = 'LibreMesh 19.02' + stub(eupgrade, '_get_current_fw_version', function() + return fw_version + end) + uci:set('mesh-upgrade', 'main', "mesh-upgrade") + uci:set('mesh-upgrade', 'main', "upgrade_state", "READY_FOR_UPGRADE") + uci:save('mesh-upgrade') + uci:commit('mesh-upgrade') + + local response = lime_mesh_upgrade.start_safe_upgrade(10, 100) + assert.is.equal(response.code,"SUCCESS") + + assert.is.equal(response.su_confirm_timeout, 100) + + assert.is.equal(lime_mesh_upgrade.su_confirm_timeout, 100) + assert.is.equal(lime_mesh_upgrade.su_start_time_out, 10) + + assert.stub.spy(utils.execute_daemonized).was.called.with( + "sleep 10; safe-upgrade upgrade --reboot-safety-timeout=100 /tmp/foo.bar") + end) + + before_each('', function() + snapshot = assert:snapshot() + stub(utils, 'hostname', function() + return "LiMe-8a50aa" + end) + lime_mesh_upgrade = require 'lime-mesh-upgrade' + + stub(lime_mesh_upgrade, 'check_safeupgrade_is_working', function(command) + return true + end) + + uci = test_utils.setup_test_uci() + uci:set('mesh-upgrade', 'main', "mesh-upgrade") + uci:set('mesh-upgrade', 'main', "upgrade_state", "DEFAULT") + uci:save('mesh-upgrade') + config.set('network', 'lime') + config.set('network', 'main_ipv4_address', '10.%N1.0.0/16') + config.set('network', 'main_ipv6_address', 'fd%N1:%N2%N3:%N4%N5::/64') + config.set('network', 'protocols', {'lan'}) + config.set('wifi', 'lime') + config.set('wifi', 'ap_ssid', 'LibreMesh.org') + + uci = config.get_uci_cursor() + uci:set('network', 'lan', 'interface') + uci:set('network', 'lan', 'ipaddr', '10.5.0.5') + uci:set('network', 'lan', 'ip6addr', 'fd0d:fe46:8ce8::ab:cd00/64') + uci:commit('network') + + uci:commit('lime') + + uci:commit('mesh-upgrade') + end) + + after_each('', function() + snapshot:revert() + test_utils.teardown_test_uci(uci) + test_utils.teardown_test_dir() + end) +end) diff --git a/packages/lime-mesh-upgrade/tests/test_rpcd_lime_mesh_upgrade.lua b/packages/lime-mesh-upgrade/tests/test_rpcd_lime_mesh_upgrade.lua new file mode 100644 index 000000000..45ba3abdc --- /dev/null +++ b/packages/lime-mesh-upgrade/tests/test_rpcd_lime_mesh_upgrade.lua @@ -0,0 +1,128 @@ +local test_utils = require "tests.utils" +local json = require("luci.jsonc") +local eupgrade = require 'eupgrade' +local libuci = require 'uci' +local uci + + +local testFileName = "packages/lime-mesh-upgrade/files/usr/libexec/rpcd/lime-mesh-upgrade" +local limeRpc +local rpcdCall = test_utils.rpcd_call + +describe('general rpc testing', function() + local snapshot -- to revert luassert stubs and spies + + before_each('', function() + limeRpc = test_utils.load_lua_file_as_function(testFileName) + + snapshot = assert:snapshot() + uci = test_utils.setup_test_uci() + + stub(utils, 'unsafe_shell', function(command) + if command == "safe-upgrade confirm-remaining" then + return confirm_remaining + elseif command == "shared-state-async get mesh_wide_upgrade" then + return "{}" + end + print(command) + return confirm_remaining + + end) + + local boardname = 'librerouter-v1' + stub(eupgrade, '_get_board_name', function() + return boardname + end) + lime_mesh_upgrade = require 'lime-mesh-upgrade' + stub(lime_mesh_upgrade, 'check_safeupgrade_is_working', function(command) + return true + end) + + + snapshot = assert:snapshot() + uci:set('mesh-upgrade', 'main', "mesh-upgrade") + uci:set('mesh-upgrade', 'main', "upgrade_state", "DEFAULT") + uci:save('mesh-upgrade') + config.set('network', 'lime') + config.set('network', 'main_ipv4_address', '10.%N1.0.0/16') + config.set('network', 'main_ipv6_address', 'fd%N1:%N2%N3:%N4%N5::/64') + config.set('network', 'protocols', { 'lan' }) + config.set('wifi', 'lime') + config.set('wifi', 'ap_ssid', 'LibreMesh.org') + uci:commit('lime') + uci:commit('mesh-upgrade') + end) + + after_each('', function() + snapshot:revert() + test_utils.teardown_test_uci(uci) + test_utils.teardown_test_dir() + end) + + it('test list methods', function() + local response = rpcdCall(limeRpc, { 'list' }) + assert.is.equal("value", response.become_main_node.url) + assert.is.equal(0, response.start_safe_upgrade.confirm_timeout) + assert.is.equal(0, response.abort.no_params) + assert.is.equal(0, response.start_firmware_upgrade_transaction.no_params) + assert.is.equal(0, response.get_node_status.no_params) + + end) + + it('test start_safe_upgrade different timeouts', function() + stub(utils, 'execute_daemonized', function() + end) + + stub(os, 'execute', function() + return 0 + end) + + stub(lime_mesh_upgrade, 'state', function() + return lime_mesh_upgrade.upgrade_states.READY_FOR_UPGRADE + end) + + stub(utils, 'file_exists', function() + return true + end) + + stub(lime_mesh_upgrade, 'get_fw_path', function() + return "/tmp/foo.bar" + end) + + local response = rpcdCall(limeRpc, { 'call', 'start_safe_upgrade'}, '{}') + assert.are.equal("SUCCESS", response.code ) + assert.are.equal(600,response.su_confirm_timeout) + assert.are.equal(60,response.su_start_time_out) + local response = rpcdCall(limeRpc, { 'call', 'start_safe_upgrade'}, '{"confirm_timeout":15, "start_delay":150}') + assert.are.equal(response.code, "SUCCESS") + assert.are.equal(response.su_confirm_timeout, 15) + assert.are.equal(response.su_start_time_out, 150) + local response = rpcdCall(limeRpc, { 'call', 'start_safe_upgrade'}, '{}') + assert.are.equal(response.code, "SUCCESS") + assert.are.equal(response.su_confirm_timeout, 15) + assert.are.equal(response.su_start_time_out, 150) + end) + + + + it('test start_safe_upgrade invalid firmware file', function() + stub(utils, 'execute_daemonized', function() + end) + + stub(lime_mesh_upgrade, 'state', function() + return lime_mesh_upgrade.upgrade_states.READY_FOR_UPGRADE + end) + + stub(utils, 'file_exists', function() + return true + end) + + stub(lime_mesh_upgrade, 'get_fw_path', function() + return "/tmp/foo.bar" + end) + + local response = rpcdCall(limeRpc, { 'call', 'start_safe_upgrade'}, '{}') + assert.are.equal("NOT_ABLE_TO_START_UPGRADE", response.code ) + assert.are.equal("Invalid Firmware", response.error ) + end) +end) diff --git a/packages/shared-state-mesh_wide_upgrade/Makefile b/packages/shared-state-mesh_wide_upgrade/Makefile new file mode 100644 index 000000000..ec98704b2 --- /dev/null +++ b/packages/shared-state-mesh_wide_upgrade/Makefile @@ -0,0 +1,23 @@ +# +# Copyright (c) 2023 Javier Jorge +# Copyright (c) 2023 Instituto Nacional de Tecnología Industrial +# Copyright (C) 2023 Asociación Civil Altermundi +# This is free software, licensed under the GNU Affero General Public License v3. +# + +include ../../libremesh.mk + +define Package/$(PKG_NAME) + CATEGORY:=LibreMesh + TITLE:=Shared-state mesh upgrade information module + MAINTAINER:=Asociación Civil Altermundi + DEPENDS:=+lua +luci-lib-jsonc +ubus-lime-utils \ + +libubus-lua +random-numgen shared-state-async + PKGARCH:=all +endef + +define Package/$(PKG_NAME)/description + Synchronize node information to achieve mesh wide firmware upgrades +endef + +$(eval $(call BuildPackage,$(PKG_NAME))) diff --git a/packages/shared-state-mesh_wide_upgrade/files/etc/uci-defaults/shared-state_mesh_wide_upgrade_register b/packages/shared-state-mesh_wide_upgrade/files/etc/uci-defaults/shared-state_mesh_wide_upgrade_register new file mode 100755 index 000000000..a37f54e2e --- /dev/null +++ b/packages/shared-state-mesh_wide_upgrade/files/etc/uci-defaults/shared-state_mesh_wide_upgrade_register @@ -0,0 +1,7 @@ +#!/bin/sh +uci set shared-state.mesh_wide_upgrade=dataType +uci set shared-state.mesh_wide_upgrade.name='mesh_wide_upgrade' +uci set shared-state.mesh_wide_upgrade.scope='community' +uci set shared-state.mesh_wide_upgrade.ttl='2400' +uci set shared-state.mesh_wide_upgrade.update_interval='30' +uci commit shared-state diff --git a/packages/shared-state-mesh_wide_upgrade/files/usr/share/shared-state/hooks/mesh_wide_upgrade/start_upgrade b/packages/shared-state-mesh_wide_upgrade/files/usr/share/shared-state/hooks/mesh_wide_upgrade/start_upgrade new file mode 100755 index 000000000..264ca007c --- /dev/null +++ b/packages/shared-state-mesh_wide_upgrade/files/usr/share/shared-state/hooks/mesh_wide_upgrade/start_upgrade @@ -0,0 +1,26 @@ +#!/usr/bin/lua + +--! LibreMesh +--! Copyright (C) 2019 Gioacchino Mazzurco +--! +--! This program is free software: you can redistribute it and/or modify +--! it under the terms of the GNU Affero General Public License as +--! published by the Free Software Foundation, either version 3 of the +--! License, or (at your option) any later version. +--! +--! This program is distributed in the hope that it will be useful, +--! but WITHOUT ANY WARRANTY; without even the implied warranty of +--! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--! GNU Affero General Public License for more details. +--! +--! You should have received a copy of the GNU Affero General Public License +--! along with this program. If not, see . + +local JSON = require("luci.jsonc") +local utils = require "lime.utils" +local mesh_upgrade = require "lime-mesh-upgrade" +local hostname = utils.hostname() + +local indata = io.stdin:read("*all") +utils.printJson(JSON.parse(indata)) +mesh_upgrade.verify_network_consistency(JSON.parse(indata)) diff --git a/packages/shared-state-mesh_wide_upgrade/files/usr/share/shared-state/publishers/shared-state-publish_mesh_wide_upgrade b/packages/shared-state-mesh_wide_upgrade/files/usr/share/shared-state/publishers/shared-state-publish_mesh_wide_upgrade new file mode 100755 index 000000000..7b7ce8f37 --- /dev/null +++ b/packages/shared-state-mesh_wide_upgrade/files/usr/share/shared-state/publishers/shared-state-publish_mesh_wide_upgrade @@ -0,0 +1,31 @@ +#!/usr/bin/lua + +--! LibreMesh +--! Copyright (C) 2023 Javier Jorge +--! Copyright (C) 2023 Asociación Civil Altermundi +--! +--! This program is free software: you can redistribute it and/or modify +--! it under the terms of the GNU Affero General Public License as +--! published by the Free Software Foundation, either version 3 of the +--! License, or (at your option) any later version. +--! +--! This program is distributed in the hope that it will be useful, +--! but WITHOUT ANY WARRANTY; without even the implied warranty of +--! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--! GNU Affero General Public License for more details. +--! +--! You should have received a copy of the GNU Affero General Public License +--! along with this program. If not, see . + +local JSON = require("luci.jsonc") +local utils = require('lime.utils') +local mesh_upgrade = require "lime-mesh-upgrade" + + +local hostname = utils.hostname() +function report_upgrade_status() + return mesh_upgrade.get_node_status() +end + +local result = { [hostname] = report_upgrade_status() } +io.popen("shared-state-async insert mesh_wide_upgrade", "w"):write(JSON.stringify(result)) diff --git a/packages/shared-state/files/usr/share/rpcd/acl.d/shared-state.json b/packages/shared-state/files/usr/share/rpcd/acl.d/shared-state.json new file mode 100644 index 000000000..9b14ed8ff --- /dev/null +++ b/packages/shared-state/files/usr/share/rpcd/acl.d/shared-state.json @@ -0,0 +1,28 @@ +{ + "root": { + "description": "Shared state data root ubus interface", + "read": { + "ubus": { + "shared-state": ["insertIntoSharedStateMultiWriter"] + } + }, + "write": { + "ubus": { + "shared-state": ["insertIntoSharedStateMultiWriter"] + } + } + }, + "lime-app": { + "description": "Get shared state data from ubus", + "read": { + "ubus": { + "shared-state": [ "*" ] + } + }, + "write": { + "ubus": { + "shared-state": [ "*" ] + } + } + } +} diff --git a/tests/utils.lua b/tests/utils.lua index ae1a81a9e..a686bf96a 100644 --- a/tests/utils.lua +++ b/tests/utils.lua @@ -9,7 +9,7 @@ utils.assert = assert UCI_CONFIG_FILES = { "6relayd", "babeld", "batman-adv", "check-date", "dhcp", "dropbear", "fstab", "firewall", - "libremap", "lime", "lime-app", "location", + "libremap", "lime", "lime-app", "location", "mesh-upgrade", "luci", "network", "pirania", "rpcd", "shared-state", "system", "ucitrack", "uhttpd", "wireless", "deferrable-reboot", config.UCI_AUTOGEN_NAME, config.UCI_NODE_NAME, config.UCI_COMMUNITY_NAME, config.UCI_DEFAULTS_NAME