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.fabric.fabric import FabricDefinition, FabricRRHBinding
|
||||||
from fiwicontrol.lab.discovery import discover_acroname_modules
|
from fiwicontrol.lab.discovery import discover_acroname_modules
|
||||||
from fiwicontrol.power.acroname import AcronamePower
|
|
||||||
|
|
||||||
_REPO_ROOT = Path(__file__).resolve().parents[2]
|
_REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
|
||||||
|
|
@ -68,7 +67,7 @@ async def _all_off(rrhs: list[FabricRRHBinding]) -> None:
|
||||||
mod = by_serial.get(serial)
|
mod = by_serial.get(serial)
|
||||||
if mod is None:
|
if mod is None:
|
||||||
raise RuntimeError("Acroname module serial {} not found on local USB".format(serial))
|
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)
|
done.add(k)
|
||||||
print("OFF: module={} port={}".format(serial, port))
|
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)
|
mod = by_serial.get(serial)
|
||||||
if mod is None:
|
if mod is None:
|
||||||
raise RuntimeError("Acroname module serial {} not found on local USB".format(serial))
|
raise RuntimeError("Acroname module serial {} not found on local USB".format(serial))
|
||||||
ap = AcronamePower(mod)
|
|
||||||
|
|
||||||
print(
|
print(
|
||||||
"\n[{}/{}] radio_id={} module={} port={} patch_panel_port={}".format(
|
"\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...")
|
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))
|
print("ON: module={} port={}".format(serial, port))
|
||||||
input("Observe fiber/link, then press Enter to power OFF this 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))
|
print("OFF: module={} port={}".format(serial, port))
|
||||||
if dwell > 0:
|
if dwell > 0:
|
||||||
await asyncio.sleep(dwell)
|
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:
|
async def _run(fabric_json: str, dwell: float, skip_all_off: bool) -> None:
|
||||||
path = _resolve_json_path(fabric_json)
|
path = _resolve_json_path(fabric_json)
|
||||||
fd = FabricDefinition.load(path)
|
fd = FabricDefinition.load(path)
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,14 @@ import argparse
|
||||||
import asyncio
|
import asyncio
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from fiwicontrol.commands.node_control import ssh_node
|
||||||
from fiwicontrol.fabric.fabric import FabricDefinition
|
from fiwicontrol.fabric.fabric import FabricDefinition
|
||||||
from fiwicontrol.lab.discovery import discover_acroname_modules
|
from fiwicontrol.lab.discovery import (
|
||||||
from fiwicontrol.power.acroname import AcronamePower
|
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]
|
_REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
|
||||||
|
|
@ -25,6 +30,7 @@ _REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||||
async def _power_only_one(
|
async def _power_only_one(
|
||||||
*,
|
*,
|
||||||
fabric_json: str,
|
fabric_json: str,
|
||||||
|
lab_ini: str | None,
|
||||||
keep_radio_id: str | None,
|
keep_radio_id: str | None,
|
||||||
all_off: bool,
|
all_off: bool,
|
||||||
dry_run: 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))
|
raise SystemExit("radio_id {!r} not found in {}".format(keep_radio_id, json_path))
|
||||||
|
|
||||||
modules = discover_acroname_modules()
|
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
|
keep_key: tuple[int | None, int] | None = None
|
||||||
if all_off:
|
if all_off:
|
||||||
|
|
@ -76,17 +104,150 @@ async def _power_only_one(
|
||||||
"{}: missing acroname_module_serial in {}".format(h.radio_id, json_path)
|
"{}: missing acroname_module_serial in {}".format(h.radio_id, json_path)
|
||||||
)
|
)
|
||||||
serial = int(h.acroname_module_serial)
|
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:
|
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
|
continue
|
||||||
ap = AcronamePower(mod)
|
if serial in local_serials:
|
||||||
await ap.port_off(h.acroname_port)
|
await asyncio.to_thread(
|
||||||
print("OFF: radio_id={} module={} port={}".format(h.radio_id, serial, h.acroname_port))
|
_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:
|
def main() -> int:
|
||||||
|
|
@ -97,6 +258,12 @@ def main() -> int:
|
||||||
default="configs/my-fabric.json",
|
default="configs/my-fabric.json",
|
||||||
help="Path to FabricDefinition JSON (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(
|
p.add_argument(
|
||||||
"--keep-radio-id",
|
"--keep-radio-id",
|
||||||
default="7915",
|
default="7915",
|
||||||
|
|
@ -116,6 +283,7 @@ def main() -> int:
|
||||||
asyncio.run(
|
asyncio.run(
|
||||||
_power_only_one(
|
_power_only_one(
|
||||||
fabric_json=args.fabric_json,
|
fabric_json=args.fabric_json,
|
||||||
|
lab_ini=args.lab_ini,
|
||||||
keep_radio_id=args.keep_radio_id if not args.all_off else None,
|
keep_radio_id=args.keep_radio_id if not args.all_off else None,
|
||||||
all_off=args.all_off,
|
all_off=args.all_off,
|
||||||
dry_run=args.dry_run,
|
dry_run=args.dry_run,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue