109 lines
3.4 KiB
Python
109 lines
3.4 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Any
|
|
|
|
import httpx
|
|
|
|
from ..settings import settings
|
|
from ..utils.logging import get_logger
|
|
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
_WATCH_PATH = "/internal/sentinel/watch"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class MetisSentinelWatchSummary:
|
|
status: str
|
|
watch_url: str
|
|
detail: str = ""
|
|
result: dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
def _watch_url() -> str:
|
|
if settings.metis_watch_url:
|
|
return settings.metis_watch_url
|
|
if settings.metis_base_url:
|
|
return f"{settings.metis_base_url}{_WATCH_PATH}"
|
|
return ""
|
|
|
|
|
|
def _normalize_payload(payload: Any) -> dict[str, Any]:
|
|
if isinstance(payload, dict):
|
|
return payload
|
|
if payload is None:
|
|
return {}
|
|
return {"result": payload}
|
|
|
|
|
|
class MetisService:
|
|
"""Trigger Metis sentinel watch runs and normalize their response."""
|
|
|
|
def ready(self) -> bool:
|
|
return bool(_watch_url())
|
|
|
|
def _finish(self, status: str, watch_url: str, detail: str = "", result: dict[str, Any] | None = None) -> MetisSentinelWatchSummary:
|
|
summary = MetisSentinelWatchSummary(
|
|
status=status,
|
|
watch_url=watch_url,
|
|
detail=detail,
|
|
result=result or {},
|
|
)
|
|
logger.info(
|
|
"metis sentinel watch finished",
|
|
extra={
|
|
"event": "metis_sentinel_watch",
|
|
"status": summary.status,
|
|
"watch_url": summary.watch_url,
|
|
"detail": summary.detail,
|
|
},
|
|
)
|
|
return summary
|
|
|
|
def watch_sentinel(self) -> MetisSentinelWatchSummary:
|
|
watch_url = _watch_url()
|
|
if not watch_url:
|
|
return self._finish("skipped", "", "metis watch url not configured")
|
|
|
|
try:
|
|
with httpx.Client(timeout=settings.metis_timeout_sec, follow_redirects=True) as client:
|
|
response = client.post(watch_url)
|
|
response.raise_for_status()
|
|
try:
|
|
payload = response.json()
|
|
except Exception:
|
|
payload = {}
|
|
except httpx.HTTPStatusError as exc:
|
|
response = exc.response
|
|
detail = f"metis watch failed with HTTP {response.status_code}"
|
|
try:
|
|
payload = response.json()
|
|
except Exception:
|
|
payload = {}
|
|
payload = _normalize_payload(payload)
|
|
if isinstance(payload.get("detail"), str) and payload["detail"].strip():
|
|
detail = payload["detail"].strip()
|
|
return self._finish("error", watch_url, detail, payload)
|
|
except Exception as exc: # noqa: BLE001
|
|
return self._finish("error", watch_url, str(exc).strip() or "metis watch failed")
|
|
|
|
payload = _normalize_payload(payload)
|
|
status = payload.get("status") if isinstance(payload.get("status"), str) else "ok"
|
|
detail = ""
|
|
if isinstance(payload.get("detail"), str):
|
|
detail = payload["detail"].strip()
|
|
elif isinstance(payload.get("message"), str):
|
|
detail = payload["message"].strip()
|
|
elif status != "ok":
|
|
detail = f"metis watch returned {status}"
|
|
|
|
if status not in {"ok", "skipped", "error"}:
|
|
status = "ok"
|
|
|
|
return self._finish(status, watch_url, detail, payload)
|
|
|
|
|
|
metis = MetisService()
|