Fix RRH power scripts to handle serial-connected hubs and relay hosts.
This updates keep/all-off control to toggle local and remote Acroname ports correctly, explicitly powers the keep target back on, and aligns mapping helper toggles with BrainStem connect-by-serial behavior. Made-with: Cursor
This commit is contained in:
parent
2034da7185
commit
667880f82a
|
|
@ -20,7 +20,6 @@ from pathlib import Path
|
|||
|
||||
from fiwicontrol.fabric.fabric import FabricDefinition, FabricRRHBinding
|
||||
from fiwicontrol.lab.discovery import discover_acroname_modules
|
||||
from fiwicontrol.power.acroname import AcronamePower
|
||||
|
||||
_REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
|
@ -68,7 +67,7 @@ async def _all_off(rrhs: list[FabricRRHBinding]) -> None:
|
|||
mod = by_serial.get(serial)
|
||||
if mod is None:
|
||||
raise RuntimeError("Acroname module serial {} not found on local USB".format(serial))
|
||||
await AcronamePower(mod).port_off(port)
|
||||
await asyncio.to_thread(_set_port_enabled_by_serial_sync, serial=serial, port=port, enabled=False)
|
||||
done.add(k)
|
||||
print("OFF: module={} port={}".format(serial, port))
|
||||
|
||||
|
|
@ -80,7 +79,6 @@ async def _step_map(rrhs: list[FabricRRHBinding], dwell: float) -> None:
|
|||
mod = by_serial.get(serial)
|
||||
if mod is None:
|
||||
raise RuntimeError("Acroname module serial {} not found on local USB".format(serial))
|
||||
ap = AcronamePower(mod)
|
||||
|
||||
print(
|
||||
"\n[{}/{}] radio_id={} module={} port={} patch_panel_port={}".format(
|
||||
|
|
@ -93,15 +91,45 @@ async def _step_map(rrhs: list[FabricRRHBinding], dwell: float) -> None:
|
|||
)
|
||||
)
|
||||
input("Press Enter to power ON this port...")
|
||||
await ap.port_on(port)
|
||||
await asyncio.to_thread(_set_port_enabled_by_serial_sync, serial=serial, port=port, enabled=True)
|
||||
print("ON: module={} port={}".format(serial, port))
|
||||
input("Observe fiber/link, then press Enter to power OFF this port...")
|
||||
await ap.port_off(port)
|
||||
await asyncio.to_thread(_set_port_enabled_by_serial_sync, serial=serial, port=port, enabled=False)
|
||||
print("OFF: module={} port={}".format(serial, port))
|
||||
if dwell > 0:
|
||||
await asyncio.sleep(dwell)
|
||||
|
||||
|
||||
def _set_port_enabled_by_serial_sync(*, serial: int, port: int, enabled: bool) -> None:
|
||||
import brainstem.discover as discover
|
||||
from brainstem import stem
|
||||
from brainstem.result import Result
|
||||
|
||||
specs = discover.findAllModules(discover.Spec.USB, buffer_length=128)
|
||||
spec = next((s for s in specs if int(s.serial_number) == int(serial)), None)
|
||||
if spec is None:
|
||||
raise RuntimeError("module serial {} not found on local USB".format(serial))
|
||||
model_map = {17: stem.USBHub2x4, 19: stem.USBHub3p, 24: stem.USBHub3c}
|
||||
cls = model_map.get(int(spec.model))
|
||||
if cls is None:
|
||||
raise RuntimeError("unsupported Acroname model {} for serial {}".format(spec.model, serial))
|
||||
hub = cls()
|
||||
err = int(hub.connectFromSpec(spec))
|
||||
if err != Result.NO_ERROR:
|
||||
raise RuntimeError("connectFromSpec failed for serial {}: {}".format(serial, err))
|
||||
try:
|
||||
err = int(hub.hub.port[int(port)].setEnabled(bool(enabled)))
|
||||
if err != Result.NO_ERROR:
|
||||
raise RuntimeError(
|
||||
"setEnabled failed for serial {} port {} enabled {}: {}".format(serial, port, enabled, err)
|
||||
)
|
||||
finally:
|
||||
try:
|
||||
hub.disconnect()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
async def _run(fabric_json: str, dwell: float, skip_all_off: bool) -> None:
|
||||
path = _resolve_json_path(fabric_json)
|
||||
fd = FabricDefinition.load(path)
|
||||
|
|
|
|||
|
|
@ -15,9 +15,14 @@ import argparse
|
|||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
from fiwicontrol.commands.node_control import ssh_node
|
||||
from fiwicontrol.fabric.fabric import FabricDefinition
|
||||
from fiwicontrol.lab.discovery import discover_acroname_modules
|
||||
from fiwicontrol.power.acroname import AcronamePower
|
||||
from fiwicontrol.lab.discovery import (
|
||||
discover_acroname_modules,
|
||||
discover_devices_remote_async,
|
||||
parse_remote_discovery_payload,
|
||||
)
|
||||
from fiwicontrol.lab.inventory_config import default_lab_ini_path, load_inventory_ini
|
||||
|
||||
_REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
|
@ -25,6 +30,7 @@ _REPO_ROOT = Path(__file__).resolve().parents[2]
|
|||
async def _power_only_one(
|
||||
*,
|
||||
fabric_json: str,
|
||||
lab_ini: str | None,
|
||||
keep_radio_id: str | None,
|
||||
all_off: bool,
|
||||
dry_run: bool,
|
||||
|
|
@ -53,7 +59,29 @@ async def _power_only_one(
|
|||
raise SystemExit("radio_id {!r} not found in {}".format(keep_radio_id, json_path))
|
||||
|
||||
modules = discover_acroname_modules()
|
||||
by_serial = {int(m.serial_number): m for m in modules}
|
||||
local_serials = {int(m.serial_number) for m in modules}
|
||||
remote_hosts: dict[int, tuple[str, str, str]] = {}
|
||||
ini = (
|
||||
Path(lab_ini).expanduser().resolve()
|
||||
if (lab_ini and str(lab_ini).strip())
|
||||
else default_lab_ini_path().expanduser().resolve()
|
||||
)
|
||||
if ini.is_file():
|
||||
doc = load_inventory_ini(ini)
|
||||
for host in doc.hosts:
|
||||
if host.mode != "relay" or not host.ipaddr:
|
||||
continue
|
||||
node = ssh_node(
|
||||
name=host.name,
|
||||
ipaddr=host.ipaddr,
|
||||
ssh_controlmaster=True,
|
||||
sshtype=host.sshtype,
|
||||
silent_mode=True,
|
||||
)
|
||||
payload = await discover_devices_remote_async(node)
|
||||
mods, _ = parse_remote_discovery_payload(payload)
|
||||
for m in mods:
|
||||
remote_hosts[int(m.serial_number)] = (host.name, host.ipaddr, host.sshtype)
|
||||
|
||||
keep_key: tuple[int | None, int] | None = None
|
||||
if all_off:
|
||||
|
|
@ -76,17 +104,150 @@ async def _power_only_one(
|
|||
"{}: missing acroname_module_serial in {}".format(h.radio_id, json_path)
|
||||
)
|
||||
serial = int(h.acroname_module_serial)
|
||||
mod = by_serial.get(serial)
|
||||
if mod is None:
|
||||
raise RuntimeError(
|
||||
"{}: module serial {} not found on local USB".format(h.radio_id, serial)
|
||||
)
|
||||
if dry_run:
|
||||
print("DRY-RUN OFF: radio_id={} module={} port={}".format(h.radio_id, serial, h.acroname_port))
|
||||
if serial in local_serials:
|
||||
print("DRY-RUN OFF: radio_id={} module={} port={}".format(h.radio_id, serial, h.acroname_port))
|
||||
elif serial in remote_hosts:
|
||||
_name, ip, _sshtype = remote_hosts[serial]
|
||||
print(
|
||||
"DRY-RUN OFF(remote): radio_id={} host={} module={} port={}".format(
|
||||
h.radio_id, ip, serial, h.acroname_port
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"SKIP: radio_id={} module={} port={} (serial not found locally or in relay hosts)".format(
|
||||
h.radio_id, serial, h.acroname_port
|
||||
)
|
||||
)
|
||||
continue
|
||||
ap = AcronamePower(mod)
|
||||
await ap.port_off(h.acroname_port)
|
||||
print("OFF: radio_id={} module={} port={}".format(h.radio_id, serial, h.acroname_port))
|
||||
if serial in local_serials:
|
||||
await asyncio.to_thread(
|
||||
_set_port_enabled_by_serial_sync,
|
||||
serial=serial,
|
||||
port=int(h.acroname_port),
|
||||
enabled=False,
|
||||
)
|
||||
print("OFF: radio_id={} module={} port={}".format(h.radio_id, serial, h.acroname_port))
|
||||
elif serial in remote_hosts:
|
||||
name, ip, sshtype = remote_hosts[serial]
|
||||
await _set_port_enabled_remote(
|
||||
host_name=name,
|
||||
ipaddr=ip,
|
||||
sshtype=sshtype,
|
||||
serial=serial,
|
||||
port=int(h.acroname_port),
|
||||
enabled=False,
|
||||
)
|
||||
print("OFF(remote): radio_id={} host={} module={} port={}".format(h.radio_id, ip, serial, h.acroname_port))
|
||||
else:
|
||||
print(
|
||||
"SKIP: radio_id={} module={} port={} (serial not found locally or in relay hosts)".format(
|
||||
h.radio_id, serial, h.acroname_port
|
||||
)
|
||||
)
|
||||
if keep_key is not None:
|
||||
keep = rrhs[keep_radio_id]
|
||||
if keep.acroname_module_serial is None:
|
||||
raise RuntimeError("{}: missing acroname_module_serial".format(keep.radio_id))
|
||||
serial = int(keep.acroname_module_serial)
|
||||
port = int(keep.acroname_port)
|
||||
if dry_run:
|
||||
if serial in local_serials:
|
||||
print("DRY-RUN ON: radio_id={} module={} port={}".format(keep.radio_id, serial, port))
|
||||
elif serial in remote_hosts:
|
||||
_name, ip, _sshtype = remote_hosts[serial]
|
||||
print("DRY-RUN ON(remote): radio_id={} host={} module={} port={}".format(keep.radio_id, ip, serial, port))
|
||||
else:
|
||||
if serial in local_serials:
|
||||
await asyncio.to_thread(
|
||||
_set_port_enabled_by_serial_sync,
|
||||
serial=serial,
|
||||
port=port,
|
||||
enabled=True,
|
||||
)
|
||||
print("ON: radio_id={} module={} port={}".format(keep.radio_id, serial, port))
|
||||
elif serial in remote_hosts:
|
||||
name, ip, sshtype = remote_hosts[serial]
|
||||
await _set_port_enabled_remote(
|
||||
host_name=name,
|
||||
ipaddr=ip,
|
||||
sshtype=sshtype,
|
||||
serial=serial,
|
||||
port=port,
|
||||
enabled=True,
|
||||
)
|
||||
print("ON(remote): radio_id={} host={} module={} port={}".format(keep.radio_id, ip, serial, port))
|
||||
|
||||
|
||||
def _set_port_enabled_by_serial_sync(*, serial: int, port: int, enabled: bool) -> None:
|
||||
import brainstem.discover as discover
|
||||
from brainstem import stem
|
||||
from brainstem.result import Result
|
||||
|
||||
specs = discover.findAllModules(discover.Spec.USB, buffer_length=128)
|
||||
spec = next((s for s in specs if int(s.serial_number) == int(serial)), None)
|
||||
if spec is None:
|
||||
raise RuntimeError("module serial {} not found on local USB".format(serial))
|
||||
model_map = {17: stem.USBHub2x4, 19: stem.USBHub3p, 24: stem.USBHub3c}
|
||||
cls = model_map.get(int(spec.model))
|
||||
if cls is None:
|
||||
raise RuntimeError("unsupported Acroname model {} for serial {}".format(spec.model, serial))
|
||||
hub = cls()
|
||||
err = int(hub.connectFromSpec(spec))
|
||||
if err != Result.NO_ERROR:
|
||||
raise RuntimeError("connectFromSpec failed for serial {}: {}".format(serial, err))
|
||||
try:
|
||||
err = int(hub.hub.port[int(port)].setEnabled(bool(enabled)))
|
||||
if err != Result.NO_ERROR:
|
||||
raise RuntimeError(
|
||||
"setEnabled failed for serial {} port {} enabled {}: {}".format(serial, port, enabled, err)
|
||||
)
|
||||
finally:
|
||||
try:
|
||||
hub.disconnect()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
async def _set_port_enabled_remote(
|
||||
*,
|
||||
host_name: str,
|
||||
ipaddr: str,
|
||||
sshtype: str,
|
||||
serial: int,
|
||||
port: int,
|
||||
enabled: bool,
|
||||
) -> None:
|
||||
node = ssh_node(
|
||||
name=host_name,
|
||||
ipaddr=ipaddr,
|
||||
ssh_controlmaster=True,
|
||||
sshtype=sshtype,
|
||||
silent_mode=True,
|
||||
)
|
||||
py_bool = "True" if enabled else "False"
|
||||
cmd = (
|
||||
"python3 -c 'import brainstem.discover as d; "
|
||||
"from brainstem import stem; "
|
||||
"from brainstem.result import Result; "
|
||||
"serial={serial}; port={port}; enabled={enabled}; "
|
||||
"specs=d.findAllModules(d.Spec.USB, buffer_length=128); "
|
||||
"spec=next((s for s in specs if int(s.serial_number)==serial), None); "
|
||||
"assert spec is not None, f\"serial {{serial}} not found\"; "
|
||||
"m={{17: stem.USBHub2x4, 19: stem.USBHub3p, 24: stem.USBHub3c}}.get(int(spec.model)); "
|
||||
"assert m is not None, f\"unsupported model {{spec.model}}\"; "
|
||||
"h=m(); "
|
||||
"err=int(h.connectFromSpec(spec)); "
|
||||
"assert err==Result.NO_ERROR, f\"connectFromSpec err={{err}}\"; "
|
||||
"err=int(h.hub.port[port].setEnabled(enabled)); "
|
||||
"h.disconnect(); "
|
||||
"assert err==Result.NO_ERROR, f\"setEnabled err={{err}}\"'"
|
||||
).format(serial=int(serial), port=int(port), enabled=py_bool)
|
||||
session = await node.rexec(cmd=cmd, IO_TIMEOUT=30.0, CMD_TIMEOUT=90, CONNECT_TIMEOUT=30.0)
|
||||
out = session.results.decode("utf-8", errors="replace")
|
||||
if "Traceback" in out or "AssertionError" in out:
|
||||
raise RuntimeError("remote setEnabled failed on {}: {}".format(ipaddr, out.strip()))
|
||||
|
||||
|
||||
def main() -> int:
|
||||
|
|
@ -97,6 +258,12 @@ def main() -> int:
|
|||
default="configs/my-fabric.json",
|
||||
help="Path to FabricDefinition JSON (default: configs/my-fabric.json)",
|
||||
)
|
||||
p.add_argument(
|
||||
"-c",
|
||||
"--lab-ini",
|
||||
default=None,
|
||||
help="Lab INI path for relay host discovery (default: configs/default.ini)",
|
||||
)
|
||||
p.add_argument(
|
||||
"--keep-radio-id",
|
||||
default="7915",
|
||||
|
|
@ -116,6 +283,7 @@ def main() -> int:
|
|||
asyncio.run(
|
||||
_power_only_one(
|
||||
fabric_json=args.fabric_json,
|
||||
lab_ini=args.lab_ini,
|
||||
keep_radio_id=args.keep_radio_id if not args.all_off else None,
|
||||
all_off=args.all_off,
|
||||
dry_run=args.dry_run,
|
||||
|
|
|
|||
Loading…
Reference in New Issue