Skip to content

Commit ead1729

Browse files
authored
Merge pull request #649 from FlorentinD/warn-about-expire
Warn about expiring sessions
2 parents 714b685 + 9afcc26 commit ead1729

File tree

4 files changed

+90
-9
lines changed

4 files changed

+90
-9
lines changed

graphdatascience/session/aura_api_responses.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
import sys
55
from collections import defaultdict
66
from dataclasses import dataclass
7-
from datetime import datetime, timezone
7+
from datetime import datetime, timedelta, timezone
88
from typing import Any, Dict, NamedTuple, Optional, Set
99

10+
from pandas import Timedelta
11+
1012

1113
@dataclass(repr=True, frozen=True)
1214
class SessionDetails:
@@ -15,28 +17,34 @@ class SessionDetails:
1517
instance_id: str
1618
memory: str
1719
status: str
18-
host: Optional[str]
19-
expiry_date: Optional[datetime]
20+
host: str
2021
created_at: datetime
22+
expiry_date: Optional[datetime]
23+
ttl: Optional[timedelta]
2124

2225
@classmethod
2326
def fromJson(cls, json: Dict[str, Any]) -> SessionDetails:
2427
expiry_date = json.get("expiry_date")
28+
ttl = json.get("ttl")
2529

2630
return cls(
2731
id=json["id"],
2832
name=json["name"],
2933
instance_id=json["instance_id"],
3034
memory=json["memory"],
3135
status=json["status"],
32-
host=json.get("host"),
36+
host=json["host"],
3337
expiry_date=TimeParser.fromisoformat(expiry_date) if expiry_date else None,
3438
created_at=TimeParser.fromisoformat(json["created_at"]),
39+
ttl=Timedelta(ttl).to_pytimedelta() if ttl else None, # datetime has no support for parsing timedetla
3540
)
3641

3742
def bolt_connection_url(self) -> str:
3843
return f"neo4j+ssc://{self.host}" # TODO use neo4j+s
3944

45+
def is_expired(self) -> bool:
46+
return self.status == "Expired"
47+
4048

4149
@dataclass(repr=True, frozen=True)
4250
class InstanceDetails:

graphdatascience/session/dedicated_sessions.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import hashlib
44
import warnings
5+
from datetime import datetime, timedelta, timezone
56
from typing import List, Optional
67

78
from graphdatascience.session.algorithm_category import AlgorithmCategory
@@ -50,6 +51,7 @@ def get_or_create(
5051

5152
# TODO configure session size (and check existing_session has same size)
5253
if existing_session:
54+
self._check_expiry_date(existing_session)
5355
session_id = existing_session.id
5456
else:
5557
create_details = self._create_session(session_name, dbid, db_connection.uri, password, memory)
@@ -129,6 +131,14 @@ def _construct_client(
129131
delete_fn=lambda: self.delete(session_name, dbid=AuraApi.extract_id(db_connection.uri)),
130132
)
131133

134+
def _check_expiry_date(self, session: SessionDetails) -> None:
135+
if session.is_expired():
136+
raise RuntimeError(f"Session `{session.name}` is expired. Please delete it and create a new one.")
137+
if session.expiry_date:
138+
until_expiry: timedelta = session.expiry_date - datetime.now(timezone.utc)
139+
if until_expiry < timedelta(days=1):
140+
raise Warning(f"Session `{session.name}` is expiring in less than a day.")
141+
132142
@classmethod
133143
def _fail_ambiguous_session(cls, session_name: str, sessions: List[SessionDetails]) -> None:
134144
candidates = [i.id for i in sessions]

graphdatascience/tests/unit/test_aura_api.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import logging
2-
from datetime import datetime, timezone
2+
from datetime import datetime, timedelta, timezone
33

44
import pytest
55
from _pytest.logging import LogCaptureFixture
@@ -48,6 +48,7 @@ def test_create_session(requests_mock: Mocker) -> None:
4848
host="1.2.3.4",
4949
memory="4G",
5050
expiry_date=None,
51+
ttl=None,
5152
)
5253

5354

@@ -82,6 +83,7 @@ def test_list_session(requests_mock: Mocker) -> None:
8283
host="1.2.3.4",
8384
memory="4G",
8485
expiry_date=TimeParser.fromisoformat("1977-01-01T00:00:00Z"),
86+
ttl=None,
8587
)
8688

8789

@@ -109,6 +111,7 @@ def test_list_sessions(requests_mock: Mocker) -> None:
109111
"instance_id": "dbid-3",
110112
"created_at": "2012-01-01T00:00:00Z",
111113
"memory": "8G",
114+
"host": "foo.bar",
112115
},
113116
],
114117
)
@@ -124,6 +127,7 @@ def test_list_sessions(requests_mock: Mocker) -> None:
124127
host="1.2.3.4",
125128
memory="4G",
126129
expiry_date=TimeParser.fromisoformat("1977-01-01T00:00:00Z"),
130+
ttl=None,
127131
)
128132

129133
expected2 = SessionDetails(
@@ -133,8 +137,9 @@ def test_list_sessions(requests_mock: Mocker) -> None:
133137
instance_id="dbid-3",
134138
created_at=TimeParser.fromisoformat("2012-01-01T00:00:00Z"),
135139
memory="8G",
136-
host=None,
140+
host="foo.bar",
137141
expiry_date=None,
142+
ttl=None,
138143
)
139144

