mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-09 22:31:41 +00:00
fix(proxy): expose db status on public /health/readiness
Backport of #27866 onto litellm_1.84.0rc2. External readiness probes consumed the legacy detailed payload's `db` field to drive alerting and pod-rotation decisions. Stripping the body to {"status": "healthy"} broke those probes silently — the HTTP code still flipped to 503, but probes checking body.db == "connected" treated the response as healthy. Add `db` back to the unauthenticated payload. The rest of the diagnostic fields (litellm_version, callbacks, cache, log_level) stay behind /health/readiness/details so the recon-leak gate from #26912 holds. Values match the legacy contract: "connected", "disconnected", "Not connected". The 503-on-DB-disconnect behavior from LIT-2607 is preserved.
This commit is contained in:
parent
2b189be7f1
commit
be37cf782c
2 changed files with 25 additions and 12 deletions
|
|
@ -1530,15 +1530,21 @@ def _allow_public_health_readiness_details() -> bool:
|
|||
return general_settings.get("allow_public_health_readiness_details") is True
|
||||
|
||||
|
||||
async def _set_public_readiness_status(response: Response) -> None:
|
||||
async def _resolve_public_readiness_db(response: Response) -> str:
|
||||
"""
|
||||
Return the db status string for the public probe and flip the response to
|
||||
503 when a configured DB is unreachable. Mirrors the legacy values:
|
||||
"Not connected" (no DB configured), "connected", "disconnected".
|
||||
"""
|
||||
from litellm.proxy.proxy_server import prisma_client
|
||||
|
||||
if prisma_client is None:
|
||||
return
|
||||
return "Not connected"
|
||||
|
||||
db_health_status = await _db_health_readiness_check()
|
||||
if db_health_status["status"] != "connected":
|
||||
response.status_code = status.HTTP_503_SERVICE_UNAVAILABLE
|
||||
return db_health_status["status"]
|
||||
|
||||
|
||||
@router.get(
|
||||
|
|
@ -1547,15 +1553,17 @@ async def _set_public_readiness_status(response: Response) -> None:
|
|||
)
|
||||
async def health_readiness(response: Response):
|
||||
"""
|
||||
Public readiness probe. Keep this low-detail for unauthenticated load
|
||||
balancers by default. Admins can opt into the legacy detailed public
|
||||
payload with general_settings.allow_public_health_readiness_details.
|
||||
Public readiness probe. Returns a low-detail payload safe to expose to
|
||||
unauthenticated load balancers — `status` plus `db` so orchestrators and
|
||||
external probes can distinguish "healthy" from "DB unreachable" without a
|
||||
credential. Admins can opt into the legacy detailed payload with
|
||||
general_settings.allow_public_health_readiness_details.
|
||||
"""
|
||||
if _allow_public_health_readiness_details():
|
||||
return await _get_health_readiness_details(response=response)
|
||||
|
||||
await _set_public_readiness_status(response=response)
|
||||
return {"status": "healthy"}
|
||||
db_status = await _resolve_public_readiness_db(response=response)
|
||||
return {"status": "healthy", "db": db_status}
|
||||
|
||||
|
||||
@router.get(
|
||||
|
|
|
|||
|
|
@ -614,9 +614,14 @@ def test_health_readiness(proxy_client):
|
|||
duration_ms < 500
|
||||
), f"Health check took {duration_ms:.2f}ms, expected < 500ms for readiness endpoint"
|
||||
|
||||
# Assert response contains only low-detail public probe fields
|
||||
# Assert response contains only low-detail public probe fields. `db` is
|
||||
# included so unauthenticated probes can distinguish "DB unreachable"
|
||||
# from a fully-healthy worker; its value depends on whether the test env
|
||||
# exposes DATABASE_URL.
|
||||
response_data = response.json()
|
||||
assert response_data == {"status": "healthy"}
|
||||
assert set(response_data.keys()) == {"status", "db"}
|
||||
assert response_data["status"] == "healthy"
|
||||
assert response_data["db"] in {"connected", "disconnected", "Not connected"}
|
||||
print(f"Response time: {duration_ms:.2f}ms")
|
||||
|
||||
|
||||
|
|
@ -1520,7 +1525,7 @@ async def test_health_readiness_returns_503_when_db_disconnected():
|
|||
result = await health_readiness(response=response)
|
||||
|
||||
assert response.status_code == 503
|
||||
assert result == {"status": "healthy"}
|
||||
assert result == {"status": "healthy", "db": "disconnected"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -1543,7 +1548,7 @@ async def test_health_readiness_returns_200_when_db_connected():
|
|||
result = await health_readiness(response=response)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert result == {"status": "healthy"}
|
||||
assert result == {"status": "healthy", "db": "connected"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -1562,7 +1567,7 @@ async def test_health_readiness_returns_200_when_no_db_configured():
|
|||
result = await health_readiness(response=response)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert result == {"status": "healthy"}
|
||||
assert result == {"status": "healthy", "db": "Not connected"}
|
||||
|
||||
|
||||
def test_clean_endpoint_data_strips_credentials_keeps_routing_fields():
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue