diff --git a/src/confd/src/infix-system-software.c b/src/confd/src/infix-system-software.c index 355415fe..1b24a186 100644 --- a/src/confd/src/infix-system-software.c +++ b/src/confd/src/infix-system-software.c @@ -5,7 +5,6 @@ #include "core.h" #include "rauc-installer.h" - static RaucInstaller *infix_system_sw_new_rauc(void) { RaucInstaller *rauc; @@ -51,12 +50,49 @@ static int infix_system_sw_install(sr_session_ctx_t *session, uint32_t sub_id, return srerr; } +/* + boot order can only be: primary, secondary and net, limited by model + */ +static int infix_system_sw_set_boot_order(sr_session_ctx_t *session, uint32_t sub_id, + const char *path, const sr_val_t *input, + const size_t input_cnt, sr_event_t event, + unsigned request_id, sr_val_t **output, + size_t *output_cnt, void *priv) { + char boot_order[23] = ""; + for (size_t i = 0; i < input_cnt; i++) { + const sr_val_t *val = &input[i]; + + if (i != 0) + strlcat(boot_order, " ", sizeof(boot_order)); + strlcat(boot_order, val->data.string_val, sizeof(boot_order)); + } + + if (fexist("/sys/firmware/devicetree/base/chosen/u-boot,version")) { + if (systemf("fw_setenv BOOT_ORDER %s", boot_order)) { + ERROR("Set-boot-order: Failed to set boot order in U-Boot"); + return SR_ERR_INTERNAL; + } + } else if (fexist("/mnt/aux/grub/grubenv")) { + if (systemf("grub-editenv /mnt/aux/grub/grubenv set ORDER=\"%s\"", boot_order)) { + ERROR("Set-boot-order: Failed to set boot order in Grub"); + return SR_ERR_INTERNAL; + } + } else { + ERROR("No supported boot loader found"); + return SR_ERR_UNSUPPORTED; + } + + return SR_ERR_OK; +} + int infix_system_sw_init(struct confd *confd) { int rc = 0; REGISTER_RPC(confd->session, "/infix-system:install-bundle", infix_system_sw_install, NULL, &confd->sub); + REGISTER_RPC(confd->session, "/infix-system:set-boot-order", + infix_system_sw_set_boot_order, NULL, &confd->sub); fail: return rc; diff --git a/src/confd/yang/infix-system-software.yang b/src/confd/yang/infix-system-software.yang index 2cc449ab..907b4224 100644 --- a/src/confd/yang/infix-system-software.yang +++ b/src/confd/yang/infix-system-software.yang @@ -20,6 +20,10 @@ submodule infix-system-software { contact "kernelkit@googlegroups.com"; description "Software status and upgrade."; + revision 2024-12-16 { + description "Add boot-order operational data"; + reference "Internal"; + } revision 2023-06-27 { description "Initial revision."; reference "internal"; @@ -39,6 +43,18 @@ submodule infix-system-software { } } + grouping boot-order { + leaf-list boot-order { + type enumeration { + enum "primary"; + enum "secondary"; + enum "net"; + } + ordered-by user; + min-elements 1; // At least one value is required + max-elements 3; // Ensure reasonable maximum size if needed + } + } grouping installer-state { leaf operation { type string; @@ -64,7 +80,6 @@ submodule infix-system-software { "The last error encountered by the installer service."; } } - augment "/sys:system-state" { container software { description @@ -95,6 +110,7 @@ submodule infix-system-software { description "Slot from which the system was booted."; } + uses boot-order; container installer { description @@ -184,7 +200,7 @@ submodule infix-system-software { } rpc install-bundle { -nacm:default-deny-all; + nacm:default-deny-all; description "Upgrade the system's software by installing the specified bundle."; input { @@ -198,4 +214,17 @@ nacm:default-deny-all; } } } + rpc set-boot-order { + nacm:default-deny-all; + description + "Set order of boot partitions"; + input { + uses boot-order; + must "count(boot-order[.='primary']) <= 1 and + count(boot-order[.='secondary']) <= 1 and + count(boot-order[.='net']) <= 1" { + error-message "Not possible to have duplicate targets in boot order."; + } + } + } } diff --git a/src/confd/yang/infix-system-software@2023-06-27.yang b/src/confd/yang/infix-system-software@2024-12-16.yang similarity index 100% rename from src/confd/yang/infix-system-software@2023-06-27.yang rename to src/confd/yang/infix-system-software@2024-12-16.yang diff --git a/src/klish-plugin-infix/xml/infix.xml b/src/klish-plugin-infix/xml/infix.xml index 23f7cf44..fac72b2c 100644 --- a/src/klish-plugin-infix/xml/infix.xml +++ b/src/klish-plugin-infix/xml/infix.xml @@ -303,7 +303,7 @@ sysrepocfg -d operational -X -f json -x /ietf-system:system-state/infix-system:software | \ jq -r '.[][].slot |= sort_by(.name)' > "$tmp" if [ -n "$KLISH_PARAM_name" ]; then - cat "$tmp" | /usr/libexec/statd/cli-pretty "infix-system" -n "$KLISH_PARAM_name" + cat "$tmp" | /usr/libexec/statd/cli-pretty "show-software" -n "$KLISH_PARAM_name" else cat "$tmp" | /usr/libexec/statd/cli-pretty "show-software" fi diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py index 29e2486b..f485fada 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -691,12 +691,21 @@ def show_software(json, name): print("Error, cannot find infix-system:software") sys.exit(1) - slots = get_json_data({}, json, 'ietf-system:system-state', 'infix-system:software', 'slot') + software = get_json_data({}, json, 'ietf-system:system-state', 'infix-system:software') + slots = software.get("slot") + boot_order = software.get("boot-order", ["Unknown"]) if name: slot = find_slot(slots, name) if slot: slot.detail() else: + print(Decore.invert("BOOT ORDER")) + order="" + for boot in boot_order: + order+=f"{boot.strip()} " + print(order) + print("") + hdr = (f"{'NAME':<{PadSoftware.name}}" f"{'STATE':<{PadSoftware.state}}" f"{'VERSION':<{PadSoftware.version}}" @@ -773,10 +782,12 @@ def main(): parser_show_software = subparsers.add_parser('show-software', help='Show software versions') parser_show_software.add_argument('-n', '--name', help='Slotname') - parser_show_routing_table = subparsers.add_parser('show-hardware', help='Show USB ports') + parser_show_hardware = subparsers.add_parser('show-hardware', help='Show USB ports') parser_show_ntp_sources = subparsers.add_parser('show-ntp', help='Show NTP sources') + parser_show_boot_order = subparsers.add_parser('show-boot-order', help='Show NTP sources') + args = parser.parse_args() UNIT_TEST = args.test @@ -793,7 +804,7 @@ def main(): elif args.command == "show-ntp": show_ntp(json_data) else: - print(f"Error, unknown command {args.command}") + print(f"Error, unknown command '{args.command}'") sys.exit(1) diff --git a/src/statd/python/yanger/yanger.py b/src/statd/python/yanger/yanger.py index 5f39f222..401d6bdc 100755 --- a/src/statd/python/yanger/yanger.py +++ b/src/statd/python/yanger/yanger.py @@ -17,6 +17,23 @@ def datetime_now(): return datetime(2023, 1, 1, 12, 0, 0, tzinfo=timezone.utc) return datetime.now(timezone.utc) +def uboot_get_boot_order(): + data = run_cmd("fw_printenv BOOT_ORDER".split(), "boot-order.txt") + for line in data: + if "BOOT_ORDER" in line: + return line.strip().split("=")[1].split() + + raise Exception + +def grub_get_boot_order(): + data = run_cmd("grub-editenv /mnt/aux/grub/grubenv list".split(), None) # No need for testfile, will be returned from uboot + + for line in data: + if "ORDER" in line: + return line.split("=")[1].strip().split() + + raise Exception + def json_get_yang_type(iface_in): if iface_in['link_type'] == "loopback": return "infix-if-type:loopback" @@ -1091,28 +1108,46 @@ def add_system_software_slots(out, data): new["class"] = slot[key].get("class") new["state"] = slot[key].get("state") new["bundle"] = {} + slot_status=value.get("slot_status", {}) + if slot_status.get("bundle", {}).get("compatible"): + new["bundle"]["compatible"] = slot_status.get("bundle", {}).get("compatible") + if slot_status.get("bundle", {}).get("version"): + new["bundle"]["version"] = slot_status.get("bundle", {}).get("version") + if slot_status.get("checksum", {}).get("size"): + new["size"] = str(slot_status.get("checksum", {}).get("size")) + if slot_status.get("checksum", {}).get("sha256"): + new["sha256"] = slot_status.get("checksum", {}).get("sha256") - if value.get("slot_status",{}).get("bundle", {}).get("compatible"): - new["bundle"]["compatible"] = value.get("slot_status",{}).get("bundle", {}).get("compatible") - if value.get("slot_status", {}).get("bundle", {}).get("version"): - new["bundle"]["version"] = value.get("slot_status", {}).get("bundle", {}).get("version") - if value.get("checksum", {}).get("size"): - new["size"] = value.get("checksum", {}).get("size") - if value.get("checksum", {}).get("sha256"): - new["sha256"] = value.get("checksum", {}).get("sha256") new["installed"] = {} - if value.get("installed", {}).get("timestamp"): - new["installed"]["datetime"] = value.get("installed", {}).get("timestamp") - if value.get("installed", {}).get("count"): - new["installed"]["count"] = value.get("installed", {}).get("count") + if slot_status.get("installed", {}).get("timestamp"): + new["installed"]["datetime"] = slot_status.get("installed", {}).get("timestamp") + + if slot_status.get("installed", {}).get("count"): + new["installed"]["count"] = slot_status.get("installed", {}).get("count") + new["activated"] = {} - if value.get("activated", {}).get("timestamp"): - new["activated"]["datetime"] = value.get("activated", {}).get("timestamp") - if value.get("activated", {}).get("count"): - new["activated"]["count"] = value.get("activated", {}).get("count") + if slot_status.get("activated", {}).get("timestamp"): + new["activated"]["datetime"] = slot_status.get("activated", {}).get("timestamp") + + if slot_status.get("activated", {}).get("count"): + new["activated"]["count"] = slot_status.get("activated", {}).get("count") slots.append(new) out["slot"] = slots +def get_system_software_boot_order(): + order = None + try: + order = uboot_get_boot_order() + except: + pass + try: + if order is None: + order = grub_get_boot_order() + except: + pass + + return order + def add_system_software(out): software = {} try: @@ -1120,6 +1155,9 @@ def add_system_software(out): software["compatible"] = data["compatible"] software["variant"] = data["variant"] software["booted"] = data["booted"] + boot_order = get_system_software_boot_order() + if not boot_order is None: + software["boot-order"] = boot_order add_system_software_slots(software, data) except subprocess.CalledProcessError: pass # Maybe an upgrade i progress, then rauc does not respond diff --git a/test/case/cli/cli-output/show-software.txt b/test/case/cli/cli-output/show-software.txt new file mode 100644 index 00000000..66deb741 --- /dev/null +++ b/test/case/cli/cli-output/show-software.txt @@ -0,0 +1,6 @@ +BOOT ORDER +net primary secondary + +NAME STATE VERSION DATE  +secondary inactive pr873.2a39a38 2024-12-16T14:40:54Z +primary inactive pr871.a4ef38f 2024-12-13T20:00:42Z diff --git a/test/case/cli/run.sh b/test/case/cli/run.sh index 5d206c4f..8c56b3c0 100755 --- a/test/case/cli/run.sh +++ b/test/case/cli/run.sh @@ -60,7 +60,9 @@ if [ $# -eq 2 ] && [ $1 = "update" ]; then elif [ $2 = "show-bridge-mdb" ]; then "$SR_EMULATOR_TOOL" | "$CLI_PRETTY_TOOL" "show-bridge-mdb" > "$CLI_OUTPUT_PATH/show-bridge-mdb.txt" elif [ $2 = "show-ntp" ]; then - "$SR_EMULATOR_TOOL" | "$CLI_PRETTY_TOOL" "show-ntp" > "$CLI_OUTPUT_PATH/show-ntp.txt" + "$SR_EMULATOR_TOOL" | "$CLI_PRETTY_TOOL" "show-ntp" > "$CLI_OUTPUT_PATH/show-ntp.txt" + elif [ $2 = "show-software" ]; then + "$SR_EMULATOR_TOOL" | "$CLI_PRETTY_TOOL" "show-software" > "$CLI_OUTPUT_PATH/show-software.txt" else echo "Unsupported cli-pretty command $2" exit 1 @@ -69,7 +71,7 @@ if [ $# -eq 2 ] && [ $1 = "update" ]; then exit 0 fi -echo "1..11" +echo "1..12" echo "# Running:" # Show interfaces @@ -101,6 +103,16 @@ if ! diff -u "$CLI_OUTPUT_PATH/show-ntp.txt" "$CLI_OUTPUT_FILE"; then fi ok "\"show ntp\" output looks intact" +# Show software +echo "# $SR_EMULATOR_TOOL | $CLI_PRETTY_TOOL show-software" +"$SR_EMULATOR_TOOL" | "$CLI_PRETTY_TOOL" "show-software" > "$CLI_OUTPUT_FILE" + +if ! diff -u "$CLI_OUTPUT_PATH/show-software.txt" "$CLI_OUTPUT_FILE"; then + print_update_txt + fail "\"show software\" output has changed" +fi +ok "\"show software\" output looks intact" + # Show ipv4 routes echo "# $SR_EMULATOR_TOOL | $CLI_PRETTY_TOOL -t show-routing-table -i ipv4" "$SR_EMULATOR_TOOL" | "$CLI_PRETTY_TOOL" "-t" "show-routing-table" -i "ipv4" > "$CLI_OUTPUT_FILE" diff --git a/test/case/cli/system-output/boot-order.txt b/test/case/cli/system-output/boot-order.txt new file mode 100644 index 00000000..72561adc --- /dev/null +++ b/test/case/cli/system-output/boot-order.txt @@ -0,0 +1 @@ +BOOT_ORDER=net primary secondary diff --git a/test/case/cli/system-output/rauc-status.json b/test/case/cli/system-output/rauc-status.json index 2a1c36ee..14418a7e 100644 --- a/test/case/cli/system-output/rauc-status.json +++ b/test/case/cli/system-output/rauc-status.json @@ -1 +1 @@ -{"compatible":"infix-x86_64","variant":"","booted":"primary","boot_primary":"rootfs.0","slots":[{"rootfs.1":{"class":"rootfs","device":"/dev/disk/by-partlabel/secondary","type":"raw","bootname":"secondary","state":"inactive","parent":null,"mountpoint":null,"boot_status":"good"}},{"rootfs.0":{"class":"rootfs","device":"/dev/disk/by-partlabel/primary","type":"raw","bootname":"primary","state":"booted","parent":null,"mountpoint":null,"boot_status":"good"}},{"net.0":{"class":"net","device":"/dev/ram0","type":"raw","bootname":"net","state":"inactive","parent":null,"mountpoint":null,"boot_status":"good"}}]} +{"compatible":"infix-aarch64","variant":"","booted":"net","boot_primary":"net.0","slots":[{"rootfs.1":{"class":"rootfs","device":"/dev/disk/by-partlabel/secondary","type":"raw","bootname":"secondary","state":"inactive","parent":null,"mountpoint":null,"boot_status":"bad","slot_status":{"bundle":{"compatible":"infix-aarch64","version":"pr873.2a39a38","hash":"99a9543fe85a2b32a4a28409f7103099fd8cfce3f3bbccb5175ace74e7bf9ea6"},"checksum":{"sha256":"d2595e8af8468c19a5da6c3fffbf27ff8e00d7ac2559a87f60591829151fd7e3","size":59707392},"installed":{"timestamp":"2024-12-16T14:40:54Z","count":12},"activated":{"timestamp":"2024-12-16T14:40:54Z","count":12},"status":"ok"}}},{"rootfs.0":{"class":"rootfs","device":"/dev/disk/by-partlabel/primary","type":"raw","bootname":"primary","state":"inactive","parent":null,"mountpoint":null,"boot_status":"bad","slot_status":{"bundle":{"compatible":"infix-aarch64","version":"pr871.a4ef38f","hash":"d36189afff31ac1193ee7eda161f7e9b2007df08ccdc0014cfa231f9a836780a"},"checksum":{"sha256":"d78a9ef52f08238972b6b9151c381327dd0c40e410f9ed4634e379056f9bacd8","size":59699200},"installed":{"timestamp":"2024-12-13T20:00:42Z","count":3},"activated":{"timestamp":"2024-12-13T20:00:42Z","count":4},"status":"ok"}}},{"net.0":{"class":"net","device":"/dev/ram0","type":"raw","bootname":"net","state":"booted","parent":null,"mountpoint":null,"boot_status":"good","slot_status":{"bundle":{"compatible":null}}}}]}