Files
litellm/tests/test_litellm/proxy/db/test_db_url_settings.py
T
014cb8fa9d feat: add componentized proxy deployment with gateway, backend, ui, and migrations (#27557)
Split the monolithic LiteLLM proxy into independently scalable Kubernetes components to allow separate horizontal scaling of the LLM data plane and management API surfaces

- Add DatabaseURLSettings pydantic-settings model that assembles DATABASE_URL (and optional DATABASE_URL_READ_REPLICA) from discrete DATABASE_* env vars before Prisma initializes, supporting both IAM token auth (minting short-lived RDS tokens) and password auth; replaces the CLI-only path that componentized entrypoints bypass
- Add gateway component (port 4000) that trims the proxy route table to the LLM data-plane surface (chat, embeddings, completions, audio, realtime, provider passthroughs, health/metrics) via an allowlist applied inside the lifespan context so plugin-registered routes are captured
- Add backend component (port 4001) that exposes the management/admin surface (keys, users, teams, orgs, spend analytics, model management, SSO, audit logs) with a complementary allowlist
- Add ui component — Next.js static export served by nginx (port 3000) with RSC payload routing, asset prefix aliasing, and SPA fallback for dashboard routes
- Add migrations component with dedicated Dockerfile that runs prisma migrate deploy via a Helm pre-install/pre-upgrade Job, eliminating per-pod schema contention on the Prisma advisory lock
- Add Helm chart (helm/litellm) with separate Deployments, Services, HPAs, and ConfigMap for each component; shared _helpers.tpl emits DATABASE_*, IAM_TOKEN_DB_AUTH, REDIS_*, and DISABLE_SCHEMA_UPDATE env vars from chart values; ingress template routes traffic to the correct component by path prefix
- Add comprehensive tests for DatabaseURLSettings covering IAM auth, password auth, read replica fallbacks, operator-pinned URL preservation, and percent-encoding; add coverage test asserting gateway + backend allowlist union equals the full proxy route set
- Add pydantic-settings>=2.14.1 as a proxy extra dependency and update liccheck allowlist

Co-authored-by: Yassin Kortam <yassinkortam@g.ucla.edu>
2026-05-16 09:25:17 -07:00

272 lines
9.4 KiB
Python

"""Tests for ``DatabaseURLSettings``.
The model assembles ``DATABASE_URL`` (and optionally
``DATABASE_URL_READ_REPLICA``) from the discrete ``DATABASE_*`` env vars
emitted by the ``helm/litellm`` chart, before Prisma initializes. It covers
both IAM auth (mint a short-lived token) and password auth, for both the
writer and the read replica.
The reader URL is opt-in via ``DATABASE_HOST_READ_REPLICA`` and must not
clobber a pre-existing ``DATABASE_URL_READ_REPLICA``. A pre-existing
``DATABASE_URL`` (password auth) is likewise left untouched.
"""
import os
from unittest.mock import patch
import pytest
from litellm.proxy.db.db_url_settings import DatabaseURLSettings
def _apply() -> bool:
"""Run the production call path: load from env, write to env."""
return DatabaseURLSettings.from_env().apply_to_env()
@pytest.fixture(autouse=True)
def _scrub_db_env(monkeypatch):
"""Remove every env var the model reads so tests start from a clean slate."""
for var in (
"IAM_TOKEN_DB_AUTH",
"DATABASE_URL",
"DATABASE_URL_READ_REPLICA",
"DATABASE_HOST",
"DATABASE_PORT",
"DATABASE_USER",
"DATABASE_USERNAME",
"DATABASE_NAME",
"DATABASE_SCHEMA",
"DATABASE_PASSWORD",
"DATABASE_HOST_READ_REPLICA",
"DATABASE_PORT_READ_REPLICA",
"DATABASE_USER_READ_REPLICA",
"DATABASE_USERNAME_READ_REPLICA",
"DATABASE_NAME_READ_REPLICA",
"DATABASE_SCHEMA_READ_REPLICA",
"DATABASE_PASSWORD_READ_REPLICA",
):
monkeypatch.delenv(var, raising=False)
def _stub_iam_token(token: str = "FAKE_TOKEN"):
"""Patch the AWS-touching token mint so tests don't need boto3 / network."""
return patch(
"litellm.proxy.auth.rds_iam_token.generate_iam_auth_token",
return_value=token,
)
# ---------------------------------------------------------------------------
# IAM auth
# ---------------------------------------------------------------------------
def test_returns_false_when_nothing_configured(monkeypatch):
"""No env mutation, no error — just a False return."""
assert _apply() is False
assert "DATABASE_URL" not in os.environ
def test_assembles_writer_url_when_iam_enabled(monkeypatch):
monkeypatch.setenv("IAM_TOKEN_DB_AUTH", "true")
monkeypatch.setenv("DATABASE_HOST", "writer.example.com")
monkeypatch.setenv("DATABASE_USER", "litellm")
monkeypatch.setenv("DATABASE_NAME", "litellm_db")
with _stub_iam_token("WRITER_TOKEN"):
assert _apply() is True
assert (
os.environ["DATABASE_URL"]
== "postgresql://litellm:WRITER_TOKEN@writer.example.com:5432/litellm_db"
)
# Reader was never configured, so it must not have been set.
assert "DATABASE_URL_READ_REPLICA" not in os.environ
def test_missing_writer_envs_raises(monkeypatch):
monkeypatch.setenv("IAM_TOKEN_DB_AUTH", "true")
# DATABASE_HOST intentionally unset.
monkeypatch.setenv("DATABASE_USER", "litellm")
monkeypatch.setenv("DATABASE_NAME", "litellm_db")
with pytest.raises(RuntimeError, match="DATABASE_HOST"):
_apply()
def test_reader_url_assembled_when_host_set_and_url_unset(monkeypatch):
monkeypatch.setenv("IAM_TOKEN_DB_AUTH", "true")
monkeypatch.setenv("DATABASE_HOST", "writer.example.com")
monkeypatch.setenv("DATABASE_USER", "litellm")
monkeypatch.setenv("DATABASE_NAME", "litellm_db")
monkeypatch.setenv("DATABASE_HOST_READ_REPLICA", "reader.example.com")
with _stub_iam_token("READER_TOKEN"):
_apply()
assert (
os.environ["DATABASE_URL_READ_REPLICA"]
== "postgresql://litellm:READER_TOKEN@reader.example.com:5432/litellm_db"
)
def test_reader_url_not_clobbered_when_already_set(monkeypatch):
"""If the operator pinned DATABASE_URL_READ_REPLICA (e.g. a non-IAM
reader), the model must leave it untouched even though
DATABASE_HOST_READ_REPLICA is also set."""
monkeypatch.setenv("IAM_TOKEN_DB_AUTH", "true")
monkeypatch.setenv("DATABASE_HOST", "writer.example.com")
monkeypatch.setenv("DATABASE_USER", "litellm")
monkeypatch.setenv("DATABASE_NAME", "litellm_db")
monkeypatch.setenv("DATABASE_HOST_READ_REPLICA", "reader.example.com")
monkeypatch.setenv(
"DATABASE_URL_READ_REPLICA",
"postgresql://app:secret@reader.example.com:5432/litellm_db",
)
with _stub_iam_token("READER_TOKEN"):
_apply()
assert (
os.environ["DATABASE_URL_READ_REPLICA"]
== "postgresql://app:secret@reader.example.com:5432/litellm_db"
)
def test_reader_url_skipped_when_host_unset(monkeypatch):
monkeypatch.setenv("IAM_TOKEN_DB_AUTH", "true")
monkeypatch.setenv("DATABASE_HOST", "writer.example.com")
monkeypatch.setenv("DATABASE_USER", "litellm")
monkeypatch.setenv("DATABASE_NAME", "litellm_db")
with _stub_iam_token("WRITER_TOKEN"):
_apply()
assert "DATABASE_URL_READ_REPLICA" not in os.environ
def test_reader_field_fallbacks_default_to_writer_values(monkeypatch):
"""When *_READ_REPLICA fields are unset (other than host), they fall
back to the writer's user / name / schema."""
monkeypatch.setenv("IAM_TOKEN_DB_AUTH", "true")
monkeypatch.setenv("DATABASE_HOST", "writer.example.com")
monkeypatch.setenv("DATABASE_USER", "litellm")
monkeypatch.setenv("DATABASE_NAME", "litellm_db")
monkeypatch.setenv("DATABASE_SCHEMA", "public")
monkeypatch.setenv("DATABASE_HOST_READ_REPLICA", "reader.example.com")
with _stub_iam_token("READER_TOKEN"):
_apply()
assert (
os.environ["DATABASE_URL_READ_REPLICA"]
== "postgresql://litellm:READER_TOKEN@reader.example.com:5432/litellm_db?schema=public"
)
# ---------------------------------------------------------------------------
# Password auth
# ---------------------------------------------------------------------------
def test_assembles_writer_url_from_password(monkeypatch):
monkeypatch.setenv("DATABASE_HOST", "writer.example.com")
monkeypatch.setenv("DATABASE_USER", "litellm")
monkeypatch.setenv("DATABASE_NAME", "litellm_db")
monkeypatch.setenv("DATABASE_PASSWORD", "s3cr3t")
assert _apply() is True
assert (
os.environ["DATABASE_URL"]
== "postgresql://litellm:s3cr3t@writer.example.com:5432/litellm_db"
)
def test_writer_password_is_percent_encoded(monkeypatch):
monkeypatch.setenv("DATABASE_HOST", "writer.example.com")
monkeypatch.setenv("DATABASE_USER", "litellm")
monkeypatch.setenv("DATABASE_NAME", "litellm_db")
monkeypatch.setenv("DATABASE_PASSWORD", "p@ss/w:rd")
assert _apply() is True
assert (
os.environ["DATABASE_URL"]
== "postgresql://litellm:p%40ss%2Fw%3Ard@writer.example.com:5432/litellm_db"
)
def test_writer_url_not_clobbered_when_already_set(monkeypatch):
"""An operator-pinned DATABASE_URL (e.g. helm's $(VAR) assembly) always
wins over the discrete fields."""
monkeypatch.setenv(
"DATABASE_URL", "postgresql://pinned:url@db.example.com:5432/litellm_db"
)
monkeypatch.setenv("DATABASE_HOST", "writer.example.com")
monkeypatch.setenv("DATABASE_USER", "litellm")
monkeypatch.setenv("DATABASE_NAME", "litellm_db")
monkeypatch.setenv("DATABASE_PASSWORD", "s3cr3t")
assert _apply() is False
assert (
os.environ["DATABASE_URL"]
== "postgresql://pinned:url@db.example.com:5432/litellm_db"
)
def test_writer_url_passwordless(monkeypatch):
monkeypatch.setenv("DATABASE_HOST", "writer.example.com")
monkeypatch.setenv("DATABASE_USER", "litellm")
monkeypatch.setenv("DATABASE_NAME", "litellm_db")
assert _apply() is True
assert (
os.environ["DATABASE_URL"]
== "postgresql://litellm@writer.example.com:5432/litellm_db"
)
def test_database_username_alias(monkeypatch):
"""DATABASE_USERNAME is accepted as an alias for DATABASE_USER (parity
with construct_database_url_from_env_vars)."""
monkeypatch.setenv("DATABASE_HOST", "writer.example.com")
monkeypatch.setenv("DATABASE_USERNAME", "litellm")
monkeypatch.setenv("DATABASE_NAME", "litellm_db")
monkeypatch.setenv("DATABASE_PASSWORD", "s3cr3t")
assert _apply() is True
assert (
os.environ["DATABASE_URL"]
== "postgresql://litellm:s3cr3t@writer.example.com:5432/litellm_db"
)
def test_password_reader_falls_back_to_writer_password(monkeypatch):
monkeypatch.setenv("DATABASE_HOST", "writer.example.com")
monkeypatch.setenv("DATABASE_USER", "litellm")
monkeypatch.setenv("DATABASE_NAME", "litellm_db")
monkeypatch.setenv("DATABASE_PASSWORD", "s3cr3t")
monkeypatch.setenv("DATABASE_HOST_READ_REPLICA", "reader.example.com")
assert _apply() is True
assert (
os.environ["DATABASE_URL_READ_REPLICA"]
== "postgresql://litellm:s3cr3t@reader.example.com:5432/litellm_db"
)
def test_password_reader_uses_own_credentials(monkeypatch):
monkeypatch.setenv("DATABASE_HOST", "writer.example.com")
monkeypatch.setenv("DATABASE_USER", "litellm")
monkeypatch.setenv("DATABASE_NAME", "litellm_db")
monkeypatch.setenv("DATABASE_PASSWORD", "s3cr3t")
monkeypatch.setenv("DATABASE_HOST_READ_REPLICA", "reader.example.com")
monkeypatch.setenv("DATABASE_USER_READ_REPLICA", "litellm_ro")
monkeypatch.setenv("DATABASE_PASSWORD_READ_REPLICA", "ro_pw")
assert _apply() is True
assert (
os.environ["DATABASE_URL_READ_REPLICA"]
== "postgresql://litellm_ro:ro_pw@reader.example.com:5432/litellm_db"
)