126 lines
3.9 KiB
Python
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)
|