fix(redis): keep the credential provider off the Sentinel monitors

This commit is contained in:
mateo-berri 2026-08-20 16:42:57 -07:00
parent 308c906cdd
commit 09b391d7b3
2 changed files with 43 additions and 2 deletions

View file

@ -551,14 +551,22 @@ def _init_redis_sentinel(redis_kwargs) -> redis.Redis:
return sentinel.master_for(service_name, **connection_kwargs)
def _sentinel_auth_kwargs(connection_kwargs: dict, sentinel_password: str | None) -> dict:
"""The Sentinel monitors are separate servers with their own password, and redis-py refuses a
password passed alongside a credential provider, so the data node's provider stays behind once
a Sentinel password is configured."""
superseded: Final = frozenset({"credential_provider"}) if sentinel_password else frozenset()
kept: Final = ((k, v) for k, v in connection_kwargs.items() if k not in superseded)
return dict(kept, password=sentinel_password)
def _init_async_redis_sentinel(redis_kwargs) -> async_redis.Redis:
sentinel_nodes: Final = redis_kwargs.get("sentinel_nodes")
sentinel_password: Final = redis_kwargs.get("sentinel_password")
service_name: Final = redis_kwargs.get("service_name")
connection_kwargs: Final = _get_redis_sentinel_connection_kwargs(redis_kwargs)
connection_kwargs.setdefault("socket_timeout", REDIS_SOCKET_TIMEOUT)
sentinel_kwargs: Final = dict(connection_kwargs)
sentinel_kwargs["password"] = sentinel_password
sentinel_kwargs: Final = _sentinel_auth_kwargs(connection_kwargs, sentinel_password)
if not sentinel_nodes or not service_name:
raise ValueError("Both 'sentinel_nodes' and 'service_name' are required for Redis Sentinel.")

View file

@ -1024,3 +1024,36 @@ def test_async_cluster_drops_a_connect_func_it_cannot_pass_on():
client = get_redis_async_client()
assert isinstance(client, async_redis.RedisCluster)
@pytest.mark.parametrize(
"markers, provider_cls",
[
(AZURE_AD_CONNECT_FUNC, AzureADCredentialProvider),
(GCP_IAM_CONNECT_FUNC, GCPIAMCredentialProvider),
],
ids=["azure_ad", "gcp_iam"],
)
def test_async_sentinel_keeps_the_credential_provider_off_the_monitors(markers, provider_cls):
"""The Sentinel monitors authenticate with their own password, and redis-py refuses a password
passed alongside a credential provider, so only the data node may carry the provider.
"""
redis_kwargs = {
"sentinel_nodes": [("sentinel-1", 26379)],
"sentinel_password": "sentinel-secret",
"service_name": "mymaster",
"redis_connect_func": SimpleNamespace(**markers),
}
with patch("litellm._redis.async_redis.Sentinel") as mock_sentinel_cls:
with patch("litellm._redis._get_redis_client_logic", return_value=redis_kwargs):
get_redis_async_client()
sentinel_kwargs = mock_sentinel_cls.call_args[1]["sentinel_kwargs"]
assert sentinel_kwargs["password"] == "sentinel-secret"
assert "credential_provider" not in sentinel_kwargs
async_redis.Connection(host="sentinel-1", port=26379, **sentinel_kwargs)
master_kwargs = mock_sentinel_cls.return_value.master_for.call_args[1]
assert isinstance(master_kwargs["credential_provider"], provider_cls)
assert "password" not in master_kwargs