From b27c1663c62cc25b3d4671b7114a1cded43da5c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20P=C3=A9rez?= Date: Mon, 13 Jul 2026 23:14:48 -0400 Subject: [PATCH] feat: publish OBP keepalive connected status in dashboard_state Expose a boolean connected field on ENHANCED_OBP openbridge entries so the monitor can show mesh leg health from the same 60s BCKA rule as routing. --- schemas/examples/dashboard_state.json | 1 + schemas/report-v2.json | 6 +- .../application/report/dashboard_state.py | 27 +++++++- tests/application/test_dashboard_state.py | 62 +++++++++++++++++++ 4 files changed, 93 insertions(+), 3 deletions(-) diff --git a/schemas/examples/dashboard_state.json b/schemas/examples/dashboard_state.json index 09ff622..42d94a8 100644 --- a/schemas/examples/dashboard_state.json +++ b/schemas/examples/dashboard_state.json @@ -57,6 +57,7 @@ "ip": "44.31.61.68", "port": 62999, "enhanced_obp": true, + "connected": false, "streams": {} } } diff --git a/schemas/report-v2.json b/schemas/report-v2.json index 0f5916f..34e1e4d 100644 --- a/schemas/report-v2.json +++ b/schemas/report-v2.json @@ -155,7 +155,11 @@ "network_id": { "$ref": "#/$defs/dmr_id" }, "ip": { "type": "string" }, "port": { "type": "integer", "minimum": 0, "maximum": 65535 }, - "enhanced_obp": { "type": "boolean" } + "enhanced_obp": { "type": "boolean" }, + "connected": { + "type": "boolean", + "description": "ENHANCED_OBP BCKA keepalive OK (peer answered within 60s). Omitted when ENHANCED_OBP is false." + } } }, "dashboard_ctable": { diff --git a/src/adn_server/application/report/dashboard_state.py b/src/adn_server/application/report/dashboard_state.py index 237acf3..1764eda 100644 --- a/src/adn_server/application/report/dashboard_state.py +++ b/src/adn_server/application/report/dashboard_state.py @@ -73,8 +73,28 @@ def _upstream_peer_block(name: str, cfg: dict[str, Any]) -> dict[str, Any]: return block -def _openbridge_block(name: str, cfg: dict[str, Any], topology_row: dict[str, Any] | None) -> dict[str, Any]: +def _obp_ka_connected(cfg: dict[str, Any], now: float) -> bool | None: + """BCKA keepalive status for ENHANCED OBP legs; ``None`` when KA gating does not apply.""" + if not cfg.get("ENHANCED_OBP"): + return None + bcka = cfg.get("_bcka") + if bcka is None: + return False + try: + return float(bcka) >= now - 60 + except (TypeError, ValueError): + return False + + +def _openbridge_block( + name: str, + cfg: dict[str, Any], + topology_row: dict[str, Any] | None, + *, + now: float, +) -> dict[str, Any]: """Enabled OPENBRIDGE legs (``CTABLE.OPENBRIDGES``); STREAMS stay empty here (live chips = monitor/voice).""" + del name block: dict[str, Any] = {"mode": "OPENBRIDGE", "streams": {}} network_id = cfg.get("NETWORK_ID") if network_id is not None: @@ -86,6 +106,9 @@ def _openbridge_block(name: str, cfg: dict[str, Any], topology_row: dict[str, An block["port"] = int(row["port"]) if row.get("enhanced_obp") or cfg.get("ENHANCED_OBP"): block["enhanced_obp"] = True + connected = _obp_ka_connected(cfg, now) + if connected is not None: + block["connected"] = connected return block @@ -137,7 +160,7 @@ def build_dashboard_state( block["port"] = int(topo["port"]) masters[name] = block elif mode == "OPENBRIDGE": - openbridges[name] = _openbridge_block(name, cfg, topo) + openbridges[name] = _openbridge_block(name, cfg, topo, now=epoch) elif mode in ("PEER", "XLXPEER") and _upstream_peer_connected(cfg): peers[name] = _upstream_peer_block(name, cfg) diff --git a/tests/application/test_dashboard_state.py b/tests/application/test_dashboard_state.py index fb14460..0b388d4 100644 --- a/tests/application/test_dashboard_state.py +++ b/tests/application/test_dashboard_state.py @@ -103,9 +103,71 @@ def test_dashboard_state_includes_enabled_openbridge(): assert obp["mode"] == "OPENBRIDGE" assert obp["network_id"] == 73010 assert obp["enhanced_obp"] is True + assert obp["connected"] is False assert obp["streams"] == {} +def test_openbridge_connected_true_when_bcka_fresh() -> None: + now = 1000.0 + systems = { + "OBP-CL": { + "MODE": "OPENBRIDGE", + "ENABLED": True, + "NETWORK_ID": 73010, + "ENHANCED_OBP": True, + "_bcka": now - 10, + "PEERS": {}, + }, + } + state = build_dashboard_state(systems, ts=now) + assert state["ctable"]["OPENBRIDGES"]["OBP-CL"]["connected"] is True + + +def test_openbridge_connected_false_when_bcka_stale() -> None: + now = 1000.0 + systems = { + "OBP-CL": { + "MODE": "OPENBRIDGE", + "ENABLED": True, + "NETWORK_ID": 73010, + "ENHANCED_OBP": True, + "_bcka": now - 61, + "PEERS": {}, + }, + } + state = build_dashboard_state(systems, ts=now) + assert state["ctable"]["OPENBRIDGES"]["OBP-CL"]["connected"] is False + + +def test_openbridge_connected_false_when_bcka_missing() -> None: + now = 1000.0 + systems = { + "OBP-CL": { + "MODE": "OPENBRIDGE", + "ENABLED": True, + "NETWORK_ID": 73010, + "ENHANCED_OBP": True, + "PEERS": {}, + }, + } + state = build_dashboard_state(systems, ts=now) + assert state["ctable"]["OPENBRIDGES"]["OBP-CL"]["connected"] is False + + +def test_openbridge_connected_omitted_without_enhanced_obp() -> None: + systems = { + "OBP-CL": { + "MODE": "OPENBRIDGE", + "ENABLED": True, + "NETWORK_ID": 73010, + "PEERS": {}, + }, + } + state = build_dashboard_state(systems) + obp = state["ctable"]["OPENBRIDGES"]["OBP-CL"] + assert "connected" not in obp + + def test_dashboard_state_includes_connected_upstream_peer(): systems = { "XLX-730": {