Commit ab19628e authored by Kantz's avatar Kantz
Browse files

Experimentelles Docker update für anderen containerstatus

parent 93c0d6c0
from __future__ import annotations
import logging
from threading import Lock
from typing import Any, Dict
import httpx
import psycopg
from fastapi import APIRouter
from fastapi.responses import JSONResponse
import app.config as config
router = APIRouter()
logger = logging.getLogger(__name__)
_READINESS_LOCK = Lock()
_READINESS_STATE: Dict[str, Any] = {"status": "starting"}
def set_readiness_starting() -> None:
with _READINESS_LOCK:
_READINESS_STATE.clear()
_READINESS_STATE.update({"status": "starting"})
def set_readiness_ready(warmup: Dict[str, Any] | None = None) -> None:
with _READINESS_LOCK:
_READINESS_STATE.clear()
_READINESS_STATE.update({"status": "ready"})
if warmup is not None:
_READINESS_STATE["warmup"] = warmup
def set_readiness_failed(detail: str, *, checks: Dict[str, Any] | None = None) -> None:
with _READINESS_LOCK:
_READINESS_STATE.clear()
_READINESS_STATE.update({"status": "failed", "detail": detail})
if checks is not None:
_READINESS_STATE["checks"] = checks
def get_readiness_state() -> Dict[str, Any]:
with _READINESS_LOCK:
return dict(_READINESS_STATE)
def _check_ollama() -> dict:
base_url = config.get_ollama_settings().base_url.rstrip("/")
......@@ -81,6 +113,14 @@ def health() -> Dict[str, Any]:
return {"status": overall, "services": services}
@router.get("/api/health/ready")
def readiness() -> Dict[str, Any]:
state = get_readiness_state()
if state.get("status") == "ready":
return state
return {"status_code": 503, "content": state}
def run_startup_checks() -> Dict[str, Any]:
result = health()
status = result.get("status")
......
......@@ -13,12 +13,24 @@ logger = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(_app: FastAPI):
health.run_startup_checks()
health.set_readiness_starting()
startup_result = health.run_startup_checks()
if startup_result.get("status") != "ok":
health.set_readiness_failed(
"Startup health checks degraded",
checks=startup_result,
)
yield
return
try:
warmup_timing = embedding_provider.warmup_embedder()
logger.info("Embedding warmup finished: %s", warmup_timing)
except Exception:
logger.exception("Embedding warmup failed")
health.set_readiness_failed("Embedding warmup failed")
else:
health.set_readiness_ready(warmup=warmup_timing)
yield
......
import json
import unittest
from app.api import health
from fastapi.responses import JSONResponse
class HealthReadinessUnitTest(unittest.TestCase):
def tearDown(self) -> None:
health.set_readiness_starting()
def test_readiness_returns_503_while_starting(self) -> None:
health.set_readiness_starting()
response = health.readiness()
self.assertIsInstance(response, JSONResponse)
self.assertEqual(response.status_code, 503)
self.assertEqual(json.loads(response.body), {"status": "starting"})
def test_readiness_returns_200_when_ready(self) -> None:
warmup = {"total_warmup_ms": 123.45}
health.set_readiness_ready(warmup=warmup)
response = health.readiness()
self.assertEqual(response, {"status": "ready", "warmup": warmup})
def test_readiness_returns_503_when_failed(self) -> None:
checks = {"status": "degraded"}
health.set_readiness_failed("Embedding warmup failed", checks=checks)
response = health.readiness()
self.assertIsInstance(response, JSONResponse)
self.assertEqual(response.status_code, 503)
self.assertEqual(
json.loads(response.body),
{
"status": "failed",
"detail": "Embedding warmup failed",
"checks": checks,
},
)
if __name__ == "__main__":
unittest.main()
......@@ -7,6 +7,18 @@ services:
- ../backend/.env
expose:
- "8000"
healthcheck:
test:
[
"CMD",
"python",
"-c",
"import urllib.request; urllib.request.urlopen('http://localhost:8000/api/health/ready')",
]
interval: 5s
timeout: 3s
retries: 10
start_period: 120s
restart: unless-stopped
volumes:
- ./logs:/app/logs
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment