Skip to content

Commit fd7e0df

Browse files
committed
Fix: Fail early when database server does not respond
1 parent d32b74a commit fd7e0df

File tree

5 files changed

+34
-17
lines changed

5 files changed

+34
-17
lines changed

CHANGES.rst

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ Changes for crate
55
Unreleased
66
==========
77

8+
- Changed connection behaviour to fail early when database server
9+
does not respond
10+
811
2025/01/30 2.0.0
912
================
1013

docs/by-example/client.rst

+2-6
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,8 @@ respond, the request is automatically routed to the next server:
3535
>>> connection = client.connect([invalid_host, crate_host])
3636
>>> connection.close()
3737

38-
If no ``servers`` are given, the default one ``http://127.0.0.1:4200`` is used:
39-
40-
>>> connection = client.connect()
41-
>>> connection.client._active_servers
42-
['http://127.0.0.1:4200']
43-
>>> connection.close()
38+
If no ``servers`` are supplied to the ``connect`` method, the default address
39+
``http://127.0.0.1:4200`` is used.
4440

4541
If the option ``error_trace`` is set to ``True``, the client will print a whole
4642
traceback if a server error occurs:

src/crate/client/connection.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
# software solely pursuant to the terms of the relevant commercial agreement.
2121

2222
from verlib2 import Version
23+
from verlib2.packaging.version import InvalidVersion
2324

2425
from .blob import BlobContainer
2526
from .cursor import Cursor
@@ -197,14 +198,20 @@ def get_blob_container(self, container_name):
197198

198199
def _lowest_server_version(self):
199200
lowest = None
201+
last_connection_error = None
200202
for server in self.client.active_servers:
201203
try:
202204
_, _, version = self.client.server_infos(server)
203205
version = Version(version)
204-
except (ValueError, ConnectionError):
206+
except ConnectionError as ex:
207+
last_connection_error = ex
208+
continue
209+
except (ValueError, InvalidVersion):
205210
continue
206211
if not lowest or version < lowest:
207212
lowest = version
213+
if lowest is None and last_connection_error is not None:
214+
raise last_connection_error
208215
return lowest or Version("0.0.0")
209216

210217
def __repr__(self):

tests/client/test_connection.py

+17-9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from urllib3 import Timeout
55

6+
import crate.client.exceptions
67
from crate.client import connect
78
from crate.client.connection import Connection
89
from crate.client.http import Client
@@ -11,6 +12,7 @@
1112

1213

1314
class ConnectionTest(TestCase):
15+
1416
def test_connection_mock(self):
1517
"""
1618
For testing purposes it is often useful to replace the client used for
@@ -21,21 +23,27 @@ def test_connection_mock(self):
2123
"""
2224

2325
class MyConnectionClient:
24-
active_servers = ["localhost:4200"]
26+
active_servers = [crate_host]
2527

2628
def __init__(self):
2729
pass
2830

2931
def server_infos(self, server):
30-
return ("localhost:4200", "my server", "0.42.0")
32+
return (crate_host, "my server", "0.42.0")
3133

3234
connection = connect([crate_host], client=MyConnectionClient())
3335
self.assertIsInstance(connection, Connection)
3436
self.assertEqual(
3537
connection.client.server_infos("foo"),
36-
("localhost:4200", "my server", "0.42.0"),
38+
(crate_host, "my server", "0.42.0"),
3739
)
3840

41+
def test_invalid_server_address(self):
42+
client = Client(servers="localhost:4202")
43+
with self.assertRaises(crate.client.exceptions.ConnectionError) as ex:
44+
connect(client=client)
45+
self.assertIn("Server not available", ex.exception.message)
46+
3947
def test_lowest_server_version(self):
4048
infos = [
4149
(None, None, "0.42.3"),
@@ -50,14 +58,14 @@ def test_lowest_server_version(self):
5058
connection.close()
5159

5260
def test_invalid_server_version(self):
53-
client = Client(servers="localhost:4200")
61+
client = Client(servers=crate_host)
5462
client.server_infos = lambda server: (None, None, "No version")
5563
connection = connect(client=client)
5664
self.assertEqual((0, 0, 0), connection.lowest_server_version.version)
5765
connection.close()
5866

5967
def test_context_manager(self):
60-
with connect("localhost:4200") as conn:
68+
with connect(crate_host) as conn:
6169
pass
6270
self.assertEqual(conn._closed, True)
6371

@@ -70,7 +78,7 @@ def test_with_timezone(self):
7078
"""
7179

7280
tz_mst = datetime.timezone(datetime.timedelta(hours=7), name="MST")
73-
connection = connect("localhost:4200", time_zone=tz_mst)
81+
connection = connect(crate_host, time_zone=tz_mst)
7482
cursor = connection.cursor()
7583
self.assertEqual(cursor.time_zone.tzname(None), "MST")
7684
self.assertEqual(
@@ -88,20 +96,20 @@ def test_timeout_float(self):
8896
"""
8997
Verify setting the timeout value as a scalar (float) works.
9098
"""
91-
with connect("localhost:4200", timeout=2.42) as conn:
99+
with connect(crate_host, timeout=2.42) as conn:
92100
self.assertEqual(conn.client._pool_kw["timeout"], 2.42)
93101

94102
def test_timeout_string(self):
95103
"""
96104
Verify setting the timeout value as a scalar (string) works.
97105
"""
98-
with connect("localhost:4200", timeout="2.42") as conn:
106+
with connect(crate_host, timeout="2.42") as conn:
99107
self.assertEqual(conn.client._pool_kw["timeout"], 2.42)
100108

101109
def test_timeout_object(self):
102110
"""
103111
Verify setting the timeout value as a Timeout object works.
104112
"""
105113
timeout = Timeout(connect=2.42, read=0.01)
106-
with connect("localhost:4200", timeout=timeout) as conn:
114+
with connect(crate_host, timeout=timeout) as conn:
107115
self.assertEqual(conn.client._pool_kw["timeout"], timeout)

tests/client/tests.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ def test_suite():
3434
suite.addTest(makeSuite(KeepAliveClientTest))
3535
suite.addTest(makeSuite(ThreadSafeHttpClientTest))
3636
suite.addTest(makeSuite(ParamsTest))
37-
suite.addTest(makeSuite(ConnectionTest))
3837
suite.addTest(makeSuite(RetryOnTimeoutServerTest))
3938
suite.addTest(makeSuite(RequestsCaBundleTest))
4039
suite.addTest(makeSuite(TestUsernameSentAsHeader))
@@ -65,6 +64,10 @@ def test_suite():
6564
# Integration tests.
6665
layer = ensure_cratedb_layer()
6766

67+
s = makeSuite(ConnectionTest)
68+
s.layer = layer
69+
suite.addTest(s)
70+
6871
s = doctest.DocFileSuite(
6972
"docs/by-example/http.rst",
7073
"docs/by-example/client.rst",

0 commit comments

Comments
 (0)