mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
fix(vector_stores): return 400 when a MongoDB TLS file cannot be read
tlsCAFile and tlsCertificateKeyFile are how a self-managed deployment presents a private CA, so they are the options on-prem operators actually set. pymongo opens those files itself during TLS setup and lets OSError out, which is neither a PyMongoError nor a ValueError, so it missed every branch of the translator and litellm.exception_type turned it into a 500 with a traceback in the body. A mistyped path, or one that exists on the host but not inside the container, is a routine mistake and has to read as a 400 naming the file. Matched on the exception carrying a filename so a socket-level OSError still falls through to the branches that handle it. Verified against a self-managed mongod with a missing CA file, a CA path that is a directory, and a missing client certificate.
This commit is contained in:
parent
50fb35e17e
commit
323f51269d
2 changed files with 51 additions and 0 deletions
|
|
@ -267,6 +267,14 @@ def translate_mongo_error(error: Exception, index_name: str, database: str, coll
|
|||
)
|
||||
if isinstance(error, InvalidOperation):
|
||||
return config_error(f"The MongoDB client was already closed or is unusable. Driver detail: {error}")
|
||||
# A tlsCAFile or tlsCertificateKeyFile the process cannot open raises OSError from the TLS setup
|
||||
# rather than a PyMongoError, and those options are how self-managed deployments present a private CA
|
||||
if isinstance(error, OSError) and error.filename:
|
||||
return config_error(
|
||||
f"'{error.filename}', named by a TLS option in mongodb_connection_string, could not be read. "
|
||||
"Check that tlsCAFile and tlsCertificateKeyFile point at files this process can open; inside "
|
||||
f"a container that is the path in the container, not on the host. Driver detail: {error}"
|
||||
)
|
||||
# pymongo raises a plain ValueError, not a PyMongoError, for an unusable port, which an unescaped
|
||||
# ':' in a password also produces, and which would otherwise reach the caller as a 500
|
||||
if isinstance(error, ValueError):
|
||||
|
|
|
|||
|
|
@ -1362,3 +1362,46 @@ class TestUnescapedCredentialsAreDiagnosed:
|
|||
|
||||
assert isinstance(translated, BadRequestError)
|
||||
assert "database name in the URI path" in str(translated)
|
||||
|
||||
|
||||
class TestUnreadableTlsFilesAreDiagnosed:
|
||||
"""A private CA is how self-managed deployments present TLS, so tlsCAFile and
|
||||
tlsCertificateKeyFile are on-prem options in practice. pymongo opens those files itself and
|
||||
lets OSError out, which is not a PyMongoError, so before this they reached the caller as a 500
|
||||
with a traceback. The errors here come from pymongo's real TLS setup."""
|
||||
|
||||
@staticmethod
|
||||
def _real_tls_error(uri):
|
||||
from pymongo import MongoClient
|
||||
|
||||
try:
|
||||
MongoClient(uri, serverSelectionTimeoutMS=1500).admin.command("ping")
|
||||
except Exception as e:
|
||||
return e
|
||||
raise AssertionError(f"expected {uri!r} to fail")
|
||||
|
||||
def _translated(self, uri):
|
||||
return translate_mongo_error(self._real_tls_error(uri), index_name=INDEX, database="db", collection="c")
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path",
|
||||
["/nonexistent-directory-for-tests/ca.pem", "/tmp"],
|
||||
)
|
||||
def test_an_unreadable_ca_file_is_a_400_naming_the_path(self, path):
|
||||
translated = self._translated(f"mongodb://localhost:27717/?tls=true&tlsCAFile={path}")
|
||||
|
||||
assert isinstance(translated, BadRequestError)
|
||||
assert path in str(translated)
|
||||
assert "tlsCAFile" in str(translated)
|
||||
|
||||
def test_an_unreadable_client_certificate_is_a_400_naming_the_path(self):
|
||||
path = "/nonexistent-directory-for-tests/client.pem"
|
||||
translated = self._translated(f"mongodb://localhost:27717/?tls=true&tlsCertificateKeyFile={path}")
|
||||
|
||||
assert isinstance(translated, BadRequestError)
|
||||
assert path in str(translated)
|
||||
|
||||
def test_an_oserror_carrying_no_filename_is_left_for_the_other_branches(self):
|
||||
translated = translate_mongo_error(OSError("socket hung up"), index_name=INDEX, database="db", collection="c")
|
||||
|
||||
assert not isinstance(translated, BadRequestError)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue