diff --git a/.circleci/config.yml b/.circleci/config.yml index e0a41310a..b0a369a35 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1039,6 +1039,48 @@ jobs: ls python -m pytest -vv tests/otel_tests -x --junitxml=test-results/junit.xml --durations=5 no_output_timeout: 120m + # Clean up first container + - run: + name: Stop and remove first container + command: | + docker stop my-app + docker rm my-app + + # Second Docker Container Run with Different Config + # NOTE: We intentionally pass a "bad" license here. We need to ensure proxy starts and serves request even with bad license + - run: + name: Run Second Docker container + command: | + docker run -d \ + -p 4000:4000 \ + -e DATABASE_URL=$PROXY_DATABASE_URL \ + -e REDIS_HOST=$REDIS_HOST \ + -e REDIS_PASSWORD=$REDIS_PASSWORD \ + -e REDIS_PORT=$REDIS_PORT \ + -e LITELLM_MASTER_KEY="sk-1234" \ + -e OPENAI_API_KEY=$OPENAI_API_KEY \ + -e LITELLM_LICENSE="bad-license" \ + --name my-app-3 \ + -v $(pwd)/litellm/proxy/example_config_yaml/enterprise_config.yaml:/app/config.yaml \ + my-app:latest \ + --config /app/config.yaml \ + --port 4000 \ + --detailed_debug + + - run: + name: Start outputting logs for second container + command: docker logs -f my-app-2 + background: true + + - run: + name: Wait for second app to be ready + command: dockerize -wait http://localhost:4000 -timeout 5m + + - run: + name: Run second round of tests + command: | + python -m pytest -vv tests/basic_proxy_startup_tests -x --junitxml=test-results/junit-2.xml --durations=5 + no_output_timeout: 120m # Store test results - store_test_results: diff --git a/litellm/proxy/common_utils/callback_utils.py b/litellm/proxy/common_utils/callback_utils.py index c3c19f551..40f66e90b 100644 --- a/litellm/proxy/common_utils/callback_utils.py +++ b/litellm/proxy/common_utils/callback_utils.py @@ -242,7 +242,9 @@ def initialize_callbacks_on_proxy( # noqa: PLR0915 if "prometheus" in value: if premium_user is not True: - raise Exception(CommonProxyErrors.not_premium_user.value) + verbose_proxy_logger.warning( + f"Prometheus metrics are only available for premium users. {CommonProxyErrors.not_premium_user.value}" + ) from litellm.proxy.proxy_server import app verbose_proxy_logger.debug("Starting Prometheus Metrics on /metrics") diff --git a/litellm/proxy/example_config_yaml/enterprise_config.yaml b/litellm/proxy/example_config_yaml/enterprise_config.yaml new file mode 100644 index 000000000..337e85177 --- /dev/null +++ b/litellm/proxy/example_config_yaml/enterprise_config.yaml @@ -0,0 +1,17 @@ +model_list: + - model_name: gpt-4 + litellm_params: + model: openai/fake + api_key: fake-key + api_base: https://exampleopenaiendpoint-production.up.railway.app/ + tags: ["teamA"] + model_info: + id: "team-a-model" + +litellm_settings: + cache: true + callbacks: ["prometheus"] + +router_settings: + enable_tag_filtering: True # 👈 Key Change + diff --git a/tests/basic_proxy_startup_tests/test_basic_proxy_startup.py b/tests/basic_proxy_startup_tests/test_basic_proxy_startup.py new file mode 100644 index 000000000..db09e38ea --- /dev/null +++ b/tests/basic_proxy_startup_tests/test_basic_proxy_startup.py @@ -0,0 +1,51 @@ +""" +This test ensures that the proxy starts and serves requests even with a bad license. + + +in ci/cd config.yml, we set the license to "bad-license" +""" + +import pytest +import aiohttp +from typing import Optional + + +@pytest.mark.asyncio +async def test_health_and_chat_completion(): + """ + Test health endpoints and chat completion: + 1. Check /health/readiness + 2. Check /health/liveness + 3. Make a chat completion call + """ + async with aiohttp.ClientSession() as session: + # Test readiness endpoint + async with session.get("http://0.0.0.0:4000/health/readiness") as response: + assert response.status == 200 + readiness_response = await response.json() + assert readiness_response["status"] == "connected" + + # Test liveness endpoint + async with session.get("http://0.0.0.0:4000/health/liveness") as response: + assert response.status == 200 + liveness_response = await response.json() + print("liveness_response", liveness_response) + + # Make a chat completion call + url = "http://0.0.0.0:4000/chat/completions" + headers = { + "Authorization": "Bearer sk-1234", + "Content-Type": "application/json", + } + data = { + "model": "gpt-4", + "messages": [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Hello!"}, + ], + } + + async with session.post(url, headers=headers, json=data) as response: + assert response.status == 200 + completion_response = await response.json() + assert "choices" in completion_response