feat(grafana-metric-gateway): slice 1 — backend gateway transport
Route all prometheus widget queries through Grafana /api/ds/query instead of
direct Prom HTTP. PrometheusConfig: drop base_url, add grafana_url +
datasource_uid; secret grafana_api_key (required). PrometheusWidgetSource →
MetricSource with _gateway_query POST method. normalize_grafana_frames
recovered from 65bae95 + shared _dedup_label helper. Gateway-path status
check. Startup old-config validation. CHANGELOG migration note. All adapter
tests rewritten for POST /api/ds/query + Grafana frames mock. Backend: 331
pytest pass, ruff clean. Frontend: build green (unchanged in S1).
This commit is contained in:
@@ -177,23 +177,52 @@ def get_prometheus_status(
|
||||
service_id: str | None = None,
|
||||
store: SettingsStore = Depends(get_settings_store),
|
||||
) -> dict[str, Any]:
|
||||
"""Probe a Prometheus service instance's health and build info."""
|
||||
"""Probe a Prometheus service's health via the Grafana gateway path (GM-110).
|
||||
|
||||
Issues a trivial ``up`` query through Grafana ``/api/ds/query``. Success
|
||||
validates the full path: Grafana is reachable, the API key works, and the
|
||||
Prometheus datasource responds.
|
||||
"""
|
||||
service = resolve_service_record(store, "prometheus", service_id)
|
||||
if service is None:
|
||||
return _status_response(None, error="no_service_configured")
|
||||
base = _base_url(service)
|
||||
timeout = _timeout(service, 10)
|
||||
headers = _auth_headers(service)
|
||||
grafana_url = str(service.config.get("grafana_url") or "").rstrip("/")
|
||||
api_key = str(service.secrets.get("grafana_api_key") or "")
|
||||
datasource_uid = str(service.config.get("datasource_uid") or "prometheus")
|
||||
timeout = int(service.config.get("timeout_seconds") or 10)
|
||||
if not grafana_url or not api_key:
|
||||
return _status_response(service, error="gateway_not_configured")
|
||||
body = {
|
||||
"queries": [
|
||||
{
|
||||
"datasource": {"uid": datasource_uid, "type": "prometheus"},
|
||||
"expr": "up",
|
||||
"format": "time_series",
|
||||
"intervalMs": 15_000,
|
||||
"maxDataPoints": 1,
|
||||
"refId": "A",
|
||||
}
|
||||
],
|
||||
"from": "now-1m",
|
||||
"to": "now",
|
||||
}
|
||||
try:
|
||||
health = requests.get(f"{base}/-/healthy", headers=headers, timeout=timeout)
|
||||
health.raise_for_status()
|
||||
build_info = requests.get(f"{base}/api/v1/status/buildinfo", headers=headers, timeout=timeout)
|
||||
build_info.raise_for_status()
|
||||
version = build_info.json().get("data", {}).get("version", "")
|
||||
except Exception:
|
||||
logger.exception("Failed to fetch Prometheus status")
|
||||
resp = requests.post(
|
||||
f"{grafana_url}/api/ds/query",
|
||||
json=body,
|
||||
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
|
||||
timeout=timeout,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
except requests.HTTPError as exc:
|
||||
status_code = exc.response.status_code if exc.response else 0
|
||||
if status_code in (401, 403):
|
||||
return _status_response(service, error="auth_failed")
|
||||
return _status_response(service, error="gateway_error")
|
||||
except requests.RequestException:
|
||||
logger.exception("Failed to fetch Prometheus status via gateway")
|
||||
return _status_response(service, error="prometheus_unreachable")
|
||||
return _status_response(service, version=version)
|
||||
return _status_response(service, version="ok")
|
||||
|
||||
|
||||
@router.post("/alertmanager-webhook")
|
||||
|
||||
Reference in New Issue
Block a user