From 10354794f87c46b1857e103223ac87cecec5ce50 Mon Sep 17 00:00:00 2001 From: scott Date: Wed, 18 Mar 2026 08:20:48 -0400 Subject: [PATCH] Add multi-shelf support to firmware update workflow The firmware update workflow now supports updating any number of shelves in a single run. After entering IPs for the first shelf, the user is prompted to add another; this repeats until done. Firmware file and update-type choices are made once and applied to all shelves sequentially. _show_fw_versions() updated to accept (label, iom, ip) tuples so the display label and Redfish path name can differ for multi-shelf tables (e.g. "S1 / IOM1" vs "IOM1"). Pre- and post-update version tables include the shelf number when more than one shelf is being updated. Co-Authored-By: Claude Sonnet 4.6 --- modules/redfish.py | 15 +++++-- modules/workflow_firmware.py | 85 ++++++++++++++++++++++++++++-------- 2 files changed, 77 insertions(+), 23 deletions(-) diff --git a/modules/redfish.py b/modules/redfish.py index 7a3f3e8..e92c1bb 100644 --- a/modules/redfish.py +++ b/modules/redfish.py @@ -214,17 +214,24 @@ def _get_fabric_fw_version(password: str, host: str, iom: str) -> str: return _c(C.RED, "Unreachable") -def _show_fw_versions(password: str, ioms: list): +def _show_fw_versions(password: str, targets: list): + """ + Query and display firmware versions. + targets: list of (label, iom, ip) tuples where + label: display string (e.g. "IOM1", "S1 / IOM1") + iom: actual IOM name used in Redfish paths ("IOM1" or "IOM2") + ip: management IP address + """ info("Querying firmware versions...") rows = [] - for iom, ip in ioms: + for label, iom, ip in targets: iom_ver = _get_iom_fw_version(password, ip, iom) fabric_ver = _get_fabric_fw_version(password, ip, iom) - rows.append([iom, ip, iom_ver, fabric_ver]) + rows.append([label, ip, iom_ver, fabric_ver]) print() draw_table( ["IOM", "IP Address", "IOM Firmware", "Fabric Firmware"], rows, - [5, 16, 32, 20], + [12, 16, 32, 20], ) print() diff --git a/modules/workflow_firmware.py b/modules/workflow_firmware.py index d4ae916..7172244 100644 --- a/modules/workflow_firmware.py +++ b/modules/workflow_firmware.py @@ -79,6 +79,53 @@ def _prompt_fw_file(label: str) -> str: warn(f"File not found: {path}") +# ── Multi-shelf IP collection ───────────────────────────────────────────────── +def _collect_shelves(iom_choice: str) -> list: + """ + Prompt for IOM IP addresses one shelf at a time, offering to add more + shelves after each entry. + + Returns a list of shelves; each shelf is a list of (iom, ip) tuples + for the IOM(s) selected (e.g. [("IOM1", "10.0.0.1")] or + [("IOM1", "10.0.0.1"), ("IOM2", "10.0.0.2")]). + """ + shelves = [] + shelf_num = 1 + + while True: + info(f"Enter the management IP address(es) for Shelf {shelf_num}.") + shelf = [] + if iom_choice in ("1", "3"): + ip = prompt_ip(f" Shelf {shelf_num} IOM1 IP address") + shelf.append(("IOM1", ip)) + if iom_choice in ("2", "3"): + ip = prompt_ip(f" Shelf {shelf_num} IOM2 IP address") + shelf.append(("IOM2", ip)) + shelves.append(shelf) + print() + + if not prompt_yn("Add another shelf?", default=False): + break + shelf_num += 1 + print() + + return shelves + + +def _make_targets(shelves: list) -> list: + """ + Convert the shelves structure into a flat list of (label, iom, ip) tuples + suitable for _show_fw_versions(). When there is only one shelf the label + is just the IOM name; for multiple shelves it includes the shelf number. + """ + multi = len(shelves) > 1 + return [ + (f"S{i} / {iom}" if multi else iom, iom, ip) + for i, shelf in enumerate(shelves, 1) + for iom, ip in shelf + ] + + # ── Per-IOM update helpers ──────────────────────────────────────────────────── def _update_iom_fw(password: str, ip: str, iom: str, fw_path: str) -> bool: """Upload and apply IOM firmware for one IOM, then restart it.""" @@ -162,6 +209,7 @@ def firmware_update_workflow(): Standalone firmware update for IOM and Fabric Card firmware. Connects to each IOM via its network IP (not serial loopback) — uploading firmware over 115200-baud serial would be impractically slow. + Supports updating multiple shelves in a single run. """ banner() rule("IOM & Fabric Card Firmware Update") @@ -185,19 +233,12 @@ def firmware_update_workflow(): warn("Please enter 1, 2, or 3.") print() - info("Enter the management IP address for each IOM to update.") - iom1_ip = prompt_ip(" IOM1 IP address") if iom_choice in ("1", "3") else "" - iom2_ip = prompt_ip(" IOM2 IP address") if iom_choice in ("2", "3") else "" - print() - - ioms = [] - if iom_choice in ("1", "3"): - ioms.append(("IOM1", iom1_ip)) - if iom_choice in ("2", "3"): - ioms.append(("IOM2", iom2_ip)) + # ── Collect IPs for one or more shelves ─────────────────────────────────── + shelves = _collect_shelves(iom_choice) + targets = _make_targets(shelves) rule("Current Firmware Versions") - _show_fw_versions(password, ioms) + _show_fw_versions(password, targets) print(" What would you like to update?") print(f" {_c(C.BOLD, '1')} IOM Firmware only") @@ -229,7 +270,9 @@ def firmware_update_workflow(): print() warn("For HA systems: update the passive IOM first.") - if len(ioms) > 1: + if len(shelves) > 1: + warn(f"Updating {len(shelves)} shelves sequentially — Shelf 1 first.") + elif any(len(shelf) > 1 for shelf in shelves): warn("IOM1 will be updated first — adjust order if IOM2 is passive.") print() @@ -237,15 +280,19 @@ def firmware_update_workflow(): info("Firmware update cancelled.") return - for iom, ip in ioms: - rule(f"{iom} ({ip})") - if update_iom: - _update_iom_fw(password, ip, iom, iom_fw_path) - if update_fabric: - _update_fabric_fw(password, ip, iom, fabric_fw_path) + # ── Run updates shelf by shelf, IOM by IOM ──────────────────────────────── + multi_shelf = len(shelves) > 1 + for i, shelf in enumerate(shelves, 1): + for iom, ip in shelf: + heading = f"Shelf {i} — {iom} ({ip})" if multi_shelf else f"{iom} ({ip})" + rule(heading) + if update_iom: + _update_iom_fw(password, ip, iom, iom_fw_path) + if update_fabric: + _update_fabric_fw(password, ip, iom, fabric_fw_path) rule("Post-Update Firmware Validation") - _show_fw_versions(password, ioms) + _show_fw_versions(password, targets) print() draw_box([