126 lines
3.9 KiB
Python

from __future__ import annotations
import json
import time
from typing import Any
from urllib.error import URLError
from urllib.parse import urlencode
from urllib.request import urlopen
from flask import jsonify
from .. import settings
_LAB_STATUS_CACHE: dict[str, Any] = {"ts": 0.0, "value": None}
def _vm_query(expr: str) -> float | None:
url = f"{settings.VM_BASE_URL}/api/v1/query?{urlencode({'query': expr})}"
with urlopen(url, timeout=settings.VM_QUERY_TIMEOUT_SEC) as resp:
payload = json.loads(resp.read().decode("utf-8"))
if payload.get("status") != "success":
return None
result = (payload.get("data") or {}).get("result") or []
if not result:
return None
values: list[float] = []
for item in result:
try:
values.append(float(item["value"][1]))
except (KeyError, IndexError, TypeError, ValueError):
continue
if not values:
return None
return max(values)
def _http_ok(url: str, expect_substring: str | None = None) -> bool:
try:
with urlopen(url, timeout=settings.HTTP_CHECK_TIMEOUT_SEC) as resp:
if getattr(resp, "status", 200) != 200:
return False
if expect_substring:
chunk = resp.read(4096).decode("utf-8", errors="ignore")
return expect_substring in chunk
return True
except (URLError, TimeoutError, ValueError):
return False
def register(app) -> None:
@app.route("/api/lab/status")
def lab_status() -> Any:
now = time.time()
cached = _LAB_STATUS_CACHE.get("value")
if cached and (now - float(_LAB_STATUS_CACHE.get("ts", 0.0)) < settings.LAB_STATUS_CACHE_SEC):
return jsonify(cached)
t_total = time.perf_counter()
timings_ms: dict[str, int] = {}
connected = False
atlas_up = False
atlas_known = False
atlas_source = "unknown"
oceanus_up = False
oceanus_known = False
oceanus_source = "unknown"
# Atlas
try:
t_probe = time.perf_counter()
atlas_grafana_ok = _http_ok(settings.GRAFANA_HEALTH_URL, expect_substring="ok")
timings_ms["grafana"] = int((time.perf_counter() - t_probe) * 1000)
if atlas_grafana_ok:
connected = True
atlas_up = True
atlas_known = True
atlas_source = "grafana"
except Exception:
pass
if not atlas_known:
try:
t_probe = time.perf_counter()
value = _vm_query("up")
timings_ms["victoria_metrics"] = int((time.perf_counter() - t_probe) * 1000)
if value is not None:
connected = True
atlas_known = True
atlas_up = value > 0
atlas_source = "victoria-metrics"
except Exception:
pass
# Oceanus (node-exporter direct probe)
try:
t_probe = time.perf_counter()
if _http_ok(settings.OCEANUS_NODE_EXPORTER_URL):
timings_ms["oceanus_node_exporter"] = int((time.perf_counter() - t_probe) * 1000)
connected = True
oceanus_known = True
oceanus_up = True
oceanus_source = "node-exporter"
except Exception:
pass
timings_ms["total"] = int((time.perf_counter() - t_total) * 1000)
payload = {
"connected": connected,
"atlas": {"up": atlas_up, "known": atlas_known, "source": atlas_source},
"oceanus": {"up": oceanus_up, "known": oceanus_known, "source": oceanus_source},
"checked_at": int(now),
"timings_ms": timings_ms,
}
_LAB_STATUS_CACHE["ts"] = now
_LAB_STATUS_CACHE["value"] = payload
return jsonify(payload)