m9_smoke.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. #!/usr/bin/env python3
  2. """
  3. M9 smoke test — observability layer
  4. Verifies:
  5. 1. Prometheus is scraping all services (including new M9 targets)
  6. 2. Key metrics are accessible in Prometheus
  7. 3. Grafana is reachable with provisioning
  8. """
  9. import urllib.request
  10. import urllib.parse
  11. import json
  12. import sys
  13. PROM = "http://localhost:9090"
  14. GRAFANA = "http://localhost:3001"
  15. def curl_json(url):
  16. try:
  17. with urllib.request.urlopen(url, timeout=10) as r:
  18. return json.loads(r.read())
  19. except Exception as e:
  20. print(f" ⚠️ fetch error: {e}", file=sys.stderr)
  21. return None
  22. def prom_query(expr):
  23. url = f"{PROM}/api/v1/query?query={urllib.parse.quote(expr, safe='')}"
  24. d = curl_json(url)
  25. if d is None:
  26. return None
  27. return d.get("data", {}).get("result", [])
  28. def pass_(msg):
  29. print(f" ✅ {msg}")
  30. def warn_(msg):
  31. print(f" ⚠️ {msg}")
  32. def fail_(msg):
  33. print(f" ❌ {msg}")
  34. sys.exit(1)
  35. # ── Step 1 ────────────────────────────────────────────────────────────────
  36. print("Step 1 — Prometheus is up and scraping all services")
  37. for svc in ["ingestd", "routerd", "deliverd-fcm", "deliverd-telegram", "admind", "archiverd", "prometheus"]:
  38. r = prom_query(f'up{{job="{svc}"}}')
  39. if r:
  40. pass_(f"Prometheus scraping {svc}")
  41. else:
  42. fail_(f"Prometheus NOT scraping {svc} (check prometheus.yml)")
  43. # ── Step 2 ────────────────────────────────────────────────────────────────
  44. print("\nStep 2 — ingestd metrics are present (source of ingest-tier metrics)")
  45. series = prom_query("ba_ingestd_alerts_received_total")
  46. cnt = len(series) if series else 0
  47. if cnt > 0:
  48. pass_(f"ba_ingestd_alerts_received_total: {cnt} series")
  49. else:
  50. warn_("ba_ingestd_alerts_received_total: 0 series (no recent traffic — might be OK)")
  51. # ── Step 3 ────────────────────────────────────────────────────────────────
  52. print("\nStep 3 — routerd recipient expansion latency metric")
  53. series = prom_query("ba_routerd_recipient_expansion_seconds_count")
  54. cnt = len(series) if series else 0
  55. if cnt > 0:
  56. pass_(f"ba_routerd_recipient_expansion_seconds: {cnt} series (M9 routerd deployed)")
  57. else:
  58. fail_("ba_routerd_recipient_expansion_seconds has no series — routerd missing M9 metric")
  59. # ── Step 4 ────────────────────────────────────────────────────────────────
  60. print("\nStep 4 — deliverd delivery attempt metrics (M9)")
  61. series = prom_query("ba_deliverd_delivery_attempts_total")
  62. cnt = len(series) if series else 0
  63. if cnt > 0:
  64. pass_(f"ba_deliverd_delivery_attempts_total: {cnt} series (M9 deliverd deployed)")
  65. else:
  66. pass_("ba_deliverd_delivery_attempts_total: 0 series (metric exists, no delivery traffic yet)")
  67. # ── Step 5 ────────────────────────────────────────────────────────────────
  68. print("\nStep 5 — Circuit breaker gauge (M9 ingestd)")
  69. series = prom_query("ba_ingestd_circuit_breaker_state")
  70. cnt = len(series) if series else 0
  71. if cnt > 0:
  72. pass_(f"ba_ingestd_circuit_breaker_state: {cnt} series (M9 ingestd deployed)")
  73. else:
  74. warn_("ba_ingestd_circuit_breaker_state: 0 series (ingestd still on pre-M9 binary)")
  75. warn_(" Rebuild: docker compose build --no-cache ingestd")
  76. warn_(" Redeploy: docker compose up -d --no-deps ingestd")
  77. # ── Step 6 ────────────────────────────────────────────────────────────────
  78. print("\nStep 6 — Grafana reachable with dashboards provisioned")
  79. try:
  80. with urllib.request.urlopen(f"{GRAFANA}/api/health", timeout=10) as r:
  81. gf_status = r.status
  82. except Exception as e:
  83. gf_status = 0
  84. if gf_status == 200:
  85. pass_(f"Grafana HTTP {gf_status} (admin/admin at http://localhost:3001)")
  86. try:
  87. import base64
  88. req = urllib.request.Request(f"{GRAFANA}/api/search")
  89. req.add_header("Authorization", "Basic " + base64.b64encode(b"admin:admin").decode())
  90. with urllib.request.urlopen(req, timeout=10) as r2:
  91. results = json.loads(r2.read())
  92. # Count items with 'broadannounce' in uid or title (folder or dashboard)
  93. cnt = len([d for d in results
  94. if 'broadannounce' in d.get('uid', '').lower()
  95. or 'broadannounce' in d.get('title', '').lower()])
  96. if cnt > 0:
  97. pass_("BroadAnnounce Overview dashboard found in Grafana")
  98. else:
  99. warn_("BroadAnnounce dashboard not found in Grafana (may need manual import)")
  100. except Exception as e:
  101. warn_(f"Could not check dashboards: {e}")
  102. else:
  103. fail_(f"Grafana returned HTTP {gf_status} — check docker compose logs grafana")
  104. # ── Step 7 ────────────────────────────────────────────────────────────────
  105. print("\nStep 7 — Prometheus self-monitoring")
  106. r = prom_query('up{job="prometheus"}')
  107. if r:
  108. pass_("Prometheus scraping itself")
  109. else:
  110. fail_("Prometheus not scraping itself")
  111. print("\n🎉 M9 smoke: all checks complete.")