140145
assert result == [expected1, expected2]
@@ -635,6 +640,7 @@ def test_parse_session_info() -> None:
635640
"expiry_date": "2022-01-01T00:00:00Z",
636641
"created_at": "2021-01-01T00:00:00Z",
637642
"host": "a.b",
643+
"ttl": "1d8h1m2s",
638644
}
639645
session_info = SessionDetails.fromJson(session_details)
640646

@@ -647,10 +653,11 @@ def test_parse_session_info() -> None:
647653
host="a.b",
648654
expiry_date=datetime(2022, 1, 1, 0, 0, 0, tzinfo=timezone.utc),
649655
created_at=datetime(2021, 1, 1, 0, 0, 0, tzinfo=timezone.utc),
656+
ttl=timedelta(days=1, hours=8, minutes=1, seconds=2),
650657
)
651658

652659

653-
def test_parse_session_info_without_expiry() -> None:
660+
def test_parse_session_info_without_optionals() -> None:
654661
session_details = {
655662
"id": "test_id",
656663
"name": "test_session",
@@ -670,5 +677,6 @@ def test_parse_session_info_without_expiry() -> None:
670677
host="a.b",
671678
status="running",
672679
expiry_date=None,
680+
ttl=None,
673681
created_at=datetime(2021, 1, 1, 0, 0, 0, tzinfo=timezone.utc),
674682
)

graphdatascience/tests/unit/test_dedicated_sessions.py

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import dataclasses
22
import re
3-
from datetime import datetime
4-
from typing import List, Optional
3+
from datetime import datetime, timedelta, timezone
4+
from typing import List, Optional, cast
55

66
import pytest
77
from pytest_mock import MockerFixture
@@ -53,13 +53,20 @@ def create_session(self, name: str, dbid: str, pwd: str, memory: str) -> Session
5353
created_at=datetime.fromisoformat("2021-01-01T00:00:00+00:00"),
5454
host="foo.bar",
5555
expiry_date=None,
56+
ttl=None,
5657
)
5758

5859
self.id_counter += 1
5960
self._sessions[details.id] = details
6061

6162
return details
6263

64+
def add_session(self, session: SessionDetails) -> None:
65+
if session.id in self._sessions:
66+
raise ValueError(f"Session with id {session.id} already exists.")
67+
68+
self._sessions[session.id] = session
69+
6370
def create_instance(self, name: str, memory: str, cloud_provider: str, region: str) -> InstanceCreateDetails:
6471
id = f"ffff{self.id_counter}"
6572
create_details = InstanceCreateDetails(
@@ -236,6 +243,54 @@ def test_get_or_create_duplicate_session(aura_api: AuraApi) -> None:
236243
sessions.get_or_create("one", SessionMemory.m_8GB, DbmsConnectionInfo(db.connection_url, "", ""))
237244

238245

246+
def test_get_or_create_expired_session(aura_api: AuraApi) -> None:
247+
db = _setup_db_instance(aura_api)
248+
249+
fake_aura_api = cast(FakeAuraApi, aura_api)
250+
fake_aura_api.add_session(
251+
SessionDetails(
252+
id="ffff0-ffff1",
253+
name="one",
254+
instance_id=db.id,
255+
memory=SessionMemory.m_8GB.value,
256+
status="Expired",
257+
created_at=datetime.now(),
258+
host="foo.bar",
259+
expiry_date=None,
260+
ttl=None,
261+
)
262+
)
263+
264+
with pytest.raises(
265+
RuntimeError, match=re.escape("Session `one` is expired. Please delete it and create a new one.")
266+
):
267+
sessions = DedicatedSessions(aura_api)
268+
sessions.get_or_create("one", SessionMemory.m_8GB, DbmsConnectionInfo(db.connection_url, "", ""))
269+
270+
271+
def test_get_or_create_soon_expired_session(aura_api: AuraApi) -> None:
272+
db = _setup_db_instance(aura_api)
273+
274+
fake_aura_api = cast(FakeAuraApi, aura_api)
275+
fake_aura_api.add_session(
276+
SessionDetails(
277+
id="ffff0-ffff1",
278+
name="one",
279+
instance_id=db.id,
280+
memory=SessionMemory.m_8GB.value,
281+
status="Ready",
282+
created_at=datetime.now(),
283+
host="foo.bar",
284+
expiry_date=datetime.now(tz=timezone.utc) - timedelta(hours=23),
285+
ttl=None,
286+
)
287+
)
288+
289+
with pytest.raises(Warning, match=re.escape("Session `one` is expiring in less than a day.")):
290+
sessions = DedicatedSessions(aura_api)
291+
sessions.get_or_create("one", SessionMemory.m_8GB, DbmsConnectionInfo(db.connection_url, "", ""))
292+
293+
239294
def test_delete_session(aura_api: AuraApi) -> None:
240295
db1 = aura_api.create_instance("db1", "1GB", "aura", "leipzig").id
241296
db2 = aura_api.create_instance("db2", "1GB", "aura", "dresden").id

0 commit comments

Comments
 (0)