diff --git a/core/database_arango.py b/core/database_arango.py index cb8efc867..ceaa28718 100644 --- a/core/database_arango.py +++ b/core/database_arango.py @@ -195,7 +195,12 @@ def connect( try: yeti_db = sys_db.has_database(database) break - except requests.exceptions.ConnectionError as e: + # python-arango's own host-resolver raises the builtin + # ConnectionError when it exhausts its internal retries, rather + # than propagating requests' ConnectionError -- catch both, since + # which one surfaces depends on where in the request the failure + # originates. + except (ConnectionError, requests.exceptions.ConnectionError) as e: logging.error("Connection error: {0:s}".format(str(e))) logging.error("Retrying in 5 seconds...") time.sleep(5) diff --git a/tests/core_tests/database_arango.py b/tests/core_tests/database_arango.py new file mode 100644 index 000000000..807e5ab84 --- /dev/null +++ b/tests/core_tests/database_arango.py @@ -0,0 +1,18 @@ +import unittest +from unittest import mock + +from core import database_arango + + +class ConnectRetryTest(unittest.TestCase): + def test_connect_retries_and_exits_cleanly_on_unreachable_host(self) -> None: + """connect() must retry (not crash with an unhandled exception) when + ArangoDB is unreachable, and exit cleanly once it exhausts its + retries. python-arango's host resolver wraps a failed connection + attempt in the builtin ConnectionError rather than + requests.exceptions.ConnectionError, so the retry loop's except + clause needs to catch both.""" + db = database_arango.ArangoDatabase() + with mock.patch("core.database_arango.time.sleep"): + with self.assertRaises(SystemExit): + db.connect(host="127.0.0.1", port=1, username="root", password="")