Files

194 lines
6.5 KiB
Python

#!/usr/bin/env python3
"""
Monitoring Stack Validation Script
Validates that all Prometheus targets are UP, key metrics exist for
each dashboard, and Grafana dashboard variables are properly configured.
Transfer to CT 141 via base64 (pitfall 40):
SCRIPT=$(base64 -w0 monitoring_validation.py)
ssh -i ~/.ssh/key root@10.0.20.70 "echo '$SCRIPT' | base64 -d | pct exec 141 -- tee /tmp/validate.py >/dev/null && pct exec 141 -- python3 /tmp/validate.py"
Usage: python3 monitoring_validation.py
Exit: 0 if all checks pass, 1 if any target down or metric missing
"""
import json
import sys
import urllib.request
import urllib.parse
import base64
PROM = "http://127.0.0.1:9090"
GRAFANA = "http://127.0.0.1:3000"
GRAFANA_USER = "admin"
GRAFANA_PASS = "Grafana2026!"
AUTH = base64.b64encode(f"{GRAFANA_USER}:{GRAFANA_PASS}".encode()).decode()
def prom_query(expr):
url = f"{PROM}/api/v1/query?query={urllib.parse.quote(expr)}"
with urllib.request.urlopen(url) as r:
d = json.loads(r.read())
return d.get("data", {}).get("result", [])
def grafana_get(path):
req = urllib.request.Request(f"{GRAFANA}{path}")
req.add_header("Authorization", f"Basic {AUTH}")
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read())
def grafana_post(path, data):
body = json.dumps(data).encode()
req = urllib.request.Request(f"{GRAFANA}{path}", data=body, method="POST")
req.add_header("Authorization", f"Basic {AUTH}")
req.add_header("Content-Type", "application/json")
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read())
def fix_datasource_var(dash, var_name, prom_uid):
"""Fix a datasource template variable with empty current value."""
templating = dash.get("templating", {}).get("list", [])
changed = False
for v in templating:
if v.get("name") == var_name and not v.get("current"):
v["current"] = {"text": "Prometheus", "value": prom_uid}
changed = True
return changed
def add_datasource_var(dash, var_name, prom_uid):
"""Add a missing datasource template variable."""
templating = dash.get("templating", {}).get("list", [])
existing = [v.get("name") for v in templating]
if var_name not in existing:
templating.insert(0, {
"name": var_name,
"label": "Prometheus",
"type": "datasource",
"query": "prometheus",
"current": {"text": "Prometheus", "value": prom_uid},
"includeAll": False,
"hide": 0,
})
return True
return False
def main():
errors = []
print("=" * 60)
print("MONITORING STACK VALIDATION")
print("=" * 60)
# 1. Check all Prometheus targets
with urllib.request.urlopen(f"{PROM}/api/v1/targets") as r:
d = json.loads(r.read())
targets = d.get("data", {}).get("activeTargets", [])
up = [t for t in targets if t["health"] == "up"]
down = [t for t in targets if t["health"] != "up"]
print(f"\n1. Prometheus Targets: {len(up)} UP, {len(down)} DOWN")
for t in down:
job = t.get("labels", {}).get("job", "?")
inst = t.get("scrapeUrl", "?")
err = t.get("lastError", "")[:60]
print(f" DOWN: {job} {inst}{err}")
errors.append(f"Target down: {job}/{inst}")
# 2. Check key metrics per dashboard
checks = [
("Node Exporter", "node_uname_info", 1),
("PVE", "pve_cpu_usage_ratio", 1),
("Ceph", "ceph_health_status", 1),
("MySQL/Galera", "mysql_up", 1),
]
print("\n2. Metrics per dashboard:")
for name, metric, min_series in checks:
r = prom_query(metric)
count = len(r)
status = "OK" if count >= min_series else "FAIL"
print(f" [{status}] {name:20s} {metric:30s} {count:4d} series")
if count < min_series:
errors.append(f"No data for {metric}")
# 3. Get Prometheus datasource UID
ds_list = grafana_get("/api/datasources")
prom_uid = None
for ds in ds_list:
if ds.get("type") == "prometheus":
prom_uid = ds.get("uid")
break
if not prom_uid:
print("\n3. ERROR: No Prometheus datasource found in Grafana!")
errors.append("No Prometheus datasource")
prom_uid = "PBFA97CFB590B2093" # fallback from memory
# 4. Check and fix dashboard variables
dashboards = {
"Node Exporter Full": {"uid": "rYdddlPWk", "ds_var": "ds_prometheus"},
"PVE via Prometheus": {"uid": "Dp7Cd57Zza", "ds_var": "DS_PROMETHEUS"},
"Ceph Cluster": {"uid": "tbO9LAiZK", "ds_var": "DS_PROMETHEUS"},
"MySQL Overview": {"uid": "MQWgroiiz", "ds_var": None},
}
print("\n3. Dashboard variables:")
for name, info in dashboards.items():
uid = info["uid"]
ds_var = info["ds_var"]
d = grafana_get(f"/api/dashboards/uid/{uid}")
dash = d.get("dashboard", {})
panels = dash.get("panels", [])
templating = dash.get("templating", {}).get("list", [])
vars_str = ", ".join(
f"${v['name']}={v.get('current', {}).get('value', '?')}"
for v in templating
)
print(f" {name:25s} {len(panels):3d} panels | {vars_str}")
# Fix missing/empty datasource variables
if ds_var:
needs_fix = add_datasource_var(dash, ds_var, prom_uid)
needs_fix = fix_datasource_var(dash, ds_var, prom_uid) or needs_fix
if needs_fix:
result = grafana_post("/api/dashboards/db", {
"dashboard": dash,
"message": f"Fix {ds_var} datasource variable",
"overwrite": True,
})
print(f" → Fixed: {result.get('status')}")
# 5. Alertmanager
print("\n4. Alertmanager:")
try:
with urllib.request.urlopen("http://127.0.0.1:9093/api/v2/alerts") as r:
alerts = json.loads(r.read())
print(f" Active alerts: {len(alerts)}")
for a in alerts[:5]:
lbl = a.get("labels", {}).get("alertname", "?")
summary = a.get("annotations", {}).get("summary", "")[:60]
print(f" - {lbl}: {summary}")
except Exception as e:
print(f" Error: {e}")
errors.append(f"Alertmanager: {e}")
# 6. Summary
print("\n" + "=" * 60)
if errors:
print(f"RESULT: {len(errors)} ISSUES FOUND")
for e in errors:
print(f" ! {e}")
sys.exit(1)
else:
print("RESULT: ALL CHECKS PASSED")
sys.exit(0)
if __name__ == "__main__":
main()