From f71121037947a1b9c0c95171f2b769922519e00f Mon Sep 17 00:00:00 2001 From: Damien Arnodo Date: Thu, 9 Jul 2026 16:48:40 +0000 Subject: [PATCH] Generate weathermap-ng panel config from IPFabric topology Adds scripts/generate_weathermap.py: fetches device inventory and connectivity-matrix from IPFabric, cross-checks IPFabric's abbreviated interface names against the live gnmic exporter, and emits a Grafana dashboard-as-code JSON (configs/grafana/weathermap-dashboard.json) with the weathermap-ng panel wired to explicit legendFormat-resolved queries for BGP status, interface tx/rx, and VXLAN MAC-per-VNI. Refs #48 --- README.md | 47 +- configs/grafana/weathermap-dashboard.json | 3301 +++++++++++++++++++++ scripts/generate_weathermap.py | 457 +++ 3 files changed, 3803 insertions(+), 2 deletions(-) create mode 100644 configs/grafana/weathermap-dashboard.json create mode 100644 scripts/generate_weathermap.py diff --git a/README.md b/README.md index 33b84fa..84c0f48 100644 --- a/README.md +++ b/README.md @@ -382,6 +382,45 @@ docker exec clab-arista-evpn-fabric-gnmic wget -qO- http://localhost:9273/metric curl -s 'http://172.16.0.71:9090/api/v1/query?query=system_memory_state_used' | jq ``` +### Weathermap panel generation (dashboard-as-code) + +`scripts/generate_weathermap.py` generates a [weathermap-ng](https://github.com/allamiro/grafana-network-weathermap-ng) +panel from live IPFabric topology + gnmic/Prometheus metrics, and writes a full +Grafana dashboard-as-code JSON to `configs/grafana/weathermap-dashboard.json` +(committed to Gitea as the source of truth, same pattern as the retired Flow +Panel YAML). See issue #48 for the schema research this is built against. + +```bash +export IPFABRIC_URL=https:// +export IPFABRIC_TOKEN= +python3 scripts/generate_weathermap.py # writes the JSON only + +export GRAFANA_URL=https:// +export GRAFANA_TOKEN= +export GRAFANA_DATASOURCE_UID= +python3 scripts/generate_weathermap.py --provision # also provisions via the Grafana API +``` + +- Re-run after any topology change (`evpn-lab.clab.yml`) to regenerate and re-provision. +- Node/link topology comes from IPFabric (`/tables/inventory/devices`, + `/tables/interfaces/connectivity-matrix`); node positions are computed as a + simple site-grouped grid (dc/core/campus bands) since IPFabric has no + layout data. +- IPFabric reports abbreviated interface names (`Et11`) while gnmic exposes + full OpenConfig names (`Ethernet11`); the script aliases known prefixes + (`Et`→`Ethernet`, `Po`→`Port-Channel`, `Lo`→`Loopback`, `Vl`→`Vlan`, + `Ma`→`Management`) and cross-checks the result against the live exporter, + logging (not silently dropping) any link whose aliased name has no matching + series — the actual fix for a real mismatch belongs in gnmic interface + aliasing (#43), not in this script. +- `.100`/`.200` subinterface rows in the connectivity-matrix (802.1Q tags used + for the gold VRF stitching on Core) are excluded — they ride the same + physical port as their parent interface and gnmic only exports physical + interface counters. +- The Grafana panel plugin id (`GRAFANA_WEATHERMAP_PLUGIN_ID`, default + `allamiro-weathermap-panel`) is a placeholder — confirm it against the + actually installed plugin before provisioning. + ## 📁 Repository Structure ``` @@ -405,8 +444,12 @@ arista-evpn-vxlan-clab/ │ ├── campus-access1.cfg, campus-access2.cfg │ ├── gnmic/ │ │ └── gnmic-config.yml -│ └── prometheus/ -│ └── prometheus.yml +│ ├── prometheus/ +│ │ └── prometheus.yml +│ └── grafana/ +│ └── weathermap-dashboard.json +├── scripts/ +│ └── generate_weathermap.py └── hosts/ ├── README.md ├── dc-server1_interfaces … dc-server4_interfaces diff --git a/configs/grafana/weathermap-dashboard.json b/configs/grafana/weathermap-dashboard.json new file mode 100644 index 0000000..8d0019b --- /dev/null +++ b/configs/grafana/weathermap-dashboard.json @@ -0,0 +1,3301 @@ +{ + "dashboard": { + "uid": "evpn-vxlan-fabric-weathermap", + "title": "EVPN/VXLAN Fabric Weathermap", + "tags": [ + "evpn", + "vxlan", + "weathermap", + "auto-generated" + ], + "timezone": "browser", + "schemaVersion": 39, + "version": 0, + "panels": [ + { + "id": 1, + "type": "tamirsuliman-weathermap-panel", + "title": "Fabric Weathermap", + "gridPos": { + "h": 24, + "w": 24, + "x": 0, + "y": 0 + }, + "datasource": { + "type": "prometheus", + "uid": "aflu2t514uf40a" + }, + "targets": [ + { + "refId": "A", + "expr": "min by (device) (network_instances_network_instance_protocols_protocol_bgp_neighbors_neighbor_state_session_state{network_instance_name=\"default\", device=~\"campus\\-spine1|dc\\-spine1|dc\\-spine2|campus\\-spine2|campus\\-border\\-leaf1|dc\\-leaf1|campus\\-border\\-leaf2|dc\\-leaf4|dc\\-leaf3|dc\\-leaf5|dc\\-border\\-leaf1|dc\\-leaf6|dc\\-access1|dc\\-access2|dc\\-leaf8|dc\\-leaf7|dc\\-access4|dc\\-access3|dc\\-border\\-leaf2|dc\\-leaf2|campus\\-leaf1|campus\\-access1|campus\\-leaf2|campus\\-leaf3|campus\\-access2|campus\\-leaf4|core1|core2\"})", + "legendFormat": "BGP {{device}}", + "datasource": { + "type": "prometheus", + "uid": "aflu2t514uf40a" + } + }, + { + "refId": "B", + "expr": "rate(interfaces_interface_state_counters_out_octets{device=~\"campus\\-spine1|dc\\-spine1|dc\\-spine2|campus\\-spine2|campus\\-border\\-leaf1|dc\\-leaf1|campus\\-border\\-leaf2|dc\\-leaf4|dc\\-leaf3|dc\\-leaf5|dc\\-border\\-leaf1|dc\\-leaf6|dc\\-access1|dc\\-access2|dc\\-leaf8|dc\\-leaf7|dc\\-access4|dc\\-access3|dc\\-border\\-leaf2|dc\\-leaf2|campus\\-leaf1|campus\\-access1|campus\\-leaf2|campus\\-leaf3|campus\\-access2|campus\\-leaf4|core1|core2\", interface=~\"Ethernet1|Ethernet10|Ethernet11|Ethernet12|Ethernet13|Ethernet14|Ethernet2|Ethernet3|Ethernet4|Ethernet5|Ethernet6|Ethernet7|Ethernet8|Ethernet9\"}[5m]) * 8", + "legendFormat": "{{device}} {{interface}} tx", + "datasource": { + "type": "prometheus", + "uid": "aflu2t514uf40a" + } + }, + { + "refId": "C", + "expr": "rate(interfaces_interface_state_counters_in_octets{device=~\"campus\\-spine1|dc\\-spine1|dc\\-spine2|campus\\-spine2|campus\\-border\\-leaf1|dc\\-leaf1|campus\\-border\\-leaf2|dc\\-leaf4|dc\\-leaf3|dc\\-leaf5|dc\\-border\\-leaf1|dc\\-leaf6|dc\\-access1|dc\\-access2|dc\\-leaf8|dc\\-leaf7|dc\\-access4|dc\\-access3|dc\\-border\\-leaf2|dc\\-leaf2|campus\\-leaf1|campus\\-access1|campus\\-leaf2|campus\\-leaf3|campus\\-access2|campus\\-leaf4|core1|core2\", interface=~\"Ethernet1|Ethernet10|Ethernet11|Ethernet12|Ethernet13|Ethernet14|Ethernet2|Ethernet3|Ethernet4|Ethernet5|Ethernet6|Ethernet7|Ethernet8|Ethernet9\"}[5m]) * 8", + "legendFormat": "{{device}} {{interface}} rx", + "datasource": { + "type": "prometheus", + "uid": "aflu2t514uf40a" + } + }, + { + "refId": "D", + "expr": "count by (device, vlan) (network_instances_network_instance_fdb_mac_table_entries_entry_vlan{device=~\"campus\\-leaf1|campus\\-leaf2|campus\\-leaf3|campus\\-leaf4|dc\\-leaf1|dc\\-leaf2|dc\\-leaf5|dc\\-leaf6\"})\n* on(device, vlan) group_left(vlan_to_vni_state_vni)\ninterfaces_interface_arista_vxlan_vlan_to_vnis_vlan_to_vni_state_vni{device=~\"campus\\-leaf1|campus\\-leaf2|campus\\-leaf3|campus\\-leaf4|dc\\-leaf1|dc\\-leaf2|dc\\-leaf5|dc\\-leaf6\"}", + "legendFormat": "VNI {{device}} {{vlan}}", + "datasource": { + "type": "prometheus", + "uid": "aflu2t514uf40a" + } + } + ], + "options": { + "weathermap": { + "version": 14, + "id": "evpn-vxlan-fabric-weathermap", + "nodes": [ + { + "id": "campus-spine1", + "label": "campus-spine1", + "position": [ + 460, + 900 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP campus-spine1", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ] + }, + { + "id": "dc-spine1", + "label": "dc-spine1", + "position": [ + 460, + 300 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP dc-spine1", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ] + }, + { + "id": "dc-spine2", + "label": "dc-spine2", + "position": [ + 640, + 300 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP dc-spine2", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ] + }, + { + "id": "campus-spine2", + "label": "campus-spine2", + "position": [ + 640, + 900 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP campus-spine2", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ] + }, + { + "id": "campus-border-leaf1", + "label": "campus-border-leaf1", + "position": [ + 460, + 750 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP campus-border-leaf1", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ] + }, + { + "id": "dc-leaf1", + "label": "dc-leaf1", + "position": [ + 100, + 150 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP dc-leaf1", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ], + "tooltipMetrics": [ + { + "label": "VNI dc-leaf1 40", + "query": "VNI dc-leaf1 40", + "units": "MACs" + } + ] + }, + { + "id": "campus-border-leaf2", + "label": "campus-border-leaf2", + "position": [ + 640, + 750 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP campus-border-leaf2", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ] + }, + { + "id": "dc-leaf4", + "label": "dc-leaf4", + "position": [ + 640, + 150 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP dc-leaf4", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ] + }, + { + "id": "dc-leaf3", + "label": "dc-leaf3", + "position": [ + 460, + 150 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP dc-leaf3", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ] + }, + { + "id": "dc-leaf5", + "label": "dc-leaf5", + "position": [ + 820, + 150 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP dc-leaf5", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ], + "tooltipMetrics": [ + { + "label": "VNI dc-leaf5 40", + "query": "VNI dc-leaf5 40", + "units": "MACs" + } + ] + }, + { + "id": "dc-border-leaf1", + "label": "dc-border-leaf1", + "position": [ + 820, + 0 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP dc-border-leaf1", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ] + }, + { + "id": "dc-leaf6", + "label": "dc-leaf6", + "position": [ + 1000, + 150 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP dc-leaf6", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ], + "tooltipMetrics": [ + { + "label": "VNI dc-leaf6 40", + "query": "VNI dc-leaf6 40", + "units": "MACs" + } + ] + }, + { + "id": "dc-access1", + "label": "dc-access1", + "position": [ + 100, + 0 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP dc-access1", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ] + }, + { + "id": "dc-access2", + "label": "dc-access2", + "position": [ + 280, + 0 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP dc-access2", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ] + }, + { + "id": "dc-leaf8", + "label": "dc-leaf8", + "position": [ + 280, + 300 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP dc-leaf8", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ] + }, + { + "id": "dc-leaf7", + "label": "dc-leaf7", + "position": [ + 100, + 300 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP dc-leaf7", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ] + }, + { + "id": "dc-access4", + "label": "dc-access4", + "position": [ + 640, + 0 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP dc-access4", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ] + }, + { + "id": "dc-access3", + "label": "dc-access3", + "position": [ + 460, + 0 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP dc-access3", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ] + }, + { + "id": "dc-border-leaf2", + "label": "dc-border-leaf2", + "position": [ + 1000, + 0 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP dc-border-leaf2", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ] + }, + { + "id": "dc-leaf2", + "label": "dc-leaf2", + "position": [ + 280, + 150 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP dc-leaf2", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ], + "tooltipMetrics": [ + { + "label": "VNI dc-leaf2 40", + "query": "VNI dc-leaf2 40", + "units": "MACs" + } + ] + }, + { + "id": "campus-leaf1", + "label": "campus-leaf1", + "position": [ + 820, + 750 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP campus-leaf1", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ], + "tooltipMetrics": [ + { + "label": "VNI campus-leaf1 50", + "query": "VNI campus-leaf1 50", + "units": "MACs" + } + ] + }, + { + "id": "campus-access1", + "label": "campus-access1", + "position": [ + 100, + 750 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP campus-access1", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ] + }, + { + "id": "campus-leaf2", + "label": "campus-leaf2", + "position": [ + 1000, + 750 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP campus-leaf2", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ], + "tooltipMetrics": [ + { + "label": "VNI campus-leaf2 50", + "query": "VNI campus-leaf2 50", + "units": "MACs" + } + ] + }, + { + "id": "campus-leaf3", + "label": "campus-leaf3", + "position": [ + 100, + 900 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP campus-leaf3", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ], + "tooltipMetrics": [ + { + "label": "VNI campus-leaf3 50", + "query": "VNI campus-leaf3 50", + "units": "MACs" + } + ] + }, + { + "id": "campus-access2", + "label": "campus-access2", + "position": [ + 280, + 750 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP campus-access2", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ] + }, + { + "id": "campus-leaf4", + "label": "campus-leaf4", + "position": [ + 280, + 900 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP campus-leaf4", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ], + "tooltipMetrics": [ + { + "label": "VNI campus-leaf4 50", + "query": "VNI campus-leaf4 50", + "units": "MACs" + } + ] + }, + { + "id": "core1", + "label": "core1", + "position": [ + 100, + 450 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP core1", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ] + }, + { + "id": "core2", + "label": "core2", + "position": [ + 280, + 450 + ], + "isConnection": false, + "useConstantSpacing": false, + "compactVerticalLinks": false, + "padding": { + "horizontal": 12, + "vertical": 6 + }, + "colors": { + "font": "#ffffff", + "background": "#22252b", + "border": "#5794F2", + "statusDown": "#F2495C" + }, + "nodeIcon": null, + "statusQuery": "BGP core2", + "nodeStatusColorTarget": "border", + "statusValueMappings": [ + { + "value": 0, + "color": "#F2495C" + }, + { + "value": 1, + "color": "#73BF69" + } + ] + } + ], + "links": [ + { + "id": "campus-border-leaf2-Ethernet11--campus-spine1-Ethernet6", + "nodes": [ + { + "id": "campus-border-leaf2" + }, + { + "id": "campus-spine1" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-border-leaf2 Ethernet11 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "campus-spine1 Ethernet6 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "campus-leaf1-Ethernet11--campus-spine1-Ethernet1", + "nodes": [ + { + "id": "campus-leaf1" + }, + { + "id": "campus-spine1" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-leaf1 Ethernet11 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "campus-spine1 Ethernet1 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "campus-leaf2-Ethernet11--campus-spine1-Ethernet2", + "nodes": [ + { + "id": "campus-leaf2" + }, + { + "id": "campus-spine1" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-leaf2 Ethernet11 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "campus-spine1 Ethernet2 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "campus-leaf3-Ethernet11--campus-spine1-Ethernet3", + "nodes": [ + { + "id": "campus-leaf3" + }, + { + "id": "campus-spine1" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-leaf3 Ethernet11 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "campus-spine1 Ethernet3 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "campus-leaf4-Ethernet11--campus-spine1-Ethernet4", + "nodes": [ + { + "id": "campus-leaf4" + }, + { + "id": "campus-spine1" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-leaf4 Ethernet11 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "campus-spine1 Ethernet4 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-leaf1-Ethernet11--dc-spine1-Ethernet1", + "nodes": [ + { + "id": "dc-leaf1" + }, + { + "id": "dc-spine1" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-leaf1 Ethernet11 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-spine1 Ethernet1 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-leaf4-Ethernet11--dc-spine1-Ethernet4", + "nodes": [ + { + "id": "dc-leaf4" + }, + { + "id": "dc-spine1" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-leaf4 Ethernet11 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-spine1 Ethernet4 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-leaf3-Ethernet11--dc-spine1-Ethernet3", + "nodes": [ + { + "id": "dc-leaf3" + }, + { + "id": "dc-spine1" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-leaf3 Ethernet11 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-spine1 Ethernet3 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-leaf5-Ethernet11--dc-spine1-Ethernet5", + "nodes": [ + { + "id": "dc-leaf5" + }, + { + "id": "dc-spine1" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-leaf5 Ethernet11 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-spine1 Ethernet5 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-border-leaf1-Ethernet11--dc-spine1-Ethernet9", + "nodes": [ + { + "id": "dc-border-leaf1" + }, + { + "id": "dc-spine1" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-border-leaf1 Ethernet11 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-spine1 Ethernet9 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-leaf6-Ethernet11--dc-spine1-Ethernet6", + "nodes": [ + { + "id": "dc-leaf6" + }, + { + "id": "dc-spine1" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-leaf6 Ethernet11 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-spine1 Ethernet6 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-leaf7-Ethernet11--dc-spine1-Ethernet7", + "nodes": [ + { + "id": "dc-leaf7" + }, + { + "id": "dc-spine1" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-leaf7 Ethernet11 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-spine1 Ethernet7 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-border-leaf2-Ethernet11--dc-spine1-Ethernet10", + "nodes": [ + { + "id": "dc-border-leaf2" + }, + { + "id": "dc-spine1" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-border-leaf2 Ethernet11 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-spine1 Ethernet10 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-leaf2-Ethernet11--dc-spine1-Ethernet2", + "nodes": [ + { + "id": "dc-leaf2" + }, + { + "id": "dc-spine1" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-leaf2 Ethernet11 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-spine1 Ethernet2 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "campus-leaf1-Ethernet12--campus-spine2-Ethernet1", + "nodes": [ + { + "id": "campus-leaf1" + }, + { + "id": "campus-spine2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-leaf1 Ethernet12 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "campus-spine2 Ethernet1 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "campus-leaf2-Ethernet12--campus-spine2-Ethernet2", + "nodes": [ + { + "id": "campus-leaf2" + }, + { + "id": "campus-spine2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-leaf2 Ethernet12 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "campus-spine2 Ethernet2 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "campus-border-leaf1-Ethernet11--campus-spine1-Ethernet5", + "nodes": [ + { + "id": "campus-border-leaf1" + }, + { + "id": "campus-spine1" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-border-leaf1 Ethernet11 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "campus-spine1 Ethernet5 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "campus-border-leaf1-Ethernet12--campus-spine2-Ethernet5", + "nodes": [ + { + "id": "campus-border-leaf1" + }, + { + "id": "campus-spine2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-border-leaf1 Ethernet12 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "campus-spine2 Ethernet5 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "campus-border-leaf1-Ethernet10--campus-border-leaf2-Ethernet10", + "nodes": [ + { + "id": "campus-border-leaf1" + }, + { + "id": "campus-border-leaf2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-border-leaf1 Ethernet10 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "campus-border-leaf2 Ethernet10 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "campus-border-leaf1-Ethernet13--core1-Ethernet3", + "nodes": [ + { + "id": "campus-border-leaf1" + }, + { + "id": "core1" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-border-leaf1 Ethernet13 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "core1 Ethernet3 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "campus-border-leaf1-Ethernet14--core2-Ethernet3", + "nodes": [ + { + "id": "campus-border-leaf1" + }, + { + "id": "core2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-border-leaf1 Ethernet14 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "core2 Ethernet3 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-leaf1-Ethernet12--dc-spine2-Ethernet1", + "nodes": [ + { + "id": "dc-leaf1" + }, + { + "id": "dc-spine2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-leaf1 Ethernet12 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-spine2 Ethernet1 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-access1-Ethernet1--dc-leaf1-Ethernet1", + "nodes": [ + { + "id": "dc-access1" + }, + { + "id": "dc-leaf1" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-access1 Ethernet1 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-leaf1 Ethernet1 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-leaf1-Ethernet10--dc-leaf2-Ethernet10", + "nodes": [ + { + "id": "dc-leaf1" + }, + { + "id": "dc-leaf2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-leaf1 Ethernet10 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-leaf2 Ethernet10 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "campus-border-leaf2-Ethernet12--campus-spine2-Ethernet6", + "nodes": [ + { + "id": "campus-border-leaf2" + }, + { + "id": "campus-spine2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-border-leaf2 Ethernet12 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "campus-spine2 Ethernet6 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "campus-border-leaf2-Ethernet13--core1-Ethernet4", + "nodes": [ + { + "id": "campus-border-leaf2" + }, + { + "id": "core1" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-border-leaf2 Ethernet13 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "core1 Ethernet4 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "campus-border-leaf2-Ethernet14--core2-Ethernet4", + "nodes": [ + { + "id": "campus-border-leaf2" + }, + { + "id": "core2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-border-leaf2 Ethernet14 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "core2 Ethernet4 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-leaf4-Ethernet12--dc-spine2-Ethernet4", + "nodes": [ + { + "id": "dc-leaf4" + }, + { + "id": "dc-spine2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-leaf4 Ethernet12 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-spine2 Ethernet4 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-leaf3-Ethernet10--dc-leaf4-Ethernet10", + "nodes": [ + { + "id": "dc-leaf3" + }, + { + "id": "dc-leaf4" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-leaf3 Ethernet10 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-leaf4 Ethernet10 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-leaf3-Ethernet12--dc-spine2-Ethernet3", + "nodes": [ + { + "id": "dc-leaf3" + }, + { + "id": "dc-spine2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-leaf3 Ethernet12 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-spine2 Ethernet3 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-leaf5-Ethernet12--dc-spine2-Ethernet5", + "nodes": [ + { + "id": "dc-leaf5" + }, + { + "id": "dc-spine2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-leaf5 Ethernet12 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-spine2 Ethernet5 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-leaf5-Ethernet10--dc-leaf6-Ethernet10", + "nodes": [ + { + "id": "dc-leaf5" + }, + { + "id": "dc-leaf6" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-leaf5 Ethernet10 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-leaf6 Ethernet10 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-access3-Ethernet1--dc-leaf5-Ethernet1", + "nodes": [ + { + "id": "dc-access3" + }, + { + "id": "dc-leaf5" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-access3 Ethernet1 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-leaf5 Ethernet1 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-border-leaf1-Ethernet12--dc-spine2-Ethernet9", + "nodes": [ + { + "id": "dc-border-leaf1" + }, + { + "id": "dc-spine2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-border-leaf1 Ethernet12 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-spine2 Ethernet9 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "core1-Ethernet1--dc-border-leaf1-Ethernet13", + "nodes": [ + { + "id": "core1" + }, + { + "id": "dc-border-leaf1" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "core1 Ethernet1 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-border-leaf1 Ethernet13 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-leaf6-Ethernet12--dc-spine2-Ethernet6", + "nodes": [ + { + "id": "dc-leaf6" + }, + { + "id": "dc-spine2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-leaf6 Ethernet12 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-spine2 Ethernet6 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-access1-Ethernet2--dc-leaf2-Ethernet1", + "nodes": [ + { + "id": "dc-access1" + }, + { + "id": "dc-leaf2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-access1 Ethernet2 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-leaf2 Ethernet1 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-access2-Ethernet2--dc-leaf4-Ethernet1", + "nodes": [ + { + "id": "dc-access2" + }, + { + "id": "dc-leaf4" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-access2 Ethernet2 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-leaf4 Ethernet1 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-access2-Ethernet1--dc-leaf3-Ethernet1", + "nodes": [ + { + "id": "dc-access2" + }, + { + "id": "dc-leaf3" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-access2 Ethernet1 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-leaf3 Ethernet1 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-leaf8-Ethernet11--dc-spine1-Ethernet8", + "nodes": [ + { + "id": "dc-leaf8" + }, + { + "id": "dc-spine1" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-leaf8 Ethernet11 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-spine1 Ethernet8 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-leaf8-Ethernet12--dc-spine2-Ethernet8", + "nodes": [ + { + "id": "dc-leaf8" + }, + { + "id": "dc-spine2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-leaf8 Ethernet12 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-spine2 Ethernet8 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-leaf7-Ethernet10--dc-leaf8-Ethernet10", + "nodes": [ + { + "id": "dc-leaf7" + }, + { + "id": "dc-leaf8" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-leaf7 Ethernet10 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-leaf8 Ethernet10 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-access4-Ethernet2--dc-leaf8-Ethernet1", + "nodes": [ + { + "id": "dc-access4" + }, + { + "id": "dc-leaf8" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-access4 Ethernet2 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-leaf8 Ethernet1 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-leaf7-Ethernet12--dc-spine2-Ethernet7", + "nodes": [ + { + "id": "dc-leaf7" + }, + { + "id": "dc-spine2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-leaf7 Ethernet12 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-spine2 Ethernet7 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-access4-Ethernet1--dc-leaf7-Ethernet1", + "nodes": [ + { + "id": "dc-access4" + }, + { + "id": "dc-leaf7" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-access4 Ethernet1 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-leaf7 Ethernet1 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-access3-Ethernet2--dc-leaf6-Ethernet1", + "nodes": [ + { + "id": "dc-access3" + }, + { + "id": "dc-leaf6" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-access3 Ethernet2 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-leaf6 Ethernet1 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-border-leaf2-Ethernet12--dc-spine2-Ethernet10", + "nodes": [ + { + "id": "dc-border-leaf2" + }, + { + "id": "dc-spine2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-border-leaf2 Ethernet12 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-spine2 Ethernet10 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-border-leaf1-Ethernet10--dc-border-leaf2-Ethernet10", + "nodes": [ + { + "id": "dc-border-leaf1" + }, + { + "id": "dc-border-leaf2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-border-leaf1 Ethernet10 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-border-leaf2 Ethernet10 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "core1-Ethernet2--dc-border-leaf2-Ethernet13", + "nodes": [ + { + "id": "core1" + }, + { + "id": "dc-border-leaf2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "core1 Ethernet2 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-border-leaf2 Ethernet13 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "core2-Ethernet2--dc-border-leaf2-Ethernet14", + "nodes": [ + { + "id": "core2" + }, + { + "id": "dc-border-leaf2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "core2 Ethernet2 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-border-leaf2 Ethernet14 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "dc-leaf2-Ethernet12--dc-spine2-Ethernet2", + "nodes": [ + { + "id": "dc-leaf2" + }, + { + "id": "dc-spine2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "dc-leaf2 Ethernet12 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-spine2 Ethernet2 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "campus-access1-Ethernet1--campus-leaf1-Ethernet1", + "nodes": [ + { + "id": "campus-access1" + }, + { + "id": "campus-leaf1" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-access1 Ethernet1 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "campus-leaf1 Ethernet1 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "campus-access1-Ethernet2--campus-leaf2-Ethernet1", + "nodes": [ + { + "id": "campus-access1" + }, + { + "id": "campus-leaf2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-access1 Ethernet2 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "campus-leaf2 Ethernet1 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "campus-leaf1-Ethernet10--campus-leaf2-Ethernet10", + "nodes": [ + { + "id": "campus-leaf1" + }, + { + "id": "campus-leaf2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-leaf1 Ethernet10 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "campus-leaf2 Ethernet10 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "campus-leaf3-Ethernet12--campus-spine2-Ethernet3", + "nodes": [ + { + "id": "campus-leaf3" + }, + { + "id": "campus-spine2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-leaf3 Ethernet12 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "campus-spine2 Ethernet3 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "campus-access2-Ethernet1--campus-leaf3-Ethernet1", + "nodes": [ + { + "id": "campus-access2" + }, + { + "id": "campus-leaf3" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-access2 Ethernet1 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "campus-leaf3 Ethernet1 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "campus-leaf4-Ethernet12--campus-spine2-Ethernet4", + "nodes": [ + { + "id": "campus-leaf4" + }, + { + "id": "campus-spine2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-leaf4 Ethernet12 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "campus-spine2 Ethernet4 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "campus-leaf3-Ethernet10--campus-leaf4-Ethernet10", + "nodes": [ + { + "id": "campus-leaf3" + }, + { + "id": "campus-leaf4" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-leaf3 Ethernet10 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "campus-leaf4 Ethernet10 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "campus-access2-Ethernet2--campus-leaf4-Ethernet1", + "nodes": [ + { + "id": "campus-access2" + }, + { + "id": "campus-leaf4" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "campus-access2 Ethernet2 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "campus-leaf4 Ethernet1 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "core2-Ethernet1--dc-border-leaf1-Ethernet14", + "nodes": [ + { + "id": "core2" + }, + { + "id": "dc-border-leaf1" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "core2 Ethernet1 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "dc-border-leaf1 Ethernet14 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + }, + { + "id": "core1-Ethernet5--core2-Ethernet5", + "nodes": [ + { + "id": "core1" + }, + { + "id": "core2" + } + ], + "sides": { + "A": { + "bandwidth": 1000000000, + "query": "core1 Ethernet5 tx", + "labelOffset": 55, + "anchor": "Right", + "dashboardLink": "" + }, + "Z": { + "bandwidth": 1000000000, + "query": "core2 Ethernet5 rx", + "labelOffset": 55, + "anchor": "Left", + "dashboardLink": "" + } + }, + "units": "bps", + "arrows": { + "width": 8, + "height": 10, + "offset": 2 + }, + "stroke": 5, + "showThroughputPercentage": false + } + ], + "scale": [ + { + "percent": 0, + "color": "#5794F2" + }, + { + "percent": 70, + "color": "#FA6400" + }, + { + "percent": 90, + "color": "#C4162A" + } + ], + "settings": { + "panel": { + "backgroundColor": "#212124", + "panelSize": { + "width": 1600, + "height": 1000 + }, + "zoomScale": 0, + "offset": { + "x": 0, + "y": 0 + }, + "showTimestamp": true, + "grid": { + "enabled": false, + "size": 10, + "guidesEnabled": false + } + }, + "link": { + "spacing": { + "horizontal": 10, + "vertical": 5 + }, + "stroke": { + "color": "#CCCCDC" + }, + "label": { + "background": "#FFFFFF", + "border": "#000000", + "font": "#000000" + }, + "showAllWithPercentage": false, + "defaultUnits": "bps" + }, + "tooltip": { + "fontSize": 10, + "textColor": "#CCCCDC", + "backgroundColor": "#1A1B1F", + "inboundColor": "#73BF69", + "outboundColor": "#5794F2", + "scaleToBandwidth": false + }, + "fontSizing": { + "node": 12, + "link": 10 + }, + "scale": { + "position": { + "x": 0, + "y": 0 + }, + "size": { + "width": 150, + "height": 100 + }, + "title": "Utilization", + "fontSizing": { + "title": 10, + "threshold": 9 + } + } + } + } + } + } + ] + }, + "overwrite": true +} diff --git a/scripts/generate_weathermap.py b/scripts/generate_weathermap.py new file mode 100644 index 0000000..b208163 --- /dev/null +++ b/scripts/generate_weathermap.py @@ -0,0 +1,457 @@ +#!/usr/bin/env python3 +"""Generate a weathermap-ng panel config from IPFabric topology + gnmic/Prometheus +metrics, and optionally provision it into a Grafana dashboard. + +Data sources (see gitea issues #44, #47, #48): + - IPFabric REST API: device inventory + connectivity-matrix (topology) + - Prometheus (gnmic exporter, configs/prometheus/prometheus.yml): live label + values, used both to validate the IPFabric->gnmic interface-name mapping + and to resolve which VTEPs/VLANs actually have VXLAN data. + +Usage: + python3 scripts/generate_weathermap.py [--output PATH] [--provision] + +Environment: + IPFABRIC_URL e.g. https://ipfabric.example.com + IPFABRIC_TOKEN API token (Inventory/Snapshots read access) + IPFABRIC_SNAPSHOT snapshot id, default "$last" + PROMETHEUS_URL default http://172.16.0.71:9090 (in-topology instance) + GRAFANA_URL required with --provision + GRAFANA_TOKEN required with --provision + GRAFANA_DATASOURCE_UID Prometheus datasource UID in Grafana, required with --provision + GRAFANA_DASHBOARD_UID default "evpn-vxlan-fabric-weathermap" + GRAFANA_WEATHERMAP_PLUGIN_ID default "allamiro-weathermap-panel" -- confirm + against the actually installed plugin id before provisioning. +""" +import argparse +import json +import os +import re +import sys +import urllib.request +import urllib.error +import urllib.parse + +WEATHERMAP_SCHEMA_VERSION = 14 + +# IPFabric abbreviated interface prefixes -> gnmic/OpenConfig full names. +# Longest-prefix-first so e.g. "Ma" doesn't shadow a hypothetical multi-letter clash. +INTERFACE_PREFIX_ALIASES = { + "Et": "Ethernet", + "Po": "Port-Channel", + "Lo": "Loopback", + "Vl": "Vlan", + "Ma": "Management", +} + +SITE_ORDER = ["dc", "core", "campus"] +GRID_COLUMNS = 6 +GRID_SPACING_X = 180 +GRID_SPACING_Y = 150 +SITE_BAND_Y = {"dc": 0, "core": 450, "campus": 750} + +NODE_COLORS = {"font": "#ffffff", "background": "#22252b", "border": "#5794F2", "statusDown": "#F2495C"} +STATUS_VALUE_MAPPINGS = [{"value": 0, "color": "#F2495C"}, {"value": 1, "color": "#73BF69"}] +DEFAULT_LINK_BANDWIDTH_BPS = 10_000_000_000 # 10G fallback when IPFabric speed is missing + + +def env(name, default=None, required=False): + val = os.environ.get(name, default) + if required and not val: + sys.exit(f"Missing required environment variable: {name}") + return val + + +# -------------------------------------------------------------------------- +# IPFabric +# -------------------------------------------------------------------------- + +def ipfabric_request(base_url, token, path, body): + req = urllib.request.Request( + f"{base_url.rstrip('/')}/api{path}", + data=json.dumps(body).encode(), + headers={"X-API-Token": token, "Content-Type": "application/json"}, + method="POST", + ) + with urllib.request.urlopen(req, timeout=30) as resp: + return json.load(resp)["data"] + + +def fetch_devices(base_url, token, snapshot): + return ipfabric_request( + base_url, token, "/tables/inventory/devices", + {"columns": ["hostname", "siteName", "vendor", "model", "sn"], "snapshot": snapshot, + "pagination": {"limit": 1000, "start": 0}}, + ) + + +def fetch_connectivity_matrix(base_url, token, snapshot): + return ipfabric_request( + base_url, token, "/tables/interfaces/connectivity-matrix", + {"columns": ["localHost", "localInt", "remoteHost", "remoteInt", "protocol"], "snapshot": snapshot, + "pagination": {"limit": 5000, "start": 0}}, + ) + + +def fetch_interface_speeds(base_url, token, snapshot): + rows = ipfabric_request( + base_url, token, "/tables/inventory/interfaces", + {"columns": ["hostname", "intName", "speed"], "snapshot": snapshot, + "pagination": {"limit": 5000, "start": 0}}, + ) + speeds = {} + for row in rows: + speed = row.get("speed") + if speed: + speeds[(row["hostname"], row["intName"])] = int(speed) + return speeds + + +# -------------------------------------------------------------------------- +# Prometheus (validation + VXLAN discovery) +# -------------------------------------------------------------------------- + +def prom_query(prometheus_url, promql): + url = f"{prometheus_url.rstrip('/')}/api/v1/query?query=" + urllib.parse.quote(promql) + with urllib.request.urlopen(url, timeout=15) as resp: + payload = json.load(resp) + if payload["status"] != "success": + sys.exit(f"Prometheus query failed: {promql}") + return payload["data"]["result"] + + +def known_interface_pairs(prometheus_url): + """(device, interface) pairs that actually exist in the gnmic exporter, + used to validate the IPFabric->gnmic interface alias before trusting it.""" + results = prom_query(prometheus_url, "interfaces_interface_state_oper_status") + return {(m["metric"]["device"], m["metric"]["interface"]) for m in results} + + +def known_vtep_vlan_pairs(prometheus_url): + """(device, vlan) pairs with a live VLAN-to-VNI mapping -- these are the + VTEP nodes eligible for a VXLAN MAC-count tooltip metric.""" + results = prom_query(prometheus_url, "interfaces_interface_arista_vxlan_vlan_to_vnis_vlan_to_vni_state_vni") + return {(m["metric"]["device"], m["metric"]["vlan"]) for m in results} + + +# -------------------------------------------------------------------------- +# Interface aliasing (IPFabric abbreviated name -> gnmic full name) +# -------------------------------------------------------------------------- + +def alias_interface(ipf_name): + match = re.match(r"^([A-Za-z]+)(\d.*)$", ipf_name) + if not match: + return ipf_name + prefix, rest = match.groups() + full = INTERFACE_PREFIX_ALIASES.get(prefix) + if full is None: + return ipf_name + return f"{full}{rest}" + + +# -------------------------------------------------------------------------- +# Topology processing +# -------------------------------------------------------------------------- + +def build_layout(devices): + by_site = {} + for dev in devices: + by_site.setdefault(dev["siteName"], []).append(dev["hostname"]) + + positions = {} + for site in SITE_ORDER: + band_y = SITE_BAND_Y.get(site, 0) + for idx, host in enumerate(sorted(by_site.get(site, []))): + col, row = idx % GRID_COLUMNS, idx // GRID_COLUMNS + positions[host] = [100 + col * GRID_SPACING_X, band_y + row * GRID_SPACING_Y] + + # Any site not in SITE_ORDER (shouldn't happen given the naming convention, + # but don't silently drop nodes if it does) gets stacked below everything else. + extra_band_y = max(SITE_BAND_Y.values()) + 300 + for site, hosts in by_site.items(): + if site in SITE_ORDER: + continue + for idx, host in enumerate(sorted(hosts)): + col, row = idx % GRID_COLUMNS, idx // GRID_COLUMNS + positions[host] = [100 + col * GRID_SPACING_X, extra_band_y + row * GRID_SPACING_Y] + return positions + + +def dedupe_links(connectivity_matrix): + """connectivity-matrix reports each physical link twice (once from each + side), plus management-plane (Management0) neighbor entries, plus one row + per 802.1Q subinterface on trunked ports (e.g. Et13.100/Et13.200, used for + the gold VRF stitching on Core -- see README). Subinterfaces ride the same + physical port as their parent interface, so they'd otherwise show up as + bogus duplicate parallel links with permanently-unresolvable queries + (gnmic only subscribes to physical interface counters). Keep only + physical Ethernet-to-Ethernet links, one entry per unordered + (host,int)-(host,int) pair.""" + seen = set() + links = [] + for row in connectivity_matrix: + local_int, remote_int = row["localInt"], row["remoteInt"] + if "." in local_int or "." in remote_int: + continue + if not (local_int.startswith("Et") and remote_int.startswith("Et")): + continue + side_a = (row["localHost"], local_int) + side_z = (row["remoteHost"], remote_int) + key = tuple(sorted([side_a, side_z])) + if key in seen: + continue + seen.add(key) + links.append({"a_host": key[0][0], "a_int": key[0][1], "z_host": key[1][0], "z_int": key[1][1]}) + return links + + +# -------------------------------------------------------------------------- +# Weathermap assembly +# -------------------------------------------------------------------------- + +def build_weathermap(devices, links, interface_speeds, positions, prometheus_url, mismatches): + hostnames = [d["hostname"] for d in devices] + host_regex = "|".join(re.escape(h) for h in hostnames) + + known_pairs = known_interface_pairs(prometheus_url) + vtep_vlan_pairs = known_vtep_vlan_pairs(prometheus_url) + vtep_hosts = sorted({dev for dev, _ in vtep_vlan_pairs}) + + def resolve_and_check(host, ipf_int, side_label): + gnmic_int = alias_interface(ipf_int) + if (host, gnmic_int) not in known_pairs: + mismatches.append( + f"{host} {ipf_int} -> {gnmic_int} ({side_label}): no matching series in " + f"interfaces_interface_state_oper_status -- link will reference a query that " + f"resolves to no data until the gnmic/IPFabric interface names are aligned " + f"(see #47/#48 interface aliasing fallback)" + ) + return gnmic_int + + # -- nodes -- + nodes = [] + for dev in devices: + host = dev["hostname"] + node = { + "id": host, + "label": host, + "position": positions[host], + "isConnection": False, + "useConstantSpacing": False, + "compactVerticalLinks": False, + "padding": {"horizontal": 12, "vertical": 6}, + "colors": dict(NODE_COLORS), + "nodeIcon": None, + "statusQuery": f"BGP {host}", + "nodeStatusColorTarget": "border", + "statusValueMappings": [dict(m) for m in STATUS_VALUE_MAPPINGS], + } + if host in vtep_hosts: + node["tooltipMetrics"] = [ + {"label": f"VNI {host} {vlan}", "query": f"VNI {host} {vlan}", "units": "MACs"} + for _, vlan in sorted(v for v in vtep_vlan_pairs if v[0] == host) + ] + nodes.append(node) + + # -- links -- + link_defs = [] + interface_regex_parts = set() + for link in links: + a_gnmic_int = resolve_and_check(link["a_host"], link["a_int"], "side A") + z_gnmic_int = resolve_and_check(link["z_host"], link["z_int"], "side Z") + interface_regex_parts.add(re.escape(a_gnmic_int)) + interface_regex_parts.add(re.escape(z_gnmic_int)) + + a_bw = interface_speeds.get((link["a_host"], link["a_int"]), DEFAULT_LINK_BANDWIDTH_BPS) + z_bw = interface_speeds.get((link["z_host"], link["z_int"]), DEFAULT_LINK_BANDWIDTH_BPS) + if (link["a_host"], link["a_int"]) not in interface_speeds: + mismatches.append(f"{link['a_host']} {link['a_int']}: no IPFabric speed, defaulting bandwidth to {DEFAULT_LINK_BANDWIDTH_BPS}") + if (link["z_host"], link["z_int"]) not in interface_speeds: + mismatches.append(f"{link['z_host']} {link['z_int']}: no IPFabric speed, defaulting bandwidth to {DEFAULT_LINK_BANDWIDTH_BPS}") + + link_defs.append({ + "id": f"{link['a_host']}-{a_gnmic_int}--{link['z_host']}-{z_gnmic_int}", + "nodes": [{"id": link["a_host"]}, {"id": link["z_host"]}], + "sides": { + "A": { + "bandwidth": a_bw, + "query": f"{link['a_host']} {a_gnmic_int} tx", + "labelOffset": 55, "anchor": "Right", "dashboardLink": "", + }, + "Z": { + "bandwidth": z_bw, + "query": f"{link['z_host']} {z_gnmic_int} rx", + "labelOffset": 55, "anchor": "Left", "dashboardLink": "", + }, + }, + "units": "bps", + "arrows": {"width": 8, "height": 10, "offset": 2}, + "stroke": 5, + "showThroughputPercentage": False, + }) + + interface_regex = "|".join(sorted(interface_regex_parts)) + + # -- targets (one query per metric family, per task spec) -- + targets = [ + { + "refId": "A", + "expr": f'min by (device) (network_instances_network_instance_protocols_protocol_bgp_neighbors_neighbor_state_session_state{{network_instance_name="default", device=~"{host_regex}"}})', + "legendFormat": "BGP {{device}}", + }, + { + "refId": "B", + "expr": f'rate(interfaces_interface_state_counters_out_octets{{device=~"{host_regex}", interface=~"{interface_regex}"}}[5m]) * 8', + "legendFormat": "{{device}} {{interface}} tx", + }, + { + "refId": "C", + "expr": f'rate(interfaces_interface_state_counters_in_octets{{device=~"{host_regex}", interface=~"{interface_regex}"}}[5m]) * 8', + "legendFormat": "{{device}} {{interface}} rx", + }, + ] + if vtep_hosts: + vtep_regex = "|".join(re.escape(h) for h in vtep_hosts) + # Verbatim join from #44 "VXLAN (MAC count per VNI)", scoped to VTEP nodes. + targets.append({ + "refId": "D", + "expr": ( + f'count by (device, vlan) (network_instances_network_instance_fdb_mac_table_entries_entry_vlan{{device=~"{vtep_regex}"}})\n' + f'* on(device, vlan) group_left(vlan_to_vni_state_vni)\n' + f'interfaces_interface_arista_vxlan_vlan_to_vnis_vlan_to_vni_state_vni{{device=~"{vtep_regex}"}}' + ), + "legendFormat": "VNI {{device}} {{vlan}}", + }) + + weathermap = { + "version": WEATHERMAP_SCHEMA_VERSION, + "id": "evpn-vxlan-fabric-weathermap", + "nodes": nodes, + "links": link_defs, + "scale": [ + {"percent": 0, "color": "#5794F2"}, + {"percent": 70, "color": "#FA6400"}, + {"percent": 90, "color": "#C4162A"}, + ], + "settings": { + "panel": {"backgroundColor": "#212124", "panelSize": {"width": 1600, "height": 1000}, + "zoomScale": 0, "offset": {"x": 0, "y": 0}, "showTimestamp": True, + "grid": {"enabled": False, "size": 10, "guidesEnabled": False}}, + "link": {"spacing": {"horizontal": 10, "vertical": 5}, "stroke": {"color": "#CCCCDC"}, + "label": {"background": "#FFFFFF", "border": "#000000", "font": "#000000"}, + "showAllWithPercentage": False, "defaultUnits": "bps"}, + "tooltip": {"fontSize": 10, "textColor": "#CCCCDC", "backgroundColor": "#1A1B1F", + "inboundColor": "#73BF69", "outboundColor": "#5794F2", "scaleToBandwidth": False}, + "fontSizing": {"node": 12, "link": 10}, + "scale": {"position": {"x": 0, "y": 0}, "size": {"width": 150, "height": 100}, + "title": "Utilization", "fontSizing": {"title": 10, "threshold": 9}}, + }, + } + return weathermap, targets + + +# -------------------------------------------------------------------------- +# Grafana provisioning +# -------------------------------------------------------------------------- + +def build_dashboard(weathermap, targets, dashboard_uid, datasource_uid, plugin_id): + return { + "dashboard": { + "uid": dashboard_uid, + "title": "EVPN/VXLAN Fabric Weathermap", + "tags": ["evpn", "vxlan", "weathermap", "auto-generated"], + "timezone": "browser", + "schemaVersion": 39, + "version": 0, + "panels": [ + { + "id": 1, + "type": plugin_id, + "title": "Fabric Weathermap", + "gridPos": {"h": 24, "w": 24, "x": 0, "y": 0}, + "datasource": {"type": "prometheus", "uid": datasource_uid}, + "targets": [dict(t, datasource={"type": "prometheus", "uid": datasource_uid}) for t in targets], + "options": {"weathermap": weathermap}, + } + ], + }, + "overwrite": True, + } + + +def provision_to_grafana(grafana_url, api_token, dashboard_payload): + req = urllib.request.Request( + f"{grafana_url.rstrip('/')}/api/dashboards/db", + data=json.dumps(dashboard_payload).encode(), + headers={"Authorization": f"Bearer {api_token}", "Content-Type": "application/json"}, + method="POST", + ) + try: + with urllib.request.urlopen(req, timeout=30) as resp: + return json.load(resp) + except urllib.error.HTTPError as e: + sys.exit(f"Grafana provisioning failed: HTTP {e.code} {e.read().decode()}") + + +# -------------------------------------------------------------------------- + +def main(): + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--output", default="configs/grafana/weathermap-dashboard.json", + help="where to write the generated dashboard-as-code JSON") + parser.add_argument("--provision", action="store_true", + help="also POST the dashboard to the Grafana API (requires GRAFANA_* env vars)") + args = parser.parse_args() + + ipfabric_url = env("IPFABRIC_URL", required=True) + ipfabric_token = env("IPFABRIC_TOKEN", required=True) + snapshot = env("IPFABRIC_SNAPSHOT", "$last") + prometheus_url = env("PROMETHEUS_URL", "http://172.16.0.71:9090") + + print(f"Fetching devices from IPFabric ({ipfabric_url}, snapshot={snapshot})...") + devices = fetch_devices(ipfabric_url, ipfabric_token, snapshot) + print(f" {len(devices)} devices") + + print("Fetching connectivity-matrix...") + matrix = fetch_connectivity_matrix(ipfabric_url, ipfabric_token, snapshot) + links = dedupe_links(matrix) + print(f" {len(links)} fabric links after Management-plane filter + dedup ({len(matrix)} raw rows)") + + print("Fetching interface speeds...") + interface_speeds = fetch_interface_speeds(ipfabric_url, ipfabric_token, snapshot) + + positions = build_layout(devices) + + mismatches = [] + print(f"Cross-checking interface names against live exporter ({prometheus_url})...") + weathermap, targets = build_weathermap(devices, links, interface_speeds, positions, prometheus_url, mismatches) + + if mismatches: + print(f"\n{len(mismatches)} interface-name / bandwidth mismatch(es) found (link kept, not dropped):", file=sys.stderr) + for m in mismatches: + print(f" - {m}", file=sys.stderr) + print(file=sys.stderr) + + dashboard_uid = env("GRAFANA_DASHBOARD_UID", "evpn-vxlan-fabric-weathermap") + datasource_uid = env("GRAFANA_DATASOURCE_UID", "PROMETHEUS_DATASOURCE_UID_PLACEHOLDER") + plugin_id = env("GRAFANA_WEATHERMAP_PLUGIN_ID", "allamiro-weathermap-panel") + dashboard_payload = build_dashboard(weathermap, targets, dashboard_uid, datasource_uid, plugin_id) + + os.makedirs(os.path.dirname(args.output) or ".", exist_ok=True) + with open(args.output, "w") as f: + json.dump(dashboard_payload, f, indent=2) + f.write("\n") + print(f"Wrote {args.output} ({len(weathermap['nodes'])} nodes, {len(weathermap['links'])} links)") + + if args.provision: + grafana_url = env("GRAFANA_URL", required=True) + grafana_token = env("GRAFANA_TOKEN", required=True) + if datasource_uid == "PROMETHEUS_DATASOURCE_UID_PLACEHOLDER": + sys.exit("Refusing to provision: set GRAFANA_DATASOURCE_UID to the real Prometheus datasource UID first") + print(f"Provisioning dashboard '{dashboard_uid}' to {grafana_url}...") + result = provision_to_grafana(grafana_url, grafana_token, dashboard_payload) + print(f" {result.get('status')}: {result.get('url')}") + + +if __name__ == "__main__": + main()