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 <noreply@anthropic.com>
This commit is contained in:
2026-03-18 08:20:48 -04:00
parent 47157ba502
commit 10354794f8
2 changed files with 77 additions and 23 deletions

View File

@@ -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()

View File

@@ -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([