Generate weathermap-ng nodes/links config from IPFabric topology #48
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Remplace la config Flow Panel YAML par une génération automatique de la config JSON
weathermap-ng(allamiro/grafana-network-weathermap-ng) à partir de la topologie découverte par IPFabric.Prérequis : #47 (endpoints IPFabric validés :
/tables/inventory/devicespour les nœuds,/tables/interfaces/connectivity-matrixpour les liens).source/nameexposés par gnmic (mapping interface-to-neighbor au niveau PromQL, pas au niveau structure)weathermap-ngSchema research from
allamiro/grafana-network-weathermap-ngsource (src/types.ts,src/utils.ts,src/WeathermapPanel.tsx) + live test fixtures (testing/grafana/dashboards/wm-device-health.json). This is ground truth, not guesswork — no generation code written yet.1. Top-level shape
Panel option field:
options.weathermap, typedWeathermap { version, id, nodes: Node[], links: Link[], scale: Threshold[], settings }. Current schemaversionis 14 (CURRENT_VERSIONin utils.ts).On load,
migrateWeathermap()doeslodash.merge(generateBasicNode(...), node)/merge(generateBasicLink(), link)per node/link — our generator only needs to emit fields it actually knows (id, label, position, statusQuery, sides.query, bandwidth...); everything else (anchors, colors, icon, arrows) gets auto-defaulted. This is also how "old knightss27 exports still work" — default-filling on overlapping field names, not byte-identical schema.2. Node (relevant fields)
anchorsis technically required by the type but safe to omit — auto-defaulted to zeros on load.3. Link (relevant fields)
Caveat: the type says
Link.nodesembeds fullNodeobjects (and live dashboard fixtures do store full duplicates there — editor round-trip artifact), butprovisioning/dashboards/sample-weathermap.jsonuses only{"id":"node-x"}stubs and loads fine — topology resolves by matchingidagainst the top-levelnodes[]array, not fromLink.nodescontent. Generator should emit{"id": "<node-id>"}stubs only, not full duplicated node objects.4. Query-reference mechanism — the important part
It is NOT
refId.statusQuery,LinkSide.query,LinkSide.bandwidthQuery,tooltipMetrics[].query(A/Z)all store the series display name Grafana computes for a returned frame's value field (getFieldDisplayName, driven by the target'slegendFormatwhen set).At render time the panel builds
Map<displayName, value>from alldata.seriesacross alltargets, then looks upnode.statusQuery/side.queryas a direct key into that map.Concrete proof from
wm-device-health.json:Implication: our script must set explicit, predictable
legendFormattemplates (e.g."{{host}} {{interface}} tx","BGP {{host}}") on every PromQL target, then reference the exact resolved string instatusQuery/query/bandwidthQuery. LeavinglegendFormaton Grafana's auto default makes the resulting display name unpredictable.5. Panel-level settings (
Weathermap.settings)Note
scaleexists twice:Weathermap.scale: Threshold[](color-by-%util gradient stops) vsWeathermap.settings.scale(legend widget geometry) — don't conflate in the generator.6. Minimal annotated example (2 nodes, 1 link, 1 target set)
Matching
targets(note thelegendFormattemplates that makequery/statusQuerystrings predictable):7. Pipeline field mapping
node.id,node.labelhostnamenode.positionnode.statusQuerysession_statequery validated in #44 (network_instances_network_instance_protocols_protocol_bgp_neighbors_neighbor_state_session_state); setlegendFormat:"BGP {{host}}", reference"BGP <hostname>"node.statusValueMappingssession_statevalues → color. Need to confirm exact enum encoding (numeric vs string, e.g.ESTABLISHED) from #44 before hardcoding — the0/1above assumes a boolified value, may need adjustmentlink.nodes[].idlocalHost/remoteHost, stub{"id":...}form only, must matchnode.idexactlylink.sides.A/Z.queryrate()query validated in #44, with explicit per-sidelegendFormatlink.sides.A/Z.bandwidthlink.sides.A/Z.anchorlocalInt/remoteInt, not criticalCompatibility note
Didn't need to cross-check the archived
knightss27/grafana-network-weathermapsource — this fork'stypes.ts/utils.tsfully explain the "compatible with old exports" behavior: old JSON getslodash.merged onto current defaults and version-bumped to 14 on load. It's default-filling, not an unchanged schema.Next step (not done here): write the actual generation script against this schema.
Status encoding — RESOLVED ✅
statusValueMappings: [{value:0,...},{value:1,...}]assumption from the schema research above is confirmed correct. Handled at the gnmic layer, not in the generation script (consistent with the "complexity belongs in config" principle):Rationale (per comment in gnmic.yaml):
oper-statusand BGPsession-stateare string enums; the Prometheus exporter silently drops non-numeric values, so both get mapped to 1 (up/established) / 0 (anything else) at the gnmic layer.Bonus:
oper-statusis transformed the same way, so link status (not just node status) can also use a reliable 0/1statusValueMappingsfor graying out down links — covers the requirement from #47/#48 ("grey out a down link").VXLAN subscription — needs doc pass in #44
The live gnmic.yaml includes an
eos-vxlansubscription (VLAN-to-VNI mapping + MAC table entries, join logic noted inline) that was in #44's original scope but never formally validated/documented like BGP/system were. Should be confirmed and added to #44 before the generator tries to consume VNI-related metrics.Generation script — done, validated against live IPFabric + gnmic
scripts/generate_weathermap.py(branchfeat/telemetry, commitf711210). Writes a full Grafana dashboard-as-code JSON toconfigs/grafana/weathermap-dashboard.json(committed, source of truth per the retired Flow Panel YAML pattern).Run:
Re-run after any topology change to regenerate + re-provision.
Real run result: 28 nodes, 61 links, 0 interface-alias mismatches — every physical link's aliased name (
Et11→Ethernet11etc.) resolved against the live exporter.API details not documented anywhere before this, now baked into the script:
X-API-Token: <token>(notAuthorization: Bearer), and the table endpoints live at/api/tables/...— no/api/v1/segment./api/v1/...returns410 Gone.tamirsuliman-weathermap-panel, notallamiro-weathermap-panelas guessed in the schema-research comment (repo name ≠ plugin id). Script takes it viaGRAFANA_WEATHERMAP_PLUGIN_IDenv var, no hardcoding.{{device}}/{{interface}}, not{{host}}as in the original schema example — matches the real relabeled Prometheus labels inconfigs/prometheus/prometheus.yml, not the generic placeholder names.Bug found + fixed during validation: the connectivity-matrix returns one row per 802.1Q subinterface on trunked ports (
Et13.100,Et13.200— the gold VRF stitching tags on Core, see README). These were initially being treated as separate physical links, producing bogus duplicate parallel links with unresolvable queries. Now excluded — only physical Ethernet interfaces are considered.Interface aliasing fallback (1f): implemented as specified — IPFabric abbreviated names (
Et,Po,Lo,Vl,Ma) are expanded to gnmic's OpenConfig-style names, cross-checked againstinterfaces_interface_state_oper_statusseries at generation time, and any unresolved link is logged (not dropped). None triggered on the current topology.Blocked: provisioning (step 2) and visual validation (step 3)
Discovered the Grafana instance's Prometheus datasource (
aflu2t514uf40a→https://prometheus.taila5ad8.ts.net) is a different Prometheus than the in-topology one (172.16.0.71:9090) that actually has the gnmic metrics — confirmed zerointerfaces_interface_state_oper_statusseries on the external instance. This matches the README's note that the in-topology Prometheus "does not replace the existing external Prometheus instance — no cutover yet."Provisioning now would push a panel with correct topology/queries but no live data. Holding off on
--provisionuntil the external Prometheus can see the gnmic data (federation / remote_write / additional scrape target — TBD, out of this issue's scope, may need its own issue). Will re-run--provisionand do the visual validation (8+ half-segments, traffic colors, BGP node status) once that's resolved.Provisioned and validated with live data — steps 2/3 done (mostly)
Datasource gap resolved: a new Grafana datasource
NetLab(uidcfrlusac89se8a) now points at the in-topology Prometheus (172.16.0.71:9090) that actually has the gnmic metrics. Regenerated + provisioned against it (commit0aff25f).Bug found during provisioning validation:
re.escape()escapes-as\-. Python's ownremodule accepts that, but PromQL's RE2-based engine doesn't — every query failed withbad_data: invalid parameter "query": ... unknown escape sequence U+002D '-'. Every hostname in this lab is hyphenated (dc-leaf1,campus-border-leaf1...) so this broke all 4 targets outright. Added apromql_escape()helper that skips escaping-(not a regex metacharacter outside a character class, so it's safe unescaped). Fixed and reprovisioned.Live validation via Grafana's
/api/ds/query(ran each panel target exactly as stored in the dashboard, against theNetLabdatasource):BGP {{device}}{{device}} {{interface}} tx{{device}} {{interface}} rxVNI {{device}} {{vlan}}A/B/C — the queries backing node border color and both sides of every link's half-segment — are confirmed live. That's the core ask (8+ half-segments with correct traffic colors, correct BGP node status).
D (VXLAN MAC/VNI) confirmed broken, root cause found, not fixed here: the
vlanjoin key doesn't actually correlate between the two source metrics on live data. E.g.campus-leaf1's VLAN-to-VNI mapping is keyedvlan="50", but its FDB entries are recorded undervlan="60"/"1006"/"1007"/"4090"/"4091"— no overlap, for any VTEP node in the topology. Likely an Arista internal-VLAN-vs-front-panel-VLAN translation the OpenConfig paths don't reconcile. This is exactly what the previous comment on this issue already flagged as unvalidated ("VXLAN subscription — needs doc pass in #44"); now empirically confirmed rather than just suspected. Non-blocking: it only feeds the decorative per-VTEP tooltip metric, notnode.statusQueryor anylink.query, so it doesn't affect topology/status/traffic rendering. Leaving the join query as-is (per #44 "use it verbatim, do not re-derive"); real fix belongs in #44 once the internal-VLAN translation is understood.Still open
$GRAFANA_URL/d/evpn-vxlan-fabric-weathermap.Fixed: panel crash on load (commit
36c93d9)TypeError: undefined is not an object (evaluating 'i.anchors[0]')— root cause: the schema research earlier in this issue was done againstallamiro/grafana-network-weathermap-ng's source, but the plugin actually installed is a different fork,tamirsuliman-weathermap-panel(confirmed via/api/plugins, already noted in the previous comment). Its rendering code isn't as forgiving about missing fields.Pulled the real
module.jsfrom the running instance and found two divergences from the schema doc:node.anchorsis not auto-defaulted on load in this fork — it's a required per-node object tallying link attachments per side:{0: {numLinks, numFilledLinks}, 1: {...}, ..., 4: {...}}. Omitting it (as the schema research said was safe) crashes immediately.link.sides.A/Z.anchoris the plugin's numeric TS enum value, not the anchor name string —Center=0, Top=1, Bottom=2, Left=3, Right=4(reverse-engineered from the enum definition in module.js:e[e.Center=0]="Center",e[e.Top=1]="Top"...). I had it as the literal string"Right"/"Left", which the anchor-indexed lookups (node.anchors[side.anchor]) silently failed to resolve.Generator now computes
node.anchorsfrom the actual link topology (every node's numLinks tally sums to2 × link count, verified: 122 = 2×61) and emits the correct numeric anchor enum on both link sides. Re-provisioned; the 4 panel targets still resolve live data through/api/ds/query(22/132/132/1 series, unchanged from before this fix).Checked the rest of the plugin source for the same "unguarded property access on an omittable field" pattern —
bandwidthQuery,portLabel,directionLabelare all null-checked/guarded in the code, so those are genuinely safe to leave out as the original research assumed.anchorswas the exception.@Damien — can you reload
$GRAFANA_URL/d/evpn-vxlan-fabric-weathermapand confirm the panel actually renders now? That's the one piece I still can't verify from here.Scope change: VXLAN MAC/VNI dropped from weathermap, moved to a separate panel (tracked as a note in #44)
Decision: instead of chasing the broken
vlanjoin (see previous comment + #44), the VXLAN MAC/VNItooltipMetricsentry is removed fromscripts/generate_weathermap.pyentirely. Cleaner separation of concerns — weathermap stays scoped to topology/link-status/traffic, VXLAN visibility becomes its own dedicated Grafana table panel (not part of this issue's deliverable, no join-fix dependency to unblock it).Script updated:
tooltipMetricsno longer references the VNI join query on VTEP nodes. No other generator behavior changed — node/link generation, layout, anchors, and the A/B/C (BGP/tx/rx) targets are untouched.Remaining open item on this issue is unchanged: visual confirmation of the rendered panel in Grafana UI.