Skip to content

Commit d4107df

Browse files
authored
[Bug Fix] Ensure /redis/info works on GCP Redis (#11732)
* fix cache_redis_info * mock_redis_client_list_restricted
1 parent 4100d1f commit d4107df

File tree

2 files changed

+104
-12
lines changed

2 files changed

+104
-12
lines changed

litellm/proxy/caching_routes.py

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,23 @@ async def cache_delete(request: Request):
159159
)
160160

161161

162+
def _get_redis_client_info(cache_instance) -> tuple[list, int]:
163+
"""
164+
Helper function to safely get Redis client list information.
165+
166+
Returns:
167+
tuple: (client_list, num_clients) where num_clients is -1 if CLIENT LIST is unavailable
168+
"""
169+
try:
170+
client_list = cache_instance.client_list()
171+
return client_list, len(client_list)
172+
except Exception as e:
173+
verbose_proxy_logger.warning(
174+
f"CLIENT LIST command failed (likely restricted on managed Redis): {str(e)}"
175+
)
176+
return ["CLIENT LIST command not available on this Redis instance"], -1
177+
178+
162179
@router.get(
163180
"/redis/info",
164181
dependencies=[Depends(user_api_key_auth)],
@@ -172,22 +189,27 @@ async def cache_redis_info():
172189
raise HTTPException(
173190
status_code=503, detail="Cache not initialized. litellm.cache is None"
174191
)
175-
if litellm.cache.type == "redis" and isinstance(
176-
litellm.cache.cache, RedisCache
192+
193+
if not (
194+
litellm.cache.type == "redis"
195+
and isinstance(litellm.cache.cache, RedisCache)
177196
):
178-
client_list = litellm.cache.cache.client_list()
179-
redis_info = litellm.cache.cache.info()
180-
num_clients = len(client_list)
181-
return {
182-
"num_clients": num_clients,
183-
"clients": client_list,
184-
"info": redis_info,
185-
}
186-
else:
187197
raise HTTPException(
188198
status_code=500,
189-
detail=f"Cache type {litellm.cache.type} does not support flushing",
199+
detail=f"Cache type {litellm.cache.type} does not support redis info",
190200
)
201+
202+
# Get client information (handles CLIENT LIST restrictions gracefully)
203+
client_list, num_clients = _get_redis_client_info(litellm.cache.cache)
204+
205+
# Get Redis server information
206+
redis_info = litellm.cache.cache.info()
207+
208+
return {
209+
"num_clients": num_clients,
210+
"clients": client_list,
211+
"info": redis_info,
212+
}
191213
except Exception as e:
192214
raise HTTPException(
193215
status_code=503,

tests/test_litellm/proxy/test_caching_routes.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,73 @@ def test_cache_ping_with_redis_version_float(mock_redis_success):
202202
cache_params = data["health_check_cache_params"]
203203
assert isinstance(cache_params, dict)
204204
assert isinstance(cache_params.get("redis_version"), float)
205+
206+
207+
@pytest.fixture
208+
def mock_redis_client_list_restricted(mocker):
209+
"""Mock Redis cache where CLIENT LIST is restricted (like GCP Redis)"""
210+
211+
def mock_client_list():
212+
raise Exception("ERR unknown command 'CLIENT'")
213+
214+
def mock_info():
215+
return {
216+
"redis_version": "6.2.7",
217+
"used_memory": "1000000",
218+
"connected_clients": "5",
219+
"keyspace_hits": "1000",
220+
"keyspace_misses": "100",
221+
}
222+
223+
mock_cache = mocker.MagicMock()
224+
mock_cache.type = "redis"
225+
mock_cache.cache = RedisCache(host="localhost", port=6379, password="hello")
226+
mock_cache.cache.client_list = mock_client_list
227+
mock_cache.cache.info = mock_info
228+
229+
mocker.patch.object(litellm, "cache", mock_cache)
230+
return mock_cache
231+
232+
233+
@pytest.fixture
234+
def mock_redis_client_list_success(mocker):
235+
"""Mock Redis cache where CLIENT LIST works normally"""
236+
237+
def mock_client_list():
238+
return [
239+
{"id": "1", "addr": "127.0.0.1:54321", "name": "client1"},
240+
{"id": "2", "addr": "127.0.0.1:54322", "name": "client2"},
241+
]
242+
243+
def mock_info():
244+
return {
245+
"redis_version": "6.2.7",
246+
"used_memory": "1000000",
247+
"connected_clients": "2",
248+
}
249+
250+
mock_cache = mocker.MagicMock()
251+
mock_cache.type = "redis"
252+
mock_cache.cache = RedisCache(host="localhost", port=6379, password="hello")
253+
mock_cache.cache.client_list = mock_client_list
254+
mock_cache.cache.info = mock_info
255+
256+
mocker.patch.object(litellm, "cache", mock_cache)
257+
return mock_cache
258+
259+
260+
def test_cache_redis_info_no_cache():
261+
"""Test /cache/redis/info when no cache is initialized"""
262+
original_cache = litellm.cache
263+
litellm.cache = None
264+
265+
response = client.get(
266+
"/cache/redis/info", headers={"Authorization": "Bearer sk-1234"}
267+
)
268+
assert response.status_code == 503
269+
270+
data = response.json()
271+
assert "Cache not initialized" in data["detail"]
272+
273+
# Restore original cache
274+
litellm.cache = original_cache

0 commit comments

Comments
 (0)