check_concentrator: single hub table with Location (local vs remote IP).
Collect local rows via concentrator discovery; parse remote show_hostcards tables; renumber globally. Location is 'local' or resolved IPv4 (fallback host string). Made-with: Cursor
This commit is contained in:
parent
f3e514ffdf
commit
91818781df
|
|
@ -1,11 +1,11 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Smoke check: :class:`fiwi.concentrator.FiWiConcentrator` plus **consolidated** USB hub inventory
|
||||
for **this machine** and every configured **remote** hub host (``show_hostcards`` / same discovery
|
||||
path as ``discover``). Optional Adnacom PCIe host-card catalog at the end.
|
||||
Smoke check: :class:`fiwi.concentrator.FiWiConcentrator` plus a **single** USB hub table covering
|
||||
**this machine** and each configured **remote** (serial, ports, and Location = ``local`` or remote
|
||||
IPv4 when resolvable). Adnacom PCIe catalog last.
|
||||
|
||||
Requires BrainStem (``brainstem``) for local ``show_hostcards`` when this host has hubs attached.
|
||||
Remote hosts need ``fiwi.py`` + venv on the Pi as for ``check_remote``.
|
||||
Requires BrainStem locally for hub enumeration on this host. Remotes use SSH ``show_hostcards``
|
||||
(captured and parsed for the summary table).
|
||||
|
||||
**Standalone**::
|
||||
|
||||
|
|
@ -23,6 +23,7 @@ from __future__ import annotations
|
|||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
from typing import TYPE_CHECKING
|
||||
|
|
@ -37,6 +38,10 @@ if _ROOT not in sys.path:
|
|||
_WIDTH = 62
|
||||
_paths_configured = False
|
||||
|
||||
_HUB_TABLE_ROW_RE = re.compile(
|
||||
r"^\s*\d+\s+\|\s+(0x[0-9A-Fa-f]+)\s+\|\s+(.+)\s*$"
|
||||
)
|
||||
|
||||
|
||||
def _rule(char: str = "-") -> str:
|
||||
return char * _WIDTH
|
||||
|
|
@ -61,6 +66,70 @@ def _instantiate_concentrator() -> FiWiConcentrator:
|
|||
return c
|
||||
|
||||
|
||||
def _remote_location_label(ssh_target: str) -> str:
|
||||
"""IPv4 for ``user@host`` when possible; otherwise the host part."""
|
||||
if "@" not in ssh_target:
|
||||
host = ssh_target.strip()
|
||||
else:
|
||||
host = ssh_target.split("@", 1)[1].strip()
|
||||
if re.fullmatch(r"(?:\d{1,3}\.){3}\d{1,3}", host):
|
||||
return host
|
||||
try:
|
||||
return socket.gethostbyname(host)
|
||||
except OSError:
|
||||
return host
|
||||
|
||||
|
||||
def _collect_local_hub_rows(c: FiWiConcentrator) -> list[tuple[str, str]]:
|
||||
"""
|
||||
``(serial_hex, ports_display)`` per local USB hub, same rules as
|
||||
:meth:`fiwi.concentrator.FiWiConcentrator._print_usb_hub_summary_table`.
|
||||
"""
|
||||
specs = c._enumerate_usb_specs()
|
||||
if not specs:
|
||||
return []
|
||||
if not c.hubs:
|
||||
c._connect_specs(specs)
|
||||
by_sn = c._serial_to_opened_port_count()
|
||||
out: list[tuple[str, str]] = []
|
||||
for spec in specs:
|
||||
sn = spec.serial_number
|
||||
sn_s = f"0x{sn:08X}"
|
||||
if sn in by_sn:
|
||||
ports = str(by_sn[sn])
|
||||
else:
|
||||
inf = c._inferred_downstream_ports_from_spec(spec)
|
||||
ports = str(inf) if inf is not None else "?"
|
||||
ports += " *"
|
||||
out.append((sn_s, ports))
|
||||
return out
|
||||
|
||||
|
||||
def _parse_hub_table_from_show_hostcards_stdout(text: str) -> list[tuple[str, str]]:
|
||||
"""Extract ``(serial, ports)`` from ``show_hostcards`` printed table."""
|
||||
lines = text.splitlines()
|
||||
in_table = False
|
||||
rows: list[tuple[str, str]] = []
|
||||
for ln in lines:
|
||||
if "Hub" in ln and "Serial" in ln and "Ports" in ln and "|" in ln:
|
||||
in_table = True
|
||||
continue
|
||||
if not in_table:
|
||||
continue
|
||||
if re.match(r"^\s*-+\s*$", ln):
|
||||
continue
|
||||
if not ln.strip():
|
||||
break
|
||||
if ln.strip().startswith("*"):
|
||||
break
|
||||
m = _HUB_TABLE_ROW_RE.match(ln)
|
||||
if m:
|
||||
rows.append((m.group(1), m.group(2).strip()))
|
||||
elif rows:
|
||||
break
|
||||
return rows
|
||||
|
||||
|
||||
def _print_banner(config_label: str) -> None:
|
||||
print(_rule("="), flush=True)
|
||||
print(f"Fi-Wi concentrator check [config: {config_label}]", flush=True)
|
||||
|
|
@ -97,49 +166,69 @@ def _merged_hub_hosts() -> list[str]:
|
|||
return seen
|
||||
|
||||
|
||||
def _print_local_usb_section(c: FiWiConcentrator) -> None:
|
||||
hn = socket.gethostname()
|
||||
print(f"--- This machine — {hn} (local USB) ---", flush=True)
|
||||
print(flush=True)
|
||||
c.show_hostcards()
|
||||
print(flush=True)
|
||||
|
||||
|
||||
def _print_remote_usb_sections() -> int:
|
||||
def _print_consolidated_usb_table(c: FiWiConcentrator) -> int:
|
||||
"""
|
||||
Run ``show_hostcards`` over SSH for each merged hub host. Returns 0 if all ok, else 1.
|
||||
One table: #, Serial, Ports, Location (``local`` or remote IP / host).
|
||||
Returns 0 if all remote SSH runs succeeded, else 1.
|
||||
"""
|
||||
from fiwi.ssh import SshNode
|
||||
rc = 0
|
||||
rows: list[tuple[str, str, str]] = []
|
||||
|
||||
for serial, ports in _collect_local_hub_rows(c):
|
||||
rows.append((serial, ports, "local"))
|
||||
|
||||
hosts = _merged_hub_hosts()
|
||||
if not hosts:
|
||||
print("--- Remote USB hubs ---", flush=True)
|
||||
print(
|
||||
" (no remote hosts) — set [remote_hubs] hosts or FIWI_REMOTE_HUBS / "
|
||||
"FIWI_CALIBRATE_REMOTES.",
|
||||
flush=True,
|
||||
)
|
||||
print(" (no remote hosts in config — only local hubs listed.)", flush=True)
|
||||
print(flush=True)
|
||||
return 0
|
||||
|
||||
rc = 0
|
||||
from fiwi.ssh import SshNode
|
||||
|
||||
for host in hosts:
|
||||
print(f"--- Remote USB hubs — SSH → {host} ---", flush=True)
|
||||
print(flush=True)
|
||||
loc = _remote_location_label(host)
|
||||
try:
|
||||
node = SshNode.parse(host)
|
||||
code, out, err = node.invoke_capture(["show_hostcards"], timeout=90, defer=False)
|
||||
if out.strip():
|
||||
print(out.rstrip(), flush=True)
|
||||
if err.strip():
|
||||
print(err.rstrip(), file=sys.stderr, flush=True)
|
||||
if code != 0:
|
||||
print(f" (show_hostcards exit {code})", flush=True)
|
||||
print(f" ! Remote {host} ({loc}): show_hostcards exit {code}", flush=True)
|
||||
rc = 1
|
||||
continue
|
||||
parsed = _parse_hub_table_from_show_hostcards_stdout(out)
|
||||
if not parsed:
|
||||
print(
|
||||
f" ! Remote {host} ({loc}): no hub table in output "
|
||||
f"(empty discover / parse miss).",
|
||||
flush=True,
|
||||
)
|
||||
rc = 1
|
||||
continue
|
||||
for serial, ports in parsed:
|
||||
rows.append((serial, ports, loc))
|
||||
except Exception as exc:
|
||||
print(f" SSH / remote run failed: {exc}", flush=True)
|
||||
print(f" ! Remote {host} ({loc}): {exc}", flush=True)
|
||||
rc = 1
|
||||
|
||||
print("USB power-control hubs (consolidated)", flush=True)
|
||||
print(_rule(), flush=True)
|
||||
hn = socket.gethostname()
|
||||
print(f" Local host: {hn} | Remote resolution: IPv4 when possible, else hostname", flush=True)
|
||||
print(flush=True)
|
||||
|
||||
if not rows:
|
||||
print(
|
||||
" (no hubs) — none on this machine and none parsed from remotes.",
|
||||
flush=True,
|
||||
)
|
||||
print(flush=True)
|
||||
return rc
|
||||
|
||||
print(f"{'#':<4} | {'Serial':<12} | {'Ports':<10} | Location", flush=True)
|
||||
print("-" * 52, flush=True)
|
||||
for i, (serial, ports, loc) in enumerate(rows, start=1):
|
||||
print(f"{i:<4} | {serial:<12} | {ports:<10} | {loc}", flush=True)
|
||||
print(flush=True)
|
||||
return rc
|
||||
|
||||
|
||||
|
|
@ -153,16 +242,8 @@ def _print_pcie_catalog_section() -> None:
|
|||
|
||||
|
||||
def _print_consolidated_report(c: FiWiConcentrator) -> int:
|
||||
"""
|
||||
One flow: config summary → USB hubs (local + all remotes) → PCIe catalog.
|
||||
Returns 0 if remote SSH sections all succeeded, else 1 (local errors still raise).
|
||||
"""
|
||||
_print_ssh_and_hosts_summary()
|
||||
print("USB power-control hubs (BrainStem hostcards)", flush=True)
|
||||
print(_rule(), flush=True)
|
||||
print(flush=True)
|
||||
_print_local_usb_section(c)
|
||||
remote_rc = _print_remote_usb_sections()
|
||||
remote_rc = _print_consolidated_usb_table(c)
|
||||
_print_pcie_catalog_section()
|
||||
return remote_rc
|
||||
|
||||
|
|
@ -174,7 +255,7 @@ def test_concentrator() -> None:
|
|||
|
||||
def _parse_args() -> argparse.Namespace:
|
||||
p = argparse.ArgumentParser(
|
||||
description="Check FiWiConcentrator: consolidated local + remote USB hub inventory.",
|
||||
description="Check FiWiConcentrator: consolidated local + remote USB hub table.",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog=(
|
||||
"PROFILE selects <repo>/config/<PROFILE>.ini (e.g. uax24, uax4, default).\n"
|
||||
|
|
@ -224,7 +305,7 @@ def main() -> int:
|
|||
print(_rule("="), flush=True)
|
||||
print(f"FiWiConcentrator() OK [config: {label}]", flush=True)
|
||||
if remote_fail != 0:
|
||||
print(" (one or more remote show_hostcards runs failed — see above)", flush=True)
|
||||
print(" (one or more remote hub queries had errors — see above)", flush=True)
|
||||
print(_rule("="), flush=True)
|
||||
return remote_fail
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue