Fix PromQL regex escaping for hyphenated hostnames

re.escape() escapes '-' as '\-', which Python's regex engine accepts
but PromQL's RE2 engine rejects outright (every hostname in this lab
is hyphenated, e.g. dc-leaf1). All 4 panel targets were failing with
"unknown escape sequence U+002D" until now. Also swaps the dashboard
to the newly-created NetLab datasource, which actually has gnmic data
(the previously-wired external Prometheus datasource had none).

Refs #48
This commit is contained in:
2026-07-09 17:08:51 +00:00
parent f711210379
commit 0aff25f509
2 changed files with 22 additions and 13 deletions

View File

@@ -24,43 +24,43 @@
}, },
"datasource": { "datasource": {
"type": "prometheus", "type": "prometheus",
"uid": "aflu2t514uf40a" "uid": "cfrlusac89se8a"
}, },
"targets": [ "targets": [
{ {
"refId": "A", "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\"})", "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}}", "legendFormat": "BGP {{device}}",
"datasource": { "datasource": {
"type": "prometheus", "type": "prometheus",
"uid": "aflu2t514uf40a" "uid": "cfrlusac89se8a"
} }
}, },
{ {
"refId": "B", "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", "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", "legendFormat": "{{device}} {{interface}} tx",
"datasource": { "datasource": {
"type": "prometheus", "type": "prometheus",
"uid": "aflu2t514uf40a" "uid": "cfrlusac89se8a"
} }
}, },
{ {
"refId": "C", "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", "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", "legendFormat": "{{device}} {{interface}} rx",
"datasource": { "datasource": {
"type": "prometheus", "type": "prometheus",
"uid": "aflu2t514uf40a" "uid": "cfrlusac89se8a"
} }
}, },
{ {
"refId": "D", "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\"}", "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}}", "legendFormat": "VNI {{device}} {{vlan}}",
"datasource": { "datasource": {
"type": "prometheus", "type": "prometheus",
"uid": "aflu2t514uf40a" "uid": "cfrlusac89se8a"
} }
} }
], ],

View File

@@ -149,6 +149,15 @@ def alias_interface(ipf_name):
return f"{full}{rest}" return f"{full}{rest}"
def promql_escape(s):
"""re.escape() escapes '-' as '\\-', which Python's own regex engine
accepts but PromQL's RE2-based engine rejects ("unknown escape sequence
U+002D '-'") -- and every hostname in this lab contains hyphens. '-' is
not a regex metacharacter outside a character class, so it's safe to
leave unescaped."""
return re.escape(s).replace("\\-", "-")
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# Topology processing # Topology processing
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
@@ -211,7 +220,7 @@ def dedupe_links(connectivity_matrix):
def build_weathermap(devices, links, interface_speeds, positions, prometheus_url, mismatches): def build_weathermap(devices, links, interface_speeds, positions, prometheus_url, mismatches):
hostnames = [d["hostname"] for d in devices] hostnames = [d["hostname"] for d in devices]
host_regex = "|".join(re.escape(h) for h in hostnames) host_regex = "|".join(promql_escape(h) for h in hostnames)
known_pairs = known_interface_pairs(prometheus_url) known_pairs = known_interface_pairs(prometheus_url)
vtep_vlan_pairs = known_vtep_vlan_pairs(prometheus_url) vtep_vlan_pairs = known_vtep_vlan_pairs(prometheus_url)
@@ -259,8 +268,8 @@ def build_weathermap(devices, links, interface_speeds, positions, prometheus_url
for link in links: for link in links:
a_gnmic_int = resolve_and_check(link["a_host"], link["a_int"], "side A") 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") 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(promql_escape(a_gnmic_int))
interface_regex_parts.add(re.escape(z_gnmic_int)) interface_regex_parts.add(promql_escape(z_gnmic_int))
a_bw = interface_speeds.get((link["a_host"], link["a_int"]), DEFAULT_LINK_BANDWIDTH_BPS) 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) z_bw = interface_speeds.get((link["z_host"], link["z_int"]), DEFAULT_LINK_BANDWIDTH_BPS)
@@ -311,7 +320,7 @@ def build_weathermap(devices, links, interface_speeds, positions, prometheus_url
}, },
] ]
if vtep_hosts: if vtep_hosts:
vtep_regex = "|".join(re.escape(h) for h in vtep_hosts) vtep_regex = "|".join(promql_escape(h) for h in vtep_hosts)
# Verbatim join from #44 "VXLAN (MAC count per VNI)", scoped to VTEP nodes. # Verbatim join from #44 "VXLAN (MAC count per VNI)", scoped to VTEP nodes.
targets.append({ targets.append({
"refId": "D", "refId": "D",