From f6a100e673d1c08af59e2f5f49db9a16ef64de1e Mon Sep 17 00:00:00 2001 From: sam Date: Tue, 19 May 2026 00:07:41 -0700 Subject: [PATCH] Add OBMP-Maps dashboard suite: BGP/IGP/AS topology and geo maps Create a new OBMP-Maps Grafana folder (folderUid 1006) with four data-visualization dashboards built on nodeGraph and geomap panels: - BGP Peer Map: routers as nodes, BGP sessions as edges; iBGP/eBGP edge typing and operator-editable rr_loopbacks variable to denote route reflectors; companion table for sessions to non-monitored neighbours. - IGP / Link-State Topology Map: reworked from LinkState-1004 and moved here (uid preserved); scoped by peer feed / protocol / AS so the 489-node BGP-LS topology stays readable; SR-capability rings. - AS Relationship Map: AS adjacency graph from consecutive AS_PATH pairs over a 200k-route sample; min-occurrence and focus-AS variables; nodes enriched from info_asn whois. - Geographic Prefix Map: geomap of RIB prefixes and origin ASes by IP geolocation, with a note that lab 10.x loopbacks do not geolocate; bounded geo_ip join via a sample-size variable. Also add a data link on the Looking Glass ASN Info panel's origin_as column that jumps to the ASN View dashboard scoped to that AS. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../obmp/Base-1001/looking_glass.json | 176 ++++++++++++-- .../obmp/LinkState-1004/ls_topo.json | 230 ------------------ .../obmp/Maps-1006/as_relationship_map.json | 70 ++++++ .../obmp/Maps-1006/bgp_peer_map.json | 78 ++++++ .../obmp/Maps-1006/geo_prefix_map.json | 104 ++++++++ .../dashboards/obmp/Maps-1006/ls_topo.json | 103 ++++++++ .../dashboards/openbmp-dashboards.yml | 11 + 7 files changed, 519 insertions(+), 253 deletions(-) delete mode 100644 obmp-grafana/dashboards/obmp/LinkState-1004/ls_topo.json create mode 100644 obmp-grafana/dashboards/obmp/Maps-1006/as_relationship_map.json create mode 100644 obmp-grafana/dashboards/obmp/Maps-1006/bgp_peer_map.json create mode 100644 obmp-grafana/dashboards/obmp/Maps-1006/geo_prefix_map.json create mode 100644 obmp-grafana/dashboards/obmp/Maps-1006/ls_topo.json diff --git a/obmp-grafana/dashboards/obmp/Base-1001/looking_glass.json b/obmp-grafana/dashboards/obmp/Base-1001/looking_glass.json index f2cc99f..908b129 100644 --- a/obmp-grafana/dashboards/obmp/Base-1001/looking_glass.json +++ b/obmp-grafana/dashboards/obmp/Base-1001/looking_glass.json @@ -27,7 +27,17 @@ "id": null, "iteration": 1654877090626, "links": [ - {"asDropdown": true,"icon": "external link","includeVars": true,"keepTime": true,"tags": ["obmp-nav"],"title": "OBMP Dashboards","type": "dashboards"} + { + "asDropdown": true, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [ + "obmp-nav" + ], + "title": "OBMP Dashboards", + "type": "dashboards" + } ], "liveNow": false, "panels": [ @@ -58,10 +68,26 @@ "description": "Geolocation of the matched prefix (from the geo_ip table).", "fieldConfig": { "defaults": { - "color": {"mode": "thresholds"}, - "custom": {"hideFrom": {"legend": false,"tooltip": false,"viz": false}}, + "color": { + "mode": "thresholds" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, "mappings": [], - "thresholds": {"mode": "absolute","steps": [{"color": "green","value": null}]} + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } }, "overrides": [] }, @@ -73,29 +99,72 @@ }, "id": 17, "options": { - "basemap": {"config": {},"name": "Layer 0","type": "default"}, - "controls": {"mouseWheelZoom": false,"showAttribution": true,"showDebug": false,"showMeasure": false,"showScale": false,"showZoom": true}, + "basemap": { + "config": {}, + "name": "Layer 0", + "type": "default" + }, + "controls": { + "mouseWheelZoom": false, + "showAttribution": true, + "showDebug": false, + "showMeasure": false, + "showScale": false, + "showZoom": true + }, "layers": [ { "config": { "showLegend": false, "style": { - "color": {"fixed": "red"}, + "color": { + "fixed": "red" + }, "opacity": 0.7, - "rotation": {"fixed": 0,"max": 360,"min": -360,"mode": "mod"}, - "size": {"fixed": 8,"max": 15,"min": 2}, - "symbol": {"fixed": "img/icons/marker/circle.svg","mode": "fixed"}, - "textConfig": {"fontSize": 12,"offsetX": 0,"offsetY": 0,"textAlign": "center","textBaseline": "middle"} + "rotation": { + "fixed": 0, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "fixed": 8, + "max": 15, + "min": 2 + }, + "symbol": { + "fixed": "img/icons/marker/circle.svg", + "mode": "fixed" + }, + "textConfig": { + "fontSize": 12, + "offsetX": 0, + "offsetY": 0, + "textAlign": "center", + "textBaseline": "middle" + } } }, - "location": {"latitude": "latitude","longitude": "longitude","mode": "coords"}, + "location": { + "latitude": "latitude", + "longitude": "longitude", + "mode": "coords" + }, "name": "Prefix Location", "tooltip": true, "type": "markers" } ], - "tooltip": {"mode": "details"}, - "view": {"allLayers": true,"id": "zero","lat": 0,"lon": 0,"zoom": 1} + "tooltip": { + "mode": "details" + }, + "view": { + "allLayers": true, + "id": "zero", + "lat": 0, + "lon": 0, + "zoom": 1 + } }, "pluginVersion": "9.1.7", "targets": [ @@ -329,12 +398,45 @@ }, "fieldConfig": { "defaults": { - "color": {"mode": "thresholds"}, - "custom": {"align": "auto","displayMode": "auto","inspect": false}, + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, "mappings": [], - "thresholds": {"mode": "absolute","steps": [{"color": "green","value": null}]} + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } }, - "overrides": [] + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "origin_as" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "title": "Open ASN View for AS ${__value.raw}", + "url": "/d/asnview-agg/asn-view?var-asn_num=${__value.raw}", + "targetBlank": true + } + ] + } + ] + } + ] }, "gridPos": { "h": 6, @@ -345,7 +447,14 @@ "id": 12, "links": [], "options": { - "footer": {"countRows": false,"fields": "","reducer": ["sum"],"show": false}, + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, "showHeader": true }, "targets": [ @@ -391,10 +500,24 @@ }, "fieldConfig": { "defaults": { - "color": {"mode": "thresholds"}, - "custom": {"align": "auto","displayMode": "auto","inspect": false}, + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, "mappings": [], - "thresholds": {"mode": "absolute","steps": [{"color": "green","value": null}]} + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } }, "overrides": [] }, @@ -407,7 +530,14 @@ "id": 13, "links": [], "options": { - "footer": {"countRows": false,"fields": "","reducer": ["sum"],"show": false}, + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, "showHeader": true }, "targets": [ diff --git a/obmp-grafana/dashboards/obmp/LinkState-1004/ls_topo.json b/obmp-grafana/dashboards/obmp/LinkState-1004/ls_topo.json deleted file mode 100644 index cb3e4c4..0000000 --- a/obmp-grafana/dashboards/obmp/LinkState-1004/ls_topo.json +++ /dev/null @@ -1,230 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "datasource", - "uid": "grafana" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, - "type": "dashboard" - } - ] - }, - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 0, - "id": 23, - "iteration": 1654877522167, - "links": [ - { - "asDropdown": true, - "icon": "external link", - "includeVars": true, - "keepTime": true, - "tags": [ - "obmp-nav" - ], - "title": "OBMP Dashboards", - "type": "dashboards" - } - ], - "liveNow": false, - "panels": [ - { - "datasource": { - "type": "postgres", - "uid": "obmp_postgres" - }, - "gridPos": { - "h": 28, - "w": 23, - "x": 0, - "y": 0 - }, - "id": 2, - "targets": [ - { - "datasource": { - "type": "postgres", - "uid": "obmp_postgres" - }, - "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", - "rawQuery": true, - "rawSql": "select local_node_hash_id as id,\n CASE WHEN max(local_router_name) = '' THEN max(local_igp_routerid) ELSE max(local_router_name) END as title,\n max(local_igp_routerid) as detail__routerid\n from v_ls_links\n where peer_hash_id = '$peer_hash'\n and local_igp_routerid like '%.0000' and remote_igp_routerid like '%.0000'\n and igp_metric <= 16777215\n and state in ($state)\n and (local_node_hash_id = '$local_node_hash' or remote_node_hash_id = '$local_node_hash')\n group by local_node_hash_id\n order by title;", - "refId": " nodes", - "select": [ - [ - { - "params": [ - "amr_rx_hbhloss_pct" - ], - "type": "column" - } - ] - ], - "table": "as_path_metrics", - "timeColumn": "start_timestamp", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - }, - { - "datasource": { - "type": "postgres", - "uid": "obmp_postgres" - }, - "format": "table", - "group": [], - "hide": false, - "metricColumn": "none", - "rawQuery": true, - "rawSql": "select local_node_hash_id || '->' || remote_node_hash_id as id,\n local_node_hash_id as source,\n remote_node_hash_id as target,\n max(igp_metric)::int as mainstat,\n max(state) as secondarystat,\n max(remote_router_name) as detail__remote\n from v_ls_links\n where peer_hash_id = '$peer_hash'\n and local_igp_routerid like '%.0000' and remote_igp_routerid like '%.0000'\n and igp_metric <= 16777215\n and state in ($state)\n and (local_node_hash_id = '$local_node_hash' or remote_node_hash_id = '$local_node_hash')\ngroup by local_node_hash_id,remote_node_hash_id;\n ", - "refId": "edges", - "select": [ - [ - { - "params": [ - "amr_rx_hbhloss_pct" - ], - "type": "column" - } - ] - ], - "table": "as_path_metrics", - "timeColumn": "start_timestamp", - "timeColumnType": "timestamp", - "where": [ - { - "name": "$__timeFilter", - "params": [], - "type": "macro" - } - ] - } - ], - "title": "Topology", - "type": "nodeGraph" - } - ], - "schemaVersion": 36, - "style": "dark", - "tags": [ - "obmp-nav", - "linkstate", - "obmp-linkstate" - ], - "templating": { - "list": [ - { - "current": { - "selected": false, - "text": "yyz01-wxbb-crt01-lo0.webex.com", - "value": "367c22e4-57d9-2328-654b-96ea750e0267" - }, - "datasource": { - "type": "postgres", - "uid": "obmp_postgres" - }, - "definition": "SELECT __text,__value FROM (\n select peername as __text, peer_hash_id as __value, count(*) as count\n from v_ls_nodes\n group by peername,peer_hash_id) d\nwhere count > 0", - "hide": 0, - "includeAll": false, - "label": "BGP Peer", - "multi": false, - "name": "peer_hash", - "options": [], - "query": "SELECT __text,__value FROM (\n select peername as __text, peer_hash_id as __value, count(*) as count\n from v_ls_nodes\n group by peername,peer_hash_id) d\nwhere count > 0", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" - }, - { - "current": { - "selected": true, - "text": "Active", - "value": "ACTIVE" - }, - "hide": 0, - "includeAll": true, - "label": "State", - "multi": false, - "name": "state", - "options": [ - { - "selected": false, - "text": "All", - "value": "$__all" - }, - { - "selected": false, - "text": "Inactive", - "value": "WITHDRAWN" - }, - { - "selected": true, - "text": "Active", - "value": "ACTIVE" - } - ], - "query": "Inactive : WITHDRAWN, Active : ACTIVE", - "queryValue": "", - "skipUrlSync": false, - "type": "custom" - }, - { - "current": { - "selected": false, - "text": "NRT02-WXBB-CRT01", - "value": "3e96d517-e4b8-7264-1479-2814e9691f10" - }, - "datasource": { - "type": "postgres", - "uid": "obmp_postgres" - }, - "definition": "select local_router_name as __text, local_node_hash_id as __value \nfrom v_ls_links\nwhere peer_hash_id = '$peer_hash'\n and local_igp_routerid like '%.0000'\n and igp_metric <= 16777215\n and state in ($state)\ngroup by local_router_name,local_node_hash_id", - "hide": 0, - "includeAll": false, - "label": "Node", - "multi": false, - "name": "local_node_hash", - "options": [], - "query": "select local_router_name as __text, local_node_hash_id as __value \nfrom v_ls_links\nwhere peer_hash_id = '$peer_hash'\n and local_igp_routerid like '%.0000'\n and igp_metric <= 16777215\n and state in ($state)\ngroup by local_router_name,local_node_hash_id", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" - } - ] - }, - "time": { - "from": "now-6h", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "LinkState Topology", - "uid": "SNOLrQlnz", - "version": 3, - "weekStart": "" -} \ No newline at end of file diff --git a/obmp-grafana/dashboards/obmp/Maps-1006/as_relationship_map.json b/obmp-grafana/dashboards/obmp/Maps-1006/as_relationship_map.json new file mode 100644 index 0000000..9d775e2 --- /dev/null +++ b/obmp-grafana/dashboards/obmp/Maps-1006/as_relationship_map.json @@ -0,0 +1,70 @@ +{ + "annotations": {"list": [{"builtIn": 1,"datasource": {"type": "datasource","uid": "grafana"},"enable": true,"hide": true,"iconColor": "rgba(0, 211, 255, 1)","name": "Annotations & Alerts","type": "dashboard"}]}, + "description": "AS adjacency graph derived from consecutive AS pairs in observed AS_PATHs. Edge label = how many times that adjacency appears in a 200k-route sample. Raise 'Min occurrences' to thin the graph; set 'Focus AS' to a 1-hop view around one AS. Manual refresh — the query explodes ~200k AS_PATH arrays.", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [{"asDropdown": true,"icon": "external link","includeVars": true,"keepTime": true,"tags": ["obmp-nav"],"title": "OBMP Dashboards","type": "dashboards"}], + "liveNow": false, + "panels": [ + { + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "description": "Each node is an AS (enriched from info_asn whois data); each edge is an adjacency seen in the AS_PATH sample. Edge label is the occurrence count.", + "fieldConfig": {"defaults": {},"overrides": []}, + "gridPos": {"h": 24,"w": 24,"x": 0,"y": 0}, + "id": 1, + "options": {"nodes": {"mainStatUnit": "","secondaryStatUnit": ""}}, + "targets": [ + { + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "format": "table", + "rawSql": "WITH sample AS (SELECT as_path FROM base_attrs WHERE as_path_count >= 2 LIMIT 200000),\npairs AS (\n SELECT a.asn AS src, b.asn AS dst\n FROM sample ba\n CROSS JOIN LATERAL unnest(ba.as_path) WITH ORDINALITY a(asn,ord)\n JOIN LATERAL unnest(ba.as_path) WITH ORDINALITY b(asn,ord) ON b.ord = a.ord + 1\n WHERE a.asn <> b.asn\n),\nedges AS (\n SELECT LEAST(src,dst) AS x, GREATEST(src,dst) AS y, COUNT(*) AS occ\n FROM pairs GROUP BY 1,2 HAVING COUNT(*) >= $min_occ ORDER BY occ DESC LIMIT 300\n),\nfedges AS (\n SELECT * FROM edges\n WHERE '$focus_as' = '' OR x::text = '$focus_as' OR y::text = '$focus_as'\n),\nnlist AS (SELECT x AS asn FROM fedges UNION SELECT y FROM fedges),\ndeg AS (SELECT asn, COUNT(*) AS d FROM (SELECT x AS asn FROM fedges UNION ALL SELECT y FROM fedges) z GROUP BY asn)\nSELECT n.asn::text AS id,\n COALESCE(NULLIF(ia.as_name,''),'AS'||n.asn) AS title,\n 'AS ' || n.asn AS mainstat,\n COALESCE(NULLIF(ia.country,''),'?') || ' · ' || dg.d::text || ' links' AS secondarystat,\n COALESCE(NULLIF(ia.org_name,''),'—') AS detail__org,\n COALESCE(NULLIF(ia.country,''),'—') AS detail__country,\n dg.d::text AS detail__degree\nFROM nlist n\nLEFT JOIN info_asn ia ON ia.asn = n.asn\nLEFT JOIN deg dg ON dg.asn = n.asn\nORDER BY dg.d DESC", + "refId": "nodes" + }, + { + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "format": "table", + "rawSql": "WITH sample AS (SELECT as_path FROM base_attrs WHERE as_path_count >= 2 LIMIT 200000),\npairs AS (\n SELECT a.asn AS src, b.asn AS dst\n FROM sample ba\n CROSS JOIN LATERAL unnest(ba.as_path) WITH ORDINALITY a(asn,ord)\n JOIN LATERAL unnest(ba.as_path) WITH ORDINALITY b(asn,ord) ON b.ord = a.ord + 1\n WHERE a.asn <> b.asn\n),\nedges AS (\n SELECT LEAST(src,dst) AS x, GREATEST(src,dst) AS y, COUNT(*) AS occ\n FROM pairs GROUP BY 1,2 HAVING COUNT(*) >= $min_occ ORDER BY occ DESC LIMIT 300\n)\nSELECT x::text || '-' || y::text AS id,\n x::text AS source, y::text AS target,\n occ AS mainstat,\n occ::text || ' paths' AS detail__occurrences\nFROM edges\nWHERE '$focus_as' = '' OR x::text = '$focus_as' OR y::text = '$focus_as'", + "refId": "edges" + } + ], + "title": "AS Adjacency Graph", + "type": "nodeGraph" + }, + { + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "description": "The strongest AS adjacencies in the sample, with whois names for both endpoints.", + "fieldConfig": {"defaults": {"custom": {"align": "auto","displayMode": "auto"}},"overrides": [{"matcher": {"id": "byName","options": "Occurrences"},"properties": [{"id": "custom.displayMode","value": "gradient-gauge"},{"id": "color","value": {"mode": "continuous-BlPu"}}]}]}, + "gridPos": {"h": 10,"w": 24,"x": 0,"y": 24}, + "id": 2, + "options": {"showHeader": true,"sortBy": [{"desc": true,"displayName": "Occurrences"}]}, + "targets": [ + { + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "format": "table", + "rawSql": "WITH sample AS (SELECT as_path FROM base_attrs WHERE as_path_count >= 2 LIMIT 200000),\npairs AS (\n SELECT a.asn AS src, b.asn AS dst\n FROM sample ba\n CROSS JOIN LATERAL unnest(ba.as_path) WITH ORDINALITY a(asn,ord)\n JOIN LATERAL unnest(ba.as_path) WITH ORDINALITY b(asn,ord) ON b.ord = a.ord + 1\n WHERE a.asn <> b.asn\n),\nedges AS (\n SELECT LEAST(src,dst) AS x, GREATEST(src,dst) AS y, COUNT(*) AS occ\n FROM pairs GROUP BY 1,2 HAVING COUNT(*) >= $min_occ ORDER BY occ DESC LIMIT 300\n)\nSELECT e.x AS \"AS A\",\n COALESCE(NULLIF(ax.as_name,''),'—') AS \"Name A\",\n e.y AS \"AS B\",\n COALESCE(NULLIF(ay.as_name,''),'—') AS \"Name B\",\n e.occ AS \"Occurrences\"\nFROM edges e\nLEFT JOIN info_asn ax ON ax.asn = e.x\nLEFT JOIN info_asn ay ON ay.asn = e.y\nWHERE '$focus_as' = '' OR e.x::text = '$focus_as' OR e.y::text = '$focus_as'\nORDER BY e.occ DESC", + "refId": "A" + } + ], + "title": "Top AS Adjacencies", + "type": "table" + } + ], + "schemaVersion": 36, + "style": "dark", + "tags": ["obmp", "obmp-nav", "obmp-maps", "asn"], + "templating": { + "list": [ + {"name": "min_occ","type": "custom","label": "Min occurrences","description": "Only draw adjacencies seen at least this many times in the 200k-route sample. Raise it to thin a cluttered graph.","query": "5,20,100,500","current": {"text": "20","value": "20"},"options": [{"text": "5","value": "5","selected": false},{"text": "20","value": "20","selected": true},{"text": "100","value": "100","selected": false},{"text": "500","value": "500","selected": false}],"hide": 0}, + {"name": "focus_as","type": "textbox","label": "Focus AS","description": "Optional. Enter an ASN to show only adjacencies touching that AS (1-hop view). Leave blank for the full graph.","query": "","current": {"text": "","value": ""},"options": [{"text": "","value": "","selected": true}],"hide": 0} + ] + }, + "time": {"from": "now-6h","to": "now"}, + "timepicker": {}, + "timezone": "", + "title": "AS Relationship Map", + "uid": "as-rel-map", + "version": 1, + "weekStart": "" +} diff --git a/obmp-grafana/dashboards/obmp/Maps-1006/bgp_peer_map.json b/obmp-grafana/dashboards/obmp/Maps-1006/bgp_peer_map.json new file mode 100644 index 0000000..dd29c28 --- /dev/null +++ b/obmp-grafana/dashboards/obmp/Maps-1006/bgp_peer_map.json @@ -0,0 +1,78 @@ +{ + "annotations": {"list": [{"builtIn": 1,"datasource": {"type": "datasource","uid": "grafana"},"enable": true,"hide": true,"iconColor": "rgba(0, 211, 255, 1)","name": "Annotations & Alerts","type": "dashboard"}]}, + "description": "BGP peering topology — every monitored router as a node, every BGP session as an edge. Route reflectors (set the RR Loopbacks variable) show a red ring. Sessions to non-monitored neighbours (e.g. the ExaBGP injector) are listed in the table below.", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [{"asDropdown": true,"icon": "external link","includeVars": true,"keepTime": true,"tags": ["obmp-nav"],"title": "OBMP Dashboards","type": "dashboards"}], + "liveNow": false, + "panels": [ + { + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "description": "Routers and their BGP sessions. Node ring: red = Route Reflector (per the RR Loopbacks variable), blue = standard router. Edge label shows iBGP vs eBGP.", + "fieldConfig": { + "defaults": {}, + "overrides": [ + {"matcher": {"id": "byName","options": "arc__rr"},"properties": [{"id": "color","value": {"fixedColor": "red","mode": "fixed"}},{"id": "displayName","value": "Route Reflector"}]}, + {"matcher": {"id": "byName","options": "arc__std"},"properties": [{"id": "color","value": {"fixedColor": "blue","mode": "fixed"}},{"id": "displayName","value": "Router"}]} + ] + }, + "gridPos": {"h": 22,"w": 24,"x": 0,"y": 0}, + "id": 1, + "options": {"nodes": {"mainStatUnit": "","secondaryStatUnit": ""}}, + "targets": [ + { + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "format": "table", + "rawSql": "SELECT vp.router_hash_id::text AS id,\n MAX(vp.routername) AS title,\n 'AS ' || MAX(vp.localasn)::text AS mainstat,\n COUNT(DISTINCT vp.peerbgpid) FILTER (WHERE vp.peer_state = 'up')::text || ' peers up' AS secondarystat,\n host(MAX(vp.localbgpid)) AS detail__bgp_id,\n CASE WHEN host(MAX(vp.localbgpid)) IN (${rr_loopbacks:singlequote}) THEN 'Route Reflector' ELSE 'Router' END AS detail__role,\n CASE WHEN host(MAX(vp.localbgpid)) IN (${rr_loopbacks:singlequote}) THEN 1 ELSE 0 END AS arc__rr,\n CASE WHEN host(MAX(vp.localbgpid)) IN (${rr_loopbacks:singlequote}) THEN 0 ELSE 1 END AS arc__std\nFROM v_peers vp\nWHERE vp.localbgpid IS NOT NULL\nGROUP BY vp.router_hash_id\nORDER BY title", + "refId": "nodes" + }, + { + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "format": "table", + "rawSql": "WITH rid AS (\n SELECT DISTINCT router_hash_id, host(localbgpid) AS bgpid\n FROM v_peers WHERE localbgpid IS NOT NULL\n),\nsess AS (\n SELECT DISTINCT\n LEAST(vp.router_hash_id, rid2.router_hash_id)::text AS a,\n GREATEST(vp.router_hash_id, rid2.router_hash_id)::text AS b,\n CASE WHEN vp.peerasn = vp.localasn THEN 'iBGP' ELSE 'eBGP' END AS kind\n FROM v_peers vp\n JOIN rid rid2 ON rid2.bgpid = host(vp.peerbgpid)\n WHERE vp.peer_state = 'up' AND vp.router_hash_id <> rid2.router_hash_id\n)\nSELECT a || '-' || b AS id, a AS source, b AS target,\n MAX(kind) AS mainstat, MAX(kind) AS detail__session_type\nFROM sess GROUP BY a, b", + "refId": "edges" + } + ], + "title": "BGP Peering Topology", + "type": "nodeGraph" + }, + { + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "description": "Up BGP sessions whose far end is not a monitored router (no node to draw an edge to) — e.g. eBGP sessions to the ExaBGP route injector or external peers.", + "fieldConfig": { + "defaults": {"custom": {"align": "auto","displayMode": "auto"}}, + "overrides": [{"matcher": {"id": "byName","options": "Type"},"properties": [{"id": "custom.displayMode","value": "color-background"},{"id": "mappings","value": [{"options": {"eBGP": {"color": "orange","index": 0},"iBGP": {"color": "blue","index": 1}},"type": "value"}]}]}] + }, + "gridPos": {"h": 8,"w": 24,"x": 0,"y": 22}, + "id": 2, + "options": {"showHeader": true,"sortBy": [{"desc": false,"displayName": "Router"}]}, + "targets": [ + { + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "format": "table", + "rawSql": "SELECT vp.routername AS \"Router\",\n host(vp.peerip) AS \"Peer\",\n vp.peerasn AS \"Peer AS\",\n CASE WHEN vp.peerasn = vp.localasn THEN 'iBGP' ELSE 'eBGP' END AS \"Type\",\n vp.peer_state AS \"State\"\nFROM v_peers vp\nWHERE vp.peer_state = 'up'\n AND NOT EXISTS (SELECT 1 FROM v_peers r WHERE host(r.localbgpid) = host(vp.peerbgpid))\nORDER BY vp.routername, vp.peerip", + "refId": "A" + } + ], + "title": "Sessions to External / Non-Monitored Neighbours", + "type": "table" + } + ], + "schemaVersion": 36, + "style": "dark", + "tags": ["obmp", "obmp-nav", "obmp-maps", "bgp"], + "templating": { + "list": [ + {"name": "rr_loopbacks","type": "custom","label": "RR Loopbacks","description": "Loopback / BGP router-id addresses of your route reflectors. Edit this list for your environment.","query": "10.10.255.0,10.10.255.20,10.11.255.0,10.11.255.20","multi": true,"includeAll": true,"current": {"text": ["All"],"value": ["$__all"]},"options": [{"text": "All","value": "$__all","selected": true},{"text": "10.10.255.0","value": "10.10.255.0","selected": false},{"text": "10.10.255.20","value": "10.10.255.20","selected": false},{"text": "10.11.255.0","value": "10.11.255.0","selected": false},{"text": "10.11.255.20","value": "10.11.255.20","selected": false}],"hide": 0} + ] + }, + "time": {"from": "now-6h","to": "now"}, + "timepicker": {}, + "timezone": "", + "title": "BGP Peer Map", + "uid": "bgp-peer-map", + "version": 1, + "weekStart": "" +} diff --git a/obmp-grafana/dashboards/obmp/Maps-1006/geo_prefix_map.json b/obmp-grafana/dashboards/obmp/Maps-1006/geo_prefix_map.json new file mode 100644 index 0000000..7719573 --- /dev/null +++ b/obmp-grafana/dashboards/obmp/Maps-1006/geo_prefix_map.json @@ -0,0 +1,104 @@ +{ + "annotations": {"list": [{"builtIn": 1,"datasource": {"type": "datasource","uid": "grafana"},"enable": true,"hide": true,"iconColor": "rgba(0, 211, 255, 1)","name": "Annotations & Alerts","type": "dashboard"}]}, + "description": "Geographic view of the routes in the RIB — prefixes and origin ASes plotted by IP-geolocation (geo_ip). Shows the injected real-IP prefixes, not the lab fabric (lab routers use 10.x loopbacks that do not geolocate — see the BGP/IGP node-graph maps for the fabric). Manual refresh — the geo_ip containment join is heavy.", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [{"asDropdown": true,"icon": "external link","includeVars": true,"keepTime": true,"tags": ["obmp-nav"],"title": "OBMP Dashboards","type": "dashboards"}], + "liveNow": false, + "panels": [ + { + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "gridPos": {"h": 4,"w": 24,"x": 0,"y": 0}, + "id": 10, + "options": {"code": {"language": "plaintext","showLineNumbers": false,"showMiniMap": false},"content": "## Geographic Prefix & Origin-AS Map\n\nThese maps plot RIB routes by **IP geolocation** (`geo_ip`). The CML lab routers use `10.x` loopback addressing, which does **not** geolocate — so this dashboard visualises the **injected real-IP prefixes** and their **origin ASes**, not the lab fabric itself. For the lab topology see **BGP Peer Map** and **IGP / Link-State Topology Map**.\n\nQueries are bounded by the **Sample size** variable and run on **manual refresh** only — the `geo_ip` containment join scans ~20M rows.","mode": "markdown"}, + "pluginVersion": "9.1.7", + "title": "", + "type": "text" + }, + { + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "description": "Each marker is a RIB prefix placed at the location of a covering geo_ip block. Bounded by the Sample size variable.", + "fieldConfig": {"defaults": {"color": {"mode": "continuous-GrYlRd"},"custom": {"hideFrom": {"legend": false,"tooltip": false,"viz": false}}},"overrides": []}, + "gridPos": {"h": 16,"w": 12,"x": 0,"y": 4}, + "id": 1, + "options": { + "basemap": {"config": {},"name": "Layer 0","type": "default"}, + "controls": {"mouseWheelZoom": true,"showAttribution": true,"showDebug": false,"showMeasure": false,"showScale": false,"showZoom": true}, + "layers": [{"config": {"showLegend": false,"style": {"color": {"fixed": "blue"},"opacity": 0.4,"size": {"fixed": 4},"symbol": {"fixed": "img/icons/marker/circle.svg","mode": "fixed"}}},"location": {"latitude": "latitude","longitude": "longitude","mode": "coords"},"name": "Prefixes","tooltip": true,"type": "markers"}], + "tooltip": {"mode": "details"}, + "view": {"id": "zero","lat": 25,"lon": 0,"zoom": 2} + }, + "pluginVersion": "9.1.7", + "targets": [ + { + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "format": "table", + "rawSql": "WITH p AS (\n SELECT DISTINCT prefix FROM ip_rib\n WHERE iswithdrawn = false AND isipv4 = true AND family(prefix) = 4\n AND NOT (prefix << '10.0.0.0/8')\n LIMIT $sample_limit\n)\nSELECT host(p.prefix) AS prefix, g.latitude, g.longitude\nFROM p CROSS JOIN LATERAL (\n SELECT latitude, longitude FROM geo_ip WHERE ip >>= p.prefix LIMIT 1\n) g", + "refId": "A" + } + ], + "title": "RIB Prefix Geolocation", + "type": "geomap" + }, + { + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "description": "One marker per origin AS, placed at the geolocation of one of its prefixes and enriched with whois name.", + "fieldConfig": {"defaults": {"color": {"mode": "continuous-BlPu"},"custom": {"hideFrom": {"legend": false,"tooltip": false,"viz": false}}},"overrides": []}, + "gridPos": {"h": 16,"w": 12,"x": 12,"y": 4}, + "id": 2, + "options": { + "basemap": {"config": {},"name": "Layer 0","type": "default"}, + "controls": {"mouseWheelZoom": true,"showAttribution": true,"showDebug": false,"showMeasure": false,"showScale": false,"showZoom": true}, + "layers": [{"config": {"showLegend": false,"style": {"color": {"fixed": "orange"},"opacity": 0.6,"size": {"fixed": 6},"symbol": {"fixed": "img/icons/marker/circle.svg","mode": "fixed"}}},"location": {"latitude": "latitude","longitude": "longitude","mode": "coords"},"name": "Origin ASes","tooltip": true,"type": "markers"}], + "tooltip": {"mode": "details"}, + "view": {"id": "zero","lat": 25,"lon": 0,"zoom": 2} + }, + "pluginVersion": "9.1.7", + "targets": [ + { + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "format": "table", + "rawSql": "WITH oa AS (\n SELECT origin_as, MIN(prefix) AS prefix\n FROM ip_rib\n WHERE iswithdrawn = false AND isipv4 = true AND family(prefix) = 4\n AND NOT (prefix << '10.0.0.0/8') AND origin_as IS NOT NULL\n GROUP BY origin_as\n)\nSELECT 'AS' || oa.origin_as AS \"AS\",\n COALESCE(NULLIF(ia.as_name,''),'AS'||oa.origin_as) AS as_name,\n g.latitude, g.longitude\nFROM oa\nLEFT JOIN info_asn ia ON ia.asn = oa.origin_as\nCROSS JOIN LATERAL (\n SELECT latitude, longitude FROM geo_ip WHERE ip >>= oa.prefix LIMIT 1\n) g", + "refId": "A" + } + ], + "title": "Origin-AS Geographic Distribution", + "type": "geomap" + }, + { + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "description": "Origin ASes grouped by whois country. ASes with no whois enrichment show as (unknown). Fast — no geo_ip join.", + "fieldConfig": {"defaults": {"custom": {"align": "auto","displayMode": "auto"}},"overrides": [{"matcher": {"id": "byName","options": "Origin ASes"},"properties": [{"id": "custom.displayMode","value": "gradient-gauge"},{"id": "color","value": {"mode": "continuous-BlPu"}}]}]}, + "gridPos": {"h": 10,"w": 24,"x": 0,"y": 20}, + "id": 3, + "options": {"showHeader": true,"sortBy": [{"desc": true,"displayName": "Origin ASes"}]}, + "targets": [ + { + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "format": "table", + "rawSql": "SELECT COALESCE(NULLIF(ia.country,''),'(unknown)') AS \"Country\",\n COUNT(DISTINCT r.origin_as) AS \"Origin ASes\"\nFROM (SELECT DISTINCT origin_as FROM ip_rib WHERE iswithdrawn = false AND origin_as IS NOT NULL) r\nLEFT JOIN info_asn ia ON ia.asn = r.origin_as\nGROUP BY 1\nORDER BY 2 DESC", + "refId": "A" + } + ], + "title": "Origin-AS by Country", + "type": "table" + } + ], + "schemaVersion": 36, + "style": "dark", + "tags": ["obmp", "obmp-nav", "obmp-maps", "geo"], + "templating": { + "list": [ + {"name": "sample_limit","type": "custom","label": "Sample size","description": "Number of distinct prefixes to geolocate on the RIB Prefix map. Larger = slower (the geo_ip join scans ~20M rows).","query": "2000,5000,10000","current": {"text": "2000","value": "2000"},"options": [{"text": "2000","value": "2000","selected": true},{"text": "5000","value": "5000","selected": false},{"text": "10000","value": "10000","selected": false}],"hide": 0} + ] + }, + "time": {"from": "now-6h","to": "now"}, + "timepicker": {}, + "timezone": "", + "title": "Geographic Prefix Map", + "uid": "geo-prefix-map", + "version": 1, + "weekStart": "" +} diff --git a/obmp-grafana/dashboards/obmp/Maps-1006/ls_topo.json b/obmp-grafana/dashboards/obmp/Maps-1006/ls_topo.json new file mode 100644 index 0000000..c5fe65f --- /dev/null +++ b/obmp-grafana/dashboards/obmp/Maps-1006/ls_topo.json @@ -0,0 +1,103 @@ +{ + "annotations": {"list": [{"builtIn": 1,"datasource": {"type": "datasource","uid": "grafana"},"enable": true,"hide": true,"iconColor": "rgba(0, 211, 255, 1)","name": "Annotations & Alerts","type": "dashboard"}]}, + "description": "IGP link-state topology (BGP-LS) as a node graph. Scope with the BGP Peer feed, IGP protocol, and AS to keep the graph readable. Edge labels are IGP metric; node rings show Segment Routing capability.", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [{"asDropdown": true,"icon": "external link","includeVars": true,"keepTime": true,"tags": ["obmp-nav"],"title": "OBMP Dashboards","type": "dashboards"}], + "liveNow": false, + "panels": [ + { + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "description": "Every BGP-LS node and link for the selected peer feed / protocol / AS. SR-capable nodes show a green ring.", + "fieldConfig": { + "defaults": {}, + "overrides": [ + {"matcher": {"id": "byName","options": "arc__sr"},"properties": [{"id": "color","value": {"fixedColor": "green","mode": "fixed"}},{"id": "displayName","value": "SR-capable"}]}, + {"matcher": {"id": "byName","options": "arc__plain"},"properties": [{"id": "color","value": {"fixedColor": "blue","mode": "fixed"}},{"id": "displayName","value": "No SR"}]} + ] + }, + "gridPos": {"h": 28,"w": 24,"x": 0,"y": 0}, + "id": 2, + "options": {"nodes": {"mainStatUnit": "","secondaryStatUnit": ""}}, + "targets": [ + { + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "format": "table", + "rawSql": "SELECT n.hash_id::text AS id,\n CASE WHEN COALESCE(n.name,'') = '' THEN n.igp_router_id ELSE n.name END AS title,\n n.router_id AS mainstat,\n n.protocol::text AS secondarystat,\n n.igp_router_id AS detail__igp_id,\n 'AS ' || n.asn AS detail__asn,\n COALESCE(NULLIF(n.sr_capabilities,''),'none') AS detail__sr_caps,\n CASE WHEN COALESCE(n.sr_capabilities,'') <> '' THEN 1 ELSE 0 END AS arc__sr,\n CASE WHEN COALESCE(n.sr_capabilities,'') = '' THEN 1 ELSE 0 END AS arc__plain\nFROM ls_nodes n\nWHERE n.iswithdrawn = false\n AND n.peer_hash_id = '$peer_hash'\n AND ('$protocol' = '' OR n.protocol::text = '$protocol')\n AND ($asn = 0 OR n.asn = $asn)\nORDER BY title", + "refId": "nodes" + }, + { + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "format": "table", + "rawSql": "SELECT l.local_node_hash_id::text || '->' || l.remote_node_hash_id::text AS id,\n l.local_node_hash_id::text AS source,\n l.remote_node_hash_id::text AS target,\n MAX(l.igp_metric)::bigint AS mainstat,\n MAX(l.protocol::text) AS secondarystat,\n MAX(l.te_def_metric)::text AS detail__te_metric,\n MAX(l.max_link_bw)::text AS detail__max_bw\nFROM ls_links l\nJOIN ls_nodes ln ON ln.hash_id = l.local_node_hash_id AND ln.peer_hash_id = l.peer_hash_id\nWHERE l.iswithdrawn = false\n AND l.peer_hash_id = '$peer_hash'\n AND ('$protocol' = '' OR l.protocol::text = '$protocol')\n AND ($asn = 0 OR ln.asn = $asn)\nGROUP BY l.local_node_hash_id, l.remote_node_hash_id", + "refId": "edges" + } + ], + "title": "IGP / Link-State Topology", + "type": "nodeGraph" + } + ], + "schemaVersion": 36, + "style": "dark", + "tags": ["obmp", "obmp-nav", "obmp-maps", "linkstate"], + "templating": { + "list": [ + { + "current": {}, + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "definition": "SELECT __text,__value FROM (select peername as __text, peer_hash_id as __value, count(*) as count from v_ls_nodes group by peername,peer_hash_id) d where count > 0", + "hide": 0, + "includeAll": false, + "label": "BGP Peer feed", + "multi": false, + "name": "peer_hash", + "options": [], + "query": "SELECT __text,__value FROM (select peername as __text, peer_hash_id as __value, count(*) as count from v_ls_nodes group by peername,peer_hash_id) d where count > 0", + "refresh": 1, + "sort": 1, + "type": "query" + }, + { + "allValue": "", + "current": {"selected": true,"text": "All","value": "$__all"}, + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "definition": "SELECT DISTINCT protocol::text FROM ls_nodes WHERE iswithdrawn=false AND protocol IS NOT NULL AND protocol::text <> '' ORDER BY 1", + "hide": 0, + "includeAll": true, + "label": "IGP Protocol", + "multi": false, + "name": "protocol", + "options": [], + "query": "SELECT DISTINCT protocol::text FROM ls_nodes WHERE iswithdrawn=false AND protocol IS NOT NULL AND protocol::text <> '' ORDER BY 1", + "refresh": 1, + "sort": 1, + "type": "query" + }, + { + "allValue": "0", + "current": {"selected": true,"text": "All","value": "$__all"}, + "datasource": {"type": "postgres","uid": "obmp_postgres"}, + "definition": "SELECT DISTINCT asn FROM ls_nodes WHERE iswithdrawn=false AND asn > 0 ORDER BY asn", + "hide": 0, + "includeAll": true, + "label": "AS", + "multi": false, + "name": "asn", + "options": [], + "query": "SELECT DISTINCT asn FROM ls_nodes WHERE iswithdrawn=false AND asn > 0 ORDER BY asn", + "refresh": 1, + "sort": 3, + "type": "query" + } + ] + }, + "time": {"from": "now-6h","to": "now"}, + "timepicker": {}, + "timezone": "", + "title": "IGP / Link-State Topology Map", + "uid": "SNOLrQlnz", + "version": 1, + "weekStart": "" +} diff --git a/obmp-grafana/provisioning/dashboards/openbmp-dashboards.yml b/obmp-grafana/provisioning/dashboards/openbmp-dashboards.yml index 73844dd..a473618 100644 --- a/obmp-grafana/provisioning/dashboards/openbmp-dashboards.yml +++ b/obmp-grafana/provisioning/dashboards/openbmp-dashboards.yml @@ -124,4 +124,15 @@ providers: allowUiUpdates: true options: path: /var/lib/grafana/dashboards/Telemetry-3001 + foldersFromFilesStructure: false + - name: 'OpenBMP-Maps' + orgId: 1 + folder: 'OBMP-Maps' + folderUid: '1006' + type: file + disableDeletion: false + updateIntervalSeconds: 30 + allowUiUpdates: true + options: + path: /var/lib/grafana/dashboards/obmp/Maps-1006 foldersFromFilesStructure: false \ No newline at end of file