""" workflow_network.py — Network-based ES24N IOM network configuration workflow. Connects to the IOM via its existing management IP address using Admin credentials and reconfigures network settings via the Redfish API. Used when the IOM is already reachable on the network (e.g. currently using DHCP) and needs its IP configuration changed. """ import time from typing import Optional from models import IOMConfig from redfish import _redfish_request from ui import ( _c, C, banner, rule, draw_table, draw_box, info, ok, warn, error, prompt, prompt_ip, prompt_yn, prompt_password, ) def _sanitize(value: str) -> str: return "".join(c for c in value if 32 <= ord(c) < 128) def _fetch_and_display(password: str, ip: str, iom: str) -> Optional[IOMConfig]: """ Query and display the current network settings for the given IOM. Returns an IOMConfig populated with current values, or None on failure. """ info(f"Querying {iom} at {ip}...") ok_flag, data = _redfish_request( password, "GET", f"/redfish/v1/Managers/{iom}/EthernetInterfaces/1", host=ip, ) if not (ok_flag and isinstance(data, dict)): error(f"{iom} query failed: {data}") draw_table( ["IOM", "Mode", "IP Address", "Gateway", "Subnet Mask"], [[iom, _c(C.RED, "No response"), "--", "--", "--"]], [5, 8, 15, 15, 15], ) print() return None dhcp_enabled = ( data.get("DHCPv4", {}).get("DHCPEnabled", False) or data.get("DHCPv6", {}).get("DHCPEnabled", False) ) addrs = data.get("IPv4StaticAddresses") or data.get("IPv4Addresses", []) if addrs: addr = addrs[0] ip_addr = _sanitize(addr.get("Address", "--")) gateway = _sanitize(addr.get("Gateway", "--")) netmask = _sanitize(addr.get("SubnetMask", "--")) else: ip_addr = gateway = netmask = "--" mode_str = _c(C.CYN, "DHCP") if dhcp_enabled else _c(C.GRN, "Static") draw_table( ["IOM", "Mode", "IP Address", "Gateway", "Subnet Mask"], [[iom, mode_str, ip_addr, gateway, netmask]], [5, 8, 15, 15, 15], ) print() return IOMConfig( iom = iom, dhcp = dhcp_enabled, ip = ip_addr if ip_addr != "--" else "", gateway = gateway if gateway != "--" else "", netmask = netmask if netmask != "--" else "", ) def _collect_new_config(iom: str) -> Optional[IOMConfig]: """ Prompt the user for the desired new network configuration. Returns a populated IOMConfig, or None if the user opts out. """ print(f" How should {iom} be configured?") print(f" {_c(C.BOLD, '1')} Static IP address") print(f" {_c(C.BOLD, '2')} DHCP") print(f" {_c(C.BOLD, '3')} Leave settings as-is") 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 == "3": info("No changes requested.") return None if choice == "2": ok(f"{iom} will be set to DHCP.") return IOMConfig(iom, dhcp=True) # Static IP print() info(f"Static network details for {_c(C.BOLD, iom)}:") new_ip = prompt_ip(f" {iom} IP address ") gateway = prompt_ip(f" {iom} Gateway ") netmask = prompt_ip(f" {iom} Subnet Mask") return IOMConfig(iom, dhcp=False, ip=new_ip, gateway=gateway, netmask=netmask) def _apply_config(password: str, host: str, iom_cfg: IOMConfig) -> tuple: """ Apply network configuration via direct Redfish PATCH. DHCP: single PATCH enabling DHCPv4. Static: two sequential PATCHes to work around the ES24N firmware bug that rejects a single request combining IPv4StaticAddresses and DHCPv4 disable. Pass 1 — set IPv4StaticAddresses (DHCP still enabled) Pass 2 — disable DHCPv4 """ path = f"/redfish/v1/Managers/{iom_cfg.iom}/EthernetInterfaces/1" if iom_cfg.dhcp: ok_flag, data = _redfish_request( password, "PATCH", path, {"DHCPv4": {"DHCPEnabled": True}}, host=host, ) return (True, "Configured: DHCP") if ok_flag else (False, str(data)[:80]) # Static — Pass 1: set address while DHCP still enabled info(f" {iom_cfg.iom} pass 1/2 — setting static address {iom_cfg.ip}...") ok_flag, data = _redfish_request( password, "PATCH", path, { "IPv4StaticAddresses": [{ "Address": iom_cfg.ip, "Gateway": iom_cfg.gateway, "SubnetMask": iom_cfg.netmask, }] }, host=host, ) if not ok_flag: return False, f"Pass 1 failed: {str(data)[:70]}" time.sleep(1) # Static — Pass 2: disable DHCP info(f" {iom_cfg.iom} pass 2/2 — disabling DHCP...") ok_flag, data = _redfish_request( password, "PATCH", path, {"DHCPv4": {"DHCPEnabled": False}}, host=host, ) if not ok_flag: return False, f"Pass 2 failed: {str(data)[:70]}" return True, f"Configured: Static {iom_cfg.ip}" def configure_iom_network() -> bool: """ Run one complete network-based IOM configuration cycle. Returns True if the user wants to configure another IOM. """ banner() rule("Configure Network Settings — Network Connection") print() password = prompt_password() print() ip = prompt_ip(" Current IOM IP address (IOM1 or IOM2)") print() print(" Which IOM would you like to configure?") print(f" {_c(C.BOLD, '1')} IOM1") print(f" {_c(C.BOLD, '2')} IOM2") print() while True: iom_choice = prompt("Select [1/2]") if iom_choice in ("1", "2"): break warn("Please enter 1 or 2.") iom = "IOM1" if iom_choice == "1" else "IOM2" rule(f"Current {iom} Network Settings") current = _fetch_and_display(password, ip, iom) if current is None: warn("Check that the IP is correct and this system can reach the IOM.") print() return prompt_yn("Try another IOM?", default=False) rule("New Configuration") new_cfg = _collect_new_config(iom) if new_cfg is None: print() return prompt_yn("Configure another IOM?", default=False) print() rule("Ready to Apply") info("Redfish PATCH requests will be sent directly to the IOM.") if not new_cfg.dhcp: warn(f"The IOM will move to {new_cfg.ip} — this connection will stop working after the change.") print() if not prompt_yn("Apply configuration now?", default=True): warn("Configuration skipped — no changes were made.") print() return prompt_yn("Configure another IOM?", default=False) rule("Applying Configuration") success, detail = _apply_config(password, ip, new_cfg) status = _c(C.GRN, "OK") if success else _c(C.RED, "FAIL") draw_table(["IOM", "Result", "Detail"], [[iom, status, detail]], [6, 8, 50]) print() if success: draw_box([ f"{_c(C.YEL, 'IMPORTANT — Per the ES24N Service Guide:')}", "", "Verify the expander appears in TrueNAS with matching drives:", "TrueNAS > System Settings > Enclosure >", "NVMe-oF Expansion Shelves", ], colour=C.YEL) print() return prompt_yn("Configure another IOM?", default=False)