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:
yuneng-jiang 2026-05-13 13:18:54 -07:00
parent 2b189be7f1
commit be37cf782c
2 changed files with 25 additions and 12 deletions

View file

@ -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(

View file

@ -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():