Skip to content

Commit bb2fc0d

Browse files
4.0 verify connectivity (#351)
* Added stub tests RETURN 1 AS x These have explicit HELLO messages. * Implemented verify_connection for direct driver. * Added script router_port_9001_one_read_port_9004_one_write_port_9006.script * Added verify_connectivity to Neo4jDriver This should be improved when better stub test is available for clustering.
1 parent 69f0a7b commit bb2fc0d

File tree

8 files changed

+159
-3
lines changed

8 files changed

+159
-3
lines changed

neo4j/__init__.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,17 @@ def close(self):
242242
"""
243243
self._pool.close()
244244

245+
def verify_connectivity(self, **config):
246+
""" This verifies if the driver can connect to a remote server or a cluster
247+
by establishing a network connection with the remote and possibly exchanging
248+
a few data before closing the connection. It throws exception if fails to connect.
249+
250+
Use the exception to further understand the cause of the connectivity problem.
251+
252+
Note: Even if this method throws an exception, the driver still need to be closed via close() to free up all resources.
253+
"""
254+
raise NotImplementedError
255+
245256

246257
class AsyncDriver:
247258

@@ -307,6 +318,15 @@ def pipeline(self, **config):
307318
PipelineConfig.consume(config))
308319
return Pipeline(self._pool, pipeline_config)
309320

321+
def verify_connectivity(self, **config):
322+
server_agent = None
323+
with self.session(**config) as session:
324+
result = session.run("RETURN 1 AS x")
325+
value = result.single().value()
326+
summary = result.summary()
327+
server_agent = summary.server.agent
328+
return server_agent
329+
310330

311331
class Neo4jDriver(Routing, Driver):
312332
""" A :class:`.Neo4jDriver` is created from a ``neo4j`` URI. The
@@ -343,6 +363,24 @@ def pipeline(self, **config):
343363
PipelineConfig.consume(config))
344364
return Pipeline(self._pool, pipeline_config)
345365

366+
def get_routing_table(self):
367+
return self._pool.routing_table
368+
369+
def verify_connectivity(self, **config):
370+
# TODO: Improve and update Stub Test Server to be able to test.
371+
return self._verify_routing_connectivity()
372+
373+
def _verify_routing_connectivity(self):
374+
table = self.get_routing_table()
375+
routing_info = {}
376+
for ix in list(table.routers):
377+
routing_info[ix] = self._pool.fetch_routing_info(table.routers[0])
378+
379+
for key, val in routing_info.items():
380+
if val is not None:
381+
return routing_info
382+
raise ServiceUnavailable("Could not connect to any routing servers.")
383+
346384

347385
class AsyncBoltDriver(Direct, AsyncDriver):
348386

tests/stub/scripts/v3/disconnect_on_run.script

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
!: AUTO RESET
55
!: PORT 9001
66

