diff --git a/CLAUDE.md b/CLAUDE.md index 3a66fe0..e9231fe 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -32,6 +32,7 @@ modules/ workflow_serial.py ← Serial-based IOM network configuration workflow workflow_firmware.py ← IOM and Fabric Card firmware update workflow workflow_check.py ← Read-only system check (network settings + firmware versions) + workflow_restart.py ← Restart IOM via network or serial ``` `es24n_conf.py` adds `modules/` to `sys.path` at startup so all inter-module imports work without a package structure. diff --git a/es24n_conf.py b/es24n_conf.py index fc92192..bb0b09b 100755 --- a/es24n_conf.py +++ b/es24n_conf.py @@ -24,6 +24,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "mod from ui import _c, C, banner, draw_box, ok, warn, prompt from workflow_check import system_check_workflow from workflow_firmware import firmware_update_workflow +from workflow_restart import restart_iom_workflow from workflow_serial import configure_shelf @@ -34,11 +35,12 @@ def main(): f" {_c(C.BOLD, '1')} Configure Network Settings", f" {_c(C.BOLD, '2')} Update IOM / Fabric Card Firmware", f" {_c(C.BOLD, '3')} Query Current Configuration", - f" {_c(C.BOLD, '4')} Exit", + f" {_c(C.BOLD, '4')} Restart IOM", + f" {_c(C.BOLD, '5')} Exit", ]) print() - choice = prompt("Select [1/2/3/4]") + choice = prompt("Select [1-5]") if choice == "1": another = True while another: @@ -48,9 +50,11 @@ def main(): elif choice == "3": system_check_workflow() elif choice == "4": + restart_iom_workflow() + elif choice == "5": break else: - warn("Please enter 1, 2, 3, or 4.") + warn("Please enter 1, 2, 3, 4, or 5.") time.sleep(1) print() diff --git a/modules/workflow_restart.py b/modules/workflow_restart.py new file mode 100644 index 0000000..5dc66b8 --- /dev/null +++ b/modules/workflow_restart.py @@ -0,0 +1,150 @@ +""" +workflow_restart.py — Restart IOM workflow. +Sends a GracefulRestart to a selected IOM via either a direct network +Redfish connection or a serial console curl command. +""" + +import time + +from redfish import _redfish_restart_iom +from workflow_serial import ( + detect_serial_device, + open_serial_connection, + _login_serial_console, + _serial_redfish_request, +) +from ui import ( + _c, C, + banner, rule, + info, ok, warn, error, + prompt, prompt_ip, prompt_password, prompt_yn, +) + + +def _iom_prompt() -> str: + """Ask which IOM to target and return 'IOM1' or 'IOM2'.""" + print() + print(" Which IOM would you like to restart?") + print(f" {_c(C.BOLD, '1')} IOM1") + print(f" {_c(C.BOLD, '2')} IOM2") + print() + while True: + choice = prompt("Select [1/2]") + if choice in ("1", "2"): + break + warn("Please enter 1 or 2.") + return "IOM1" if choice == "1" else "IOM2" + + +# ── Network restart ──────────────────────────────────────────────────────────── + +def _restart_via_network(): + banner() + rule("Restart IOM -- Network Connection") + + print() + password = prompt_password() + print() + ip = prompt_ip(" IOM IP address (either IOM1 or IOM2)") + iom = _iom_prompt() + + print() + if not prompt_yn(f"Restart {iom} at {ip}?", default=False): + info("Restart cancelled.") + print() + prompt("Press Enter to return to main menu") + return + + info(f"Sending restart command to {iom}...") + ok_flag, data = _redfish_restart_iom(password, ip, iom) + if ok_flag: + ok(f"{iom} restart initiated.") + info("The IOM will be temporarily unreachable while rebooting.") + else: + error(f"Restart failed: {data}") + + print() + prompt("Press Enter to return to main menu") + + +# ── Serial restart ───────────────────────────────────────────────────────────── + +def _restart_via_serial(): + banner() + rule("Restart IOM -- Serial Connection") + + device = detect_serial_device() + if not device: + error("Could not detect a serial device. Returning to main menu.") + time.sleep(2) + return + + ser = open_serial_connection(device) + if not ser: + error("Could not open serial port. Returning to main menu.") + time.sleep(2) + return + + print() + logged_in, password = _login_serial_console(ser) + if not logged_in: + error("Could not log in to IOM console. Returning to main menu.") + if ser.is_open: + ser.close() + time.sleep(2) + return + + iom = _iom_prompt() + + print() + if not prompt_yn(f"Restart {iom} via serial?", default=False): + info("Restart cancelled.") + if ser.is_open: + ser.close() + print() + prompt("Press Enter to return to main menu") + return + + info(f"Sending restart command to {iom}...") + ok_flag, data = _serial_redfish_request( + ser, password, "POST", + f"/redfish/v1/Managers/{iom}/Actions/Manager.Reset", + {"ResetType": "GracefulRestart"}, + ) + if ok_flag: + ok(f"{iom} restart initiated.") + info("The IOM will be temporarily unreachable while rebooting.") + else: + error(f"Restart failed: {data}") + + if ser.is_open: + ser.close() + ok(f"Serial port {device} closed.") + + print() + prompt("Press Enter to return to main menu") + + +# ── Top-level entry point ────────────────────────────────────────────────────── + +def restart_iom_workflow(): + banner() + rule("Restart IOM") + + print(" How do you want to connect to the IOM?") + print() + print(f" {_c(C.BOLD, '1')} Network connection") + print(f" {_c(C.BOLD, '2')} Serial connection") + print(f" {_c(C.BOLD, '3')} Cancel") + print() + + while True: + choice = prompt("Select [1/2/3]") + if choice in ("1", "2", "3"): + break + warn("Please enter 1, 2, or 3.") + + if choice == "1": + _restart_via_network() + elif choice == "2": + _restart_via_serial()