Skip to content

Commit 18c6be4

Browse files
committed
Handle reconnect to non-running instances
Before we got into odd state for instances with state creating or destroying
1 parent de5ff16 commit 18c6be4

File tree

4 files changed

+44
-29
lines changed

4 files changed

+44
-29
lines changed

graphdatascience/gds_session/aura_api.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import time
77
from collections import defaultdict
88
from dataclasses import dataclass
9-
from typing import Any, List, Optional, Set
9+
from typing import Any, List, NamedTuple, Optional, Set
1010
from urllib.parse import urlparse
1111

1212
import requests as req
@@ -71,6 +71,19 @@ def from_json(cls, json: dict[str, Any]) -> InstanceCreateDetails:
7171
return cls(**{f.name: json[f.name] for f in fields})
7272

7373

74+
class WaitResult(NamedTuple):
75+
connection_url: str
76+
error: str
77+
78+
@classmethod
79+
def from_error(cls, error: str) -> WaitResult:
80+
return cls(connection_url="", error=error)
81+
82+
@classmethod
83+
def from_connection_url(cls, connection_url: str) -> WaitResult:
84+
return cls(connection_url=connection_url, error="")
85+
86+
7487
@dataclass(repr=True, frozen=True)
7588
class TenantDetails:
7689
id: str
@@ -210,16 +223,16 @@ def list_instance(self, instance_id: str) -> Optional[InstanceSpecificDetails]:
210223

211224
def wait_for_instance_running(
212225
self, instance_id: str, sleep_time: float = 0.2, max_sleep_time: float = 300
213-
) -> Optional[str]:
226+
) -> WaitResult:
214227
waited_time = 0.0
215228
while waited_time <= max_sleep_time:
216229
instance = self.list_instance(instance_id)
217230
if instance is None:
218-
return "Instance is not found -- please retry"
231+
return WaitResult.from_error("Instance is not found -- please retry")
219232
elif instance.status in ["deleting", "destroying"]:
220-
return "Instance is being deleted"
233+
return WaitResult.from_error("Instance is being deleted")
221234
elif instance.status == "running":
222-
return None
235+
return WaitResult.from_connection_url(instance.connection_url)
223236
else:
224237
self._logger.debug(
225238
f"Instance `{instance_id}` is not yet running. "
@@ -229,7 +242,7 @@ def wait_for_instance_running(
229242
waited_time += sleep_time
230243
time.sleep(sleep_time)
231244

232-
return f"Instance is not running after waiting for {waited_time} seconds"
245+
return WaitResult.from_error(f"Instance is not running after waiting for {waited_time} seconds")
233246

234247
def _get_tenant_id(self) -> str:
235248
response = req.get(

graphdatascience/gds_session/gds_sessions.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,11 @@ def get_or_create(
6767
GdsSessions._instance_name(session_name), size.value, db_instance.cloud_provider, region
6868
)
6969
wait_result = self._aura_api.wait_for_instance_running(create_details.id)
70-
if wait_result is not None:
71-
raise RuntimeError(f"Failed to create session `{session_name}`: {wait_result}")
70+
if err := wait_result.error:
71+
raise RuntimeError(f"Failed to create session `{session_name}`: {err}")
7272

7373
gds_user = create_details.username
74-
gds_url = create_details.connection_url
74+
gds_url = wait_result.connection_url
7575

7676
self._change_initial_pw(
7777
gds_url=gds_url, gds_user=gds_user, initial_pw=create_details.password, new_pw=db_connection.password
@@ -125,14 +125,10 @@ def _try_connect(self, session_name: str, db_connection: DbmsConnectionInfo) ->
125125
if len(matched_instances) > 1:
126126
self._fail_ambiguous_session(session_name, matched_instances)
127127

128-
instance_details = self._aura_api.list_instance(matched_instances[0].id)
129-
130-
if instance_details:
131-
gds_url = instance_details.connection_url
132-
else:
133-
raise RuntimeError(
134-
f"Unable to get connection information for session `{session_name}`. Does it still exist?"
135-
)
128+
wait_result = self._aura_api.wait_for_instance_running(matched_instances[0].id)
129+
if err := wait_result.error:
130+
raise RuntimeError(f"Failed to connect to session `{session_name}`: {err}")
131+
gds_url = wait_result.connection_url
136132

137133
return self._construct_client(session_name=session_name, gds_url=gds_url, db_connection=db_connection)
138134

graphdatascience/tests/unit/test_aura_api.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
InstanceCreateDetails,
1111
InstanceSpecificDetails,
1212
TenantDetails,
13+
WaitResult,
1314
)
1415

1516

@@ -88,10 +89,12 @@ def test_create_instance(requests_mock: Mocker) -> None:
8889

8990
requests_mock.get(
9091
"https://api.neo4j.io/v1/tenants/some-tenant",
91-
json={"data": {
92-
"id": "some_tenant",
93-
"instance_configurations": [{"type": "enterprise-ds", "region": "leipzig-1", "cloud_provider": "aws"}]
94-
}},
92+
json={
93+
"data": {
94+
"id": "some_tenant",
95+
"instance_configurations": [{"type": "enterprise-ds", "region": "leipzig-1", "cloud_provider": "aws"}],
96+
}
97+
},
9598
)
9699

97100
requests_mock.post(
@@ -264,8 +267,9 @@ def test_dont_wait_forever(requests_mock: Mocker, caplog: LogCaptureFixture) ->
264267
api = AuraApi("", "", tenant_id="some-tenant")
265268

266269
with caplog.at_level(logging.DEBUG):
267-
assert "Instance is not running after waiting for 0.8" in api.wait_for_instance_running( # type: ignore
268-
"id0", max_sleep_time=0.7
270+
assert (
271+
"Instance is not running after waiting for 0.8"
272+
in api.wait_for_instance_running("id0", max_sleep_time=0.7).error
269273
)
270274

271275
assert "Instance `id0` is not yet running. Current status: creating. Retrying in 0.2 seconds..." in caplog.text
@@ -279,7 +283,7 @@ def test_wait_for_instance_running(requests_mock: Mocker) -> None:
279283
"data": {
280284
"status": "running",
281285
"cloud_provider": None,
282-
"connection_url": None,
286+
"connection_url": "foo.bar",
283287
"id": None,
284288
"name": None,
285289
"region": None,
@@ -291,7 +295,7 @@ def test_wait_for_instance_running(requests_mock: Mocker) -> None:
291295

292296
api = AuraApi("", "", tenant_id="some-tenant")
293297

294-
assert api.wait_for_instance_running("id0") is None
298+
assert api.wait_for_instance_running("id0") == WaitResult.from_connection_url("foo.bar")
295299

296300

297301
def test_wait_for_instance_deleting(requests_mock: Mocker) -> None:
@@ -329,8 +333,8 @@ def test_wait_for_instance_deleting(requests_mock: Mocker) -> None:
329333

330334
api = AuraApi("", "", tenant_id="some-tenant")
331335

332-
assert "Instance is being deleted" in api.wait_for_instance_running("id0") # type: ignore
333-
assert "Instance is being deleted" in api.wait_for_instance_running("id1") # type: ignore
336+
assert api.wait_for_instance_running("id0") == WaitResult.from_error("Instance is being deleted")
337+
assert api.wait_for_instance_running("id1") == WaitResult.from_error("Instance is being deleted")
334338

335339

336340
def test_extract_id() -> None:

graphdatascience/tests/unit/test_gds_sessions.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
InstanceDetails,
1414
InstanceSpecificDetails,
1515
TenantDetails,
16+
WaitResult,
1617
)
1718
from graphdatascience.gds_session.dbms_connection_info import DbmsConnectionInfo
1819
from graphdatascience.gds_session.gds_sessions import (
@@ -78,7 +79,7 @@ def list_instance(self, instance_id: str) -> Optional[InstanceSpecificDetails]:
7879

7980
def wait_for_instance_running(
8081
self, instance_id: str, sleep_time: float = 0.2, max_sleep_time: float = 300
81-
) -> Optional[str]:
82+
) -> WaitResult:
8283
return super().wait_for_instance_running(instance_id, sleep_time=0.0001, max_sleep_time=0.001)
8384

8485
def tenant_details(self) -> TenantDetails:
@@ -406,7 +407,8 @@ def test_create_session_invalid_region(aura_api: AuraApi) -> None:
406407
sessions._aura_api = aura_api
407408

408409
expected_message = (
409-
"Region `only-db-region` is not supported by the tenant `tenant_id`." " Supported regions: {'leipzig-1', 'dresden-2'}."
410+
"Region `only-db-region` is not supported by the tenant `tenant_id`."
411+
" Supported regions: {'leipzig-1', 'dresden-2'}."
410412
)
411413
with pytest.raises(ValueError, match=expected_message):
412414
sessions.get_or_create(

0 commit comments

Comments
 (0)