7-
C: RUN "RETURN $x" {"x": 1} {}
7+
C: RUN "RETURN 1 AS x" {} {}
88
S: <EXIT>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
!: BOLT 3
2+
!: AUTO GOODBYE
3+
!: AUTO RESET
4+
!: PORT 9001
5+
6+
C: HELLO {"user_agent": "test", "scheme": "basic", "principal": "test", "credentials": "test"}
7+
S: SUCCESS {"server": "Neo4j/3.0.0", "connection_id": "123e4567-e89b-12d3-a456-426655440000"}
8+
C: RUN "RETURN 1 AS x" {} {}
9+
PULL_ALL
10+
S: SUCCESS {"fields": ["x"]}
11+
RECORD [1]
12+
SUCCESS {"bookmark": "neo4j:bookmark-test-1", type": "r", "t_last": 5}

tests/stub/scripts/v4x0/disconnect_on_run.script

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
!: AUTO RESET
55
!: PORT 9001
66

7-
C: RUN "RETURN $x" {"x": 1} {}
7+
C: RUN "RETURN 1 AS x" {} {}
88
S: <EXIT>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
!: BOLT 4
2+
!: AUTO GOODBYE
3+
!: AUTO RESET
4+
!: PORT 9001
5+
6+
C: HELLO {"user_agent": "test", "scheme": "basic", "principal": "test", "credentials": "test"}
7+
S: SUCCESS {"server": "Neo4j/4.0.0", "connection_id": "123e4567-e89b-12d3-a456-426655440000"}
8+
C: RUN "RETURN 1 AS x" {} {}
9+
PULL {"n": -1}
10+
S: SUCCESS {"fields": ["x"]}
11+
RECORD [1]
12+
SUCCESS {"bookmark": "neo4j:bookmark-test-1", type": "r", "t_last": 5, "db": "system"}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
!: BOLT 4
2+
!: AUTO GOODBYE
3+
!: AUTO RESET
4+
!: PORT 9001
5+
6+
C: HELLO {"user_agent": "test", "scheme": "basic", "principal": "test", "credentials": "test"}
7+
S: SUCCESS {"server": "Neo4j/4.0.0", "connection_id": "123e4567-e89b-12d3-a456-426655440000"}
8+
9+
C: RUN "CALL dbms.cluster.routing.getRoutingTable($context)" {"context":{}} {}
10+
PULL {"n": -1}
11+
S: SUCCESS {"fields": ["ttl", "servers"]}
12+
RECORD [300, [{"role":"ROUTE", "addresses":["127.0.0.1:9001", "127.0.0.1:9002"]}, {"role":"READ", "addresses":["127.0.0.1:9004"]}, {"role":"WRITE", "addresses":["127.0.0.1:9006"]}]]
13+
SUCCESS {"bookmark": "neo4j:bookmark-test-1", type": "r", "t_last": 5, "db": "system"}

tests/stub/test_directdriver.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,48 @@ def test_bolt_uri_constructs_bolt_driver(driver_info, test_script):
5050
assert isinstance(driver, BoltDriver)
5151

5252

53+
def test_direct_driver_with_wrong_port(driver_info):
54+
# python -m pytest tests/stub/test_directdriver.py -s -v -k test_direct_driver_with_wrong_port
55+
uri = "bolt://127.0.0.1:9002"
56+
with pytest.raises(ServiceUnavailable):
57+
driver = GraphDatabase.driver(uri, auth=driver_info["auth_token"], user_agent="test")
58+
# assert isinstance(driver, BoltDriver)
59+
# with pytest.raises(ServiceUnavailable):
60+
# driver.verify_connectivity()
61+
62+
63+
@pytest.mark.parametrize(
64+
"test_script, test_expected",
65+
[
66+
("v3/return_1_port_9001.script", "Neo4j/3.0.0"),
67+
("v4x0/return_1_port_9001.script", "Neo4j/4.0.0"),
68+
]
69+
)
70+
def test_direct_verify_connectivity(driver_info, test_script, test_expected):
71+
# python -m pytest tests/stub/test_directdriver.py -s -v -k test_direct_verify_connectivity
72+
with StubCluster(test_script):
73+
uri = "bolt://127.0.0.1:9001"
74+
with GraphDatabase.driver(uri, auth=driver_info["auth_token"], user_agent="test") as driver:
75+
assert isinstance(driver, BoltDriver)
76+
assert driver.verify_connectivity() == test_expected
77+
78+
79+
@pytest.mark.parametrize(
80+
"test_script",
81+
[
82+
"v3/disconnect_on_run.script",
83+
"v4x0/disconnect_on_run.script",
84+
]
85+
)
86+
def test_direct_verify_connectivity_disconnect_on_run(driver_info, test_script):
87+
# python -m pytest tests/stub/test_directdriver.py -s -v -k test_direct_verify_connectivity_disconnect_on_run
88+
with StubCluster(test_script):
89+
uri = "bolt://127.0.0.1:9001"
90+
with GraphDatabase.driver(uri, auth=driver_info["auth_token"]) as driver:
91+
with pytest.raises(ServiceUnavailable):
92+
driver.verify_connectivity()
93+
94+
5395
@pytest.mark.parametrize(
5496
"test_script",
5597
[
@@ -64,7 +106,7 @@ def test_direct_disconnect_on_run(driver_info, test_script):
64106
with GraphDatabase.driver(uri, auth=driver_info["auth_token"]) as driver:
65107
with pytest.raises(ServiceUnavailable):
66108
with driver.session() as session:
67-
session.run("RETURN $x", {"x": 1}).consume()
109+
session.run("RETURN 1 AS x").consume()
68110

69111

70112
@pytest.mark.parametrize(

tests/stub/test_routingdriver.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,45 @@ def test_bolt_plus_routing_uri_constructs_neo4j_driver(driver_info, test_script)
5757
assert isinstance(driver, Neo4jDriver)
5858

5959

60+
@pytest.mark.parametrize(
61+
"test_script",
62+
[
63+
"v3/router.script",
64+
"v4x0/router_port_9001_one_read_port_9004_one_write_port_9006.script",
65+
]
66+
)
67+
def test_neo4j_driver_verify_connectivity(driver_info, test_script):
68+
# python -m pytest tests/stub/test_routingdriver.py -s -v -k test_neo4j_driver_verify_connectivity
69+
uri = "bolt+routing://127.0.0.1:9001"
70+
with StubCluster(test_script):
71+
driver = GraphDatabase.driver(uri, auth=driver_info["auth_token"], user_agent="test")
72+
assert isinstance(driver, Neo4jDriver)
73+
74+
with StubCluster(test_script):
75+
assert driver.verify_connectivity() is not None
76+
driver.close()
77+
78+
79+
@pytest.mark.parametrize(
80+
"test_script",
81+
[
82+
"v3/router.script",
83+
"v4x0/router_port_9001_one_read_port_9004_one_write_port_9006.script",
84+
]
85+
)
86+
def test_neo4j_driver_verify_connectivity_server_down(driver_info, test_script):
87+
# python -m pytest tests/stub/test_routingdriver.py -s -v -k test_neo4j_driver_verify_connectivity_server_down
88+
uri = "bolt+routing://127.0.0.1:9001"
89+
with StubCluster(test_script):
90+
driver = GraphDatabase.driver(uri, auth=driver_info["auth_token"], user_agent="test")
91+
assert isinstance(driver, Neo4jDriver)
92+
93+
with pytest.raises(ServiceUnavailable):
94+
driver.verify_connectivity()
95+
96+
driver.close()
97+
98+
6099
@pytest.mark.parametrize(
61100
"test_script",
62101
[

0 commit comments

Comments
 (0)