Split weathermap generation from dashboard composition (manual + generated merge) #52

Closed
opened 2026-07-10 10:05:30 +00:00 by Damien · 1 comment
Owner

Follow-up to #49. Two architectural changes to the dashboard generation pipeline, no change to the underlying metrics/queries already validated in #44/#45/#48/#49.

1. Split script scope: weathermap-only

scripts/generate_weathermap.py currently outputs a full dashboard JSON (weathermap + whatever else was in it at generation time). Scope narrows to: the script only ever produces the weathermap panel (targets + options.weathermap, node/link generation from live IPFabric topology, as already implemented). It must not know about or touch the BGP table, ports table, or throughput panels.

Site label: no change — the existing hostname-regex Prometheus relabel (campus-*/core*/dc-*site) stays as-is. Site naming is a standardized convention, not something the script should adapt around; if a device ever breaks the convention, that's a human fix to the network/Prometheus config, not a case for the script to handle dynamically.

2. Manual + generated dashboard composition

The rest of the dashboard (BGP sessions table, ports/interfaces table, throughput-per-site graphs, template variables, panel layout — all built in #49) becomes a manually-authored JSON file, living in the same directory as the weathermap panel output. For this POC, "manual" means agent-authored, committed directly — not derived from IPFabric or Prometheus discovery.

New merge step (extend generate_weathermap.py or a new small script, whichever is cleaner) that:

  1. Loads the manual dashboard base JSON (panels, variables, layout — everything except the weathermap panel's content)
  2. Generates the weathermap panel content fresh from live IPFabric + Prometheus (existing logic, unchanged)
  3. Locates the weathermap panel slot in the base file by a stable identifier (fixed panel id or title) and injects the generated content, preserving the base file's gridPos (layout stays manual)
  4. Writes the merged, full dashboard JSON as the build artifact to provision
  5. --provision still pushes it to Grafana via the API, as today

Why file-based merge, not live-fetch-from-Grafana

Considered fetching the current dashboard from Grafana's API and patching just the weathermap panel in place (avoids drift if someone hand-edits in the UI). Decided against it for this POC: the manual JSON file is the deliberate source of truth here, edited directly, not the live Grafana state. Simpler, and fits the "GitOps" pattern already used for the weathermap panel itself.

Idempotency / adapting to topology changes

No diffing logic needed — the script already fully regenerates the weathermap panel from live IPFabric data on every run (declarative, not incremental), so re-running it after a topology change already produces the correct up-to-date panel. The only operational gap is triggering: for now, documented as "rerun manually after a topology change"; scheduling/webhook automation is a stretch goal, not in scope here.

Tasks

  • Refactor generate_weathermap.py to emit weathermap-panel-only JSON
  • Author the manual base dashboard JSON (BGP table, ports table, throughput panels, variables, layout) — ported from what #49 already built and validated, just relocated into its own file
  • Implement the merge step
  • Re-provision and confirm the merged dashboard still matches what was visually/API-validated in #49 (no regression on any panel)
  • Document the two-file structure and the merge/provision workflow in the repo README or a short doc
Follow-up to #49. Two architectural changes to the dashboard generation pipeline, no change to the underlying metrics/queries already validated in #44/#45/#48/#49. ## 1. Split script scope: weathermap-only `scripts/generate_weathermap.py` currently outputs a full dashboard JSON (weathermap + whatever else was in it at generation time). Scope narrows to: **the script only ever produces the weathermap panel** (targets + `options.weathermap`, node/link generation from live IPFabric topology, as already implemented). It must not know about or touch the BGP table, ports table, or throughput panels. Site label: **no change** — the existing hostname-regex Prometheus relabel (`campus-*`/`core*`/`dc-*` → `site`) stays as-is. Site naming is a standardized convention, not something the script should adapt around; if a device ever breaks the convention, that's a human fix to the network/Prometheus config, not a case for the script to handle dynamically. ## 2. Manual + generated dashboard composition The rest of the dashboard (BGP sessions table, ports/interfaces table, throughput-per-site graphs, template variables, panel layout — all built in #49) becomes a **manually-authored JSON file**, living in the same directory as the weathermap panel output. For this POC, "manual" means agent-authored, committed directly — not derived from IPFabric or Prometheus discovery. New merge step (extend `generate_weathermap.py` or a new small script, whichever is cleaner) that: 1. Loads the manual dashboard base JSON (panels, variables, layout — everything except the weathermap panel's content) 2. Generates the weathermap panel content fresh from live IPFabric + Prometheus (existing logic, unchanged) 3. Locates the weathermap panel slot in the base file by a stable identifier (fixed panel `id` or title) and injects the generated content, preserving the base file's `gridPos` (layout stays manual) 4. Writes the merged, full dashboard JSON as the build artifact to provision 5. `--provision` still pushes it to Grafana via the API, as today ## Why file-based merge, not live-fetch-from-Grafana Considered fetching the current dashboard from Grafana's API and patching just the weathermap panel in place (avoids drift if someone hand-edits in the UI). Decided against it for this POC: the manual JSON file is the deliberate source of truth here, edited directly, not the live Grafana state. Simpler, and fits the "GitOps" pattern already used for the weathermap panel itself. ## Idempotency / adapting to topology changes No diffing logic needed — the script already fully regenerates the weathermap panel from live IPFabric data on every run (declarative, not incremental), so re-running it after a topology change already produces the correct up-to-date panel. The only operational gap is triggering: for now, documented as "rerun manually after a topology change"; scheduling/webhook automation is a stretch goal, not in scope here. ## Tasks - Refactor `generate_weathermap.py` to emit weathermap-panel-only JSON - Author the manual base dashboard JSON (BGP table, ports table, throughput panels, variables, layout) — ported from what #49 already built and validated, just relocated into its own file - Implement the merge step - Re-provision and confirm the merged dashboard still matches what was visually/API-validated in #49 (no regression on any panel) - Document the two-file structure and the merge/provision workflow in the repo README or a short doc
Damien added the telemetry label 2026-07-10 10:05:30 +00:00
Author
Owner

Implemented in 632d146 (branch feat/telemetry).

File structure

File Role
scripts/generate_weathermap.py Narrowed to weathermap-panel-only. build_weathermap_panel() returns just {type, datasource, targets, options.weathermap} — no gridPos/id/title of its own. No reference to BGP/ports/throughput panels or template variables anywhere in the script.
configs/grafana/dashboard-base.json New, manually-authored (agent-authored per the issue's POC definition, committed directly — not derived from IPFabric/Prometheus). Contains: templating (site/device vars, ported verbatim from #49), and 4 panels: the reserved slot (id: 1, title: "__WEATHERMAP_SLOT__", fixed gridPos), BGP Sessions table, Ports/Interfaces table, Throughput per Site — all ported verbatim from #49's validated queries/transformations, including the #50 join-uniqueness guard and #51 Management0 exclusion (those live in the weathermap script and base file respectively, so both fixes carried through the split intact). Datasource UID is a __DATASOURCE_UID__ placeholder, substituted at merge time.
configs/grafana/weathermap-dashboard.json Build artifact only, same filename as before. Produced by the merge step, not hand-edited.

Merge step

Extended generate_weathermap.py itself (not a separate script — simplest option, avoids inter-script imports): main() now loads the base JSON, substitutes __DATASOURCE_UID__ recursively, finds the panel titled __WEATHERMAP_SLOT__, and dict.update()s the generated panel content into it while preserving that panel's gridPos/id. Renamed to "Fabric Weathermap" on merge. --provision unchanged — still POSTs the merged payload to /api/dashboards/db.

Site label mechanism: untouched, exactly as specified (hostname-regex Prometheus relabel from #49 stays as the standard).

Regression check

Diffed the merged output against the pre-refactor generator-only JSON (both generated back-to-back against the same live IPFabric/Prometheus state, no topology change in between): 1 line different, and that line is the same content ("type": "tamirsuliman-weathermap-panel") just at a different position in the JSON — purely a key-ordering artifact of dict.update() vs the old inline dict literal, not a content change.

Re-validated live via /api/ds/query post-merge:

  • Weathermap STATUS query: 28/28 devices resolve (same as #49)
  • BGP Sessions table query: 180 series resolve
  • Ports/Interfaces table (oper-status / in-rate / out-rate): 198 series each
  • Throughput per site: all 3 sites return values
  • VNI tooltip join (#50 fix): resolves cleanly, max by (device, vlan) guard intact

No topology change was convenient to simulate in this pass (lab topology unchanged since #49/#50/#51) — re-running after a real topology change to confirm the panel updates in place is still an open manual check, but the mechanism is unchanged from #49 (full regeneration every run, no diffing), so no new risk introduced by the split.

Operational note (adapts to topology changes)

No new automation added — as scoped, this is a stretch goal, not in scope. Documented in scripts/README.md: re-running generate_weathermap.py always regenerates the weathermap panel fresh from live IPFabric+Prometheus and re-merges it into the (possibly separately-edited) base, so a topology change requires a manual re-run + --provision, same operational model as before. Editing the BGP/ports/throughput panels or variables now only requires editing dashboard-base.json and re-running — no IPFabric/topology fetch needed for that path (though the script currently always does that fetch regardless; a --base-only fast path that skips it wasn't requested and would be scope creep for this issue).

Implemented in `632d146` (branch `feat/telemetry`). ## File structure | File | Role | |---|---| | `scripts/generate_weathermap.py` | Narrowed to weathermap-panel-only. `build_weathermap_panel()` returns just `{type, datasource, targets, options.weathermap}` — no `gridPos`/`id`/`title` of its own. No reference to BGP/ports/throughput panels or template variables anywhere in the script. | | `configs/grafana/dashboard-base.json` | New, manually-authored (agent-authored per the issue's POC definition, committed directly — not derived from IPFabric/Prometheus). Contains: `templating` (`site`/`device` vars, ported verbatim from #49), and 4 panels: the reserved slot (`id: 1`, `title: "__WEATHERMAP_SLOT__"`, fixed `gridPos`), BGP Sessions table, Ports/Interfaces table, Throughput per Site — all ported verbatim from #49's validated queries/transformations, including the #50 join-uniqueness guard and #51 Management0 exclusion (those live in the weathermap script and base file respectively, so both fixes carried through the split intact). Datasource UID is a `__DATASOURCE_UID__` placeholder, substituted at merge time. | | `configs/grafana/weathermap-dashboard.json` | Build artifact only, same filename as before. Produced by the merge step, not hand-edited. | ## Merge step Extended `generate_weathermap.py` itself (not a separate script — simplest option, avoids inter-script imports): `main()` now loads the base JSON, substitutes `__DATASOURCE_UID__` recursively, finds the panel titled `__WEATHERMAP_SLOT__`, and `dict.update()`s the generated panel content into it while preserving that panel's `gridPos`/`id`. Renamed to `"Fabric Weathermap"` on merge. `--provision` unchanged — still POSTs the merged payload to `/api/dashboards/db`. Site label mechanism: untouched, exactly as specified (hostname-regex Prometheus relabel from #49 stays as the standard). ## Regression check Diffed the merged output against the pre-refactor generator-only JSON (both generated back-to-back against the same live IPFabric/Prometheus state, no topology change in between): **1 line different**, and that line is the same content (`"type": "tamirsuliman-weathermap-panel"`) just at a different position in the JSON — purely a key-ordering artifact of `dict.update()` vs the old inline dict literal, not a content change. Re-validated live via `/api/ds/query` post-merge: - Weathermap STATUS query: 28/28 devices resolve (same as #49) - BGP Sessions table query: 180 series resolve - Ports/Interfaces table (oper-status / in-rate / out-rate): 198 series each - Throughput per site: all 3 sites return values - VNI tooltip join (#50 fix): resolves cleanly, `max by (device, vlan)` guard intact No topology change was convenient to simulate in this pass (lab topology unchanged since #49/#50/#51) — re-running after a real topology change to confirm the panel updates in place is still an open manual check, but the mechanism is unchanged from #49 (full regeneration every run, no diffing), so no new risk introduced by the split. ## Operational note (adapts to topology changes) No new automation added — as scoped, this is a stretch goal, not in scope. Documented in `scripts/README.md`: re-running `generate_weathermap.py` always regenerates the weathermap panel fresh from live IPFabric+Prometheus and re-merges it into the (possibly separately-edited) base, so a topology change requires a manual re-run + `--provision`, same operational model as before. Editing the BGP/ports/throughput panels or variables now only requires editing `dashboard-base.json` and re-running — no IPFabric/topology fetch needed for that path (though the script currently always does that fetch regardless; a `--base-only` fast path that skips it wasn't requested and would be scope creep for this issue).
Damien added reference feat/telemetry 2026-07-10 13:01:18 +00:00
Sign in to join this conversation.