#!/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()