Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.

Commit 3abd886

Browse files
use effective_end_time for lease calculations when available
this accounts for ended leases as well. If present it has the actual end time of the lease. change column to RELEASE TIME to make sense for ended leases as well
1 parent 97b31dc commit 3abd886

3 files changed

Lines changed: 57 additions & 28 deletions

File tree

packages/jumpstarter/jumpstarter/client/grpc.py

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def add_display_columns(table, options: WithOptions = None):
3232
if options.show_leases:
3333
table.add_column("LEASED BY")
3434
table.add_column("LEASE STATUS")
35-
table.add_column("EXPECTED RELEASE")
35+
table.add_column("RELEASE TIME")
3636

3737

3838
def add_exporter_row(table, exporter, options: WithOptions = None, lease_info: tuple[str, str, str] | None = None):
@@ -97,15 +97,19 @@ def rich_add_rows(self, table, options: WithOptions = None):
9797
if options and options.show_leases and self.lease:
9898
lease_client = self.lease.client
9999
lease_status = self.lease.get_status()
100-
expected_release = ""
101-
if self.lease.effective_begin_time and self.lease.effective_duration:
102-
release_time = self.lease.effective_begin_time + self.lease.effective_duration
103-
expected_release = release_time.strftime("%Y-%m-%d %H:%M:%S")
100+
release_time = ""
101+
if self.lease.effective_end_time:
102+
# Ended: use actual end time
103+
release_time = self.lease.effective_end_time.strftime("%Y-%m-%d %H:%M:%S")
104+
elif self.lease.effective_begin_time:
105+
# Active: calculate expected end
106+
release_time = self.lease.effective_begin_time + self.lease.duration
107+
release_time = release_time.strftime("%Y-%m-%d %H:%M:%S")
104108
elif self.lease.begin_time:
105-
dur = self.lease.effective_duration or self.lease.duration
106-
release_time = self.lease.begin_time + dur
107-
expected_release = release_time.strftime("%Y-%m-%d %H:%M:%S")
108-
lease_info = (lease_client, lease_status, expected_release)
109+
# Scheduled: calculate expected end
110+
release_time = self.lease.begin_time + self.lease.duration
111+
release_time = release_time.strftime("%Y-%m-%d %H:%M:%S")
112+
lease_info = (lease_client, lease_status, release_time)
109113
elif options and options.show_leases:
110114
lease_info = ("", "Available", "")
111115
add_exporter_row(table, self, options, lease_info)
@@ -125,6 +129,7 @@ class Lease(BaseModel):
125129
exporter: str
126130
conditions: list[kubernetes_pb2.Condition]
127131
effective_begin_time: datetime | None = None
132+
effective_end_time: datetime | None = None
128133

129134
model_config = ConfigDict(
130135
arbitrary_types_allowed=True,
@@ -161,6 +166,12 @@ def from_protobuf(cls, data: client_pb2.Lease) -> Lease:
161166
tzinfo=datetime.now().astimezone().tzinfo,
162167
)
163168

169+
effective_end_time = None
170+
if data.HasField("effective_end_time"):
171+
effective_end_time = data.effective_end_time.ToDatetime(
172+
tzinfo=datetime.now().astimezone().tzinfo,
173+
)
174+
164175
return cls(
165176
namespace=namespace,
166177
name=name,
@@ -171,6 +182,7 @@ def from_protobuf(cls, data: client_pb2.Lease) -> Lease:
171182
client=client,
172183
exporter=exporter,
173184
effective_begin_time=effective_begin_time,
185+
effective_end_time=effective_end_time,
174186
conditions=data.conditions,
175187
)
176188

@@ -191,12 +203,8 @@ def rich_add_rows(self, table):
191203
elif self.begin_time:
192204
begin_time = self.begin_time.strftime("%Y-%m-%d %H:%M:%S")
193205

194-
# Show effective_duration if available, otherwise show requested duration
195-
duration = ""
196-
if self.effective_duration:
197-
duration = str(self.effective_duration)
198-
elif self.duration:
199-
duration = str(self.duration)
206+
# Show actual duration for ended leases, requested duration otherwise
207+
duration = str(self.effective_duration if self.effective_end_time else self.duration or "")
200208

201209
table.add_row(
202210
self.name,
@@ -212,6 +220,10 @@ def rich_add_names(self, names):
212220

213221
def get_status(self) -> str:
214222
"""Get the lease status based on conditions"""
223+
# Check if lease has ended (effective_end_time is set)
224+
if self.effective_end_time:
225+
return "Ended"
226+
215227
if not self.conditions:
216228
return "Unknown"
217229

packages/jumpstarter/jumpstarter/client/grpc_test.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,15 @@ def test_with_leases_columns(self):
4848
add_display_columns(table, options)
4949

5050
columns = [col.header for col in table.columns]
51-
assert columns == ["NAME", "LABELS", "LEASED BY", "LEASE STATUS", "EXPECTED RELEASE"]
51+
assert columns == ["NAME", "LABELS", "LEASED BY", "LEASE STATUS", "RELEASE TIME"]
5252

5353
def test_with_all_columns(self):
5454
table = Table()
5555
options = WithOptions(show_online=True, show_leases=True)
5656
add_display_columns(table, options)
5757

5858
columns = [col.header for col in table.columns]
59-
assert columns == ["NAME", "ONLINE", "LABELS", "LEASED BY", "LEASE STATUS", "EXPECTED RELEASE"]
59+
assert columns == ["NAME", "ONLINE", "LABELS", "LEASED BY", "LEASE STATUS", "RELEASE TIME"]
6060

6161

6262
class TestAddExporterRow:
@@ -91,7 +91,7 @@ def test_row_with_lease_info(self):
9191
add_exporter_row(table, exporter, options, lease_info)
9292

9393
assert len(table.rows) == 1
94-
assert len(table.columns) == 5 # NAME, LABELS, LEASED BY, LEASE STATUS, EXPECTED RELEASE
94+
assert len(table.columns) == 5 # NAME, LABELS, LEASED BY, LEASE STATUS, RELEASE TIME
9595

9696
def test_row_with_lease_info_available(self):
9797
table = Table()
@@ -115,21 +115,23 @@ def test_row_with_all_options(self):
115115
add_exporter_row(table, exporter, options, lease_info)
116116

117117
assert len(table.rows) == 1
118-
assert len(table.columns) == 6 # NAME, ONLINE, LABELS, LEASED BY, LEASE STATUS, EXPECTED RELEASE
118+
assert len(table.columns) == 6 # NAME, ONLINE, LABELS, LEASED BY, LEASE STATUS, RELEASE TIME
119119

120120

121121
class TestExporterList:
122122
def create_test_lease(self, client="test-client", status="Active",
123123
effective_begin_time=datetime(2023, 1, 1, 10, 0, 0),
124124
effective_duration=timedelta(hours=1),
125-
begin_time=None, duration=timedelta(hours=1)):
125+
begin_time=None, duration=timedelta(hours=1),
126+
effective_end_time=None):
126127
lease = Mock(spec=Lease)
127128
lease.client = client
128129
lease.get_status.return_value = status
129130
lease.effective_begin_time = effective_begin_time
130131
lease.effective_duration = effective_duration
131132
lease.begin_time = begin_time
132133
lease.duration = duration
134+
lease.effective_end_time = effective_end_time
133135
return lease
134136

135137
def test_exporter_without_lease(self):
@@ -181,7 +183,7 @@ def test_exporter_with_lease_display(self):
181183
exporter.rich_add_rows(table, options)
182184

183185
assert len(table.rows) == 1
184-
assert len(table.columns) == 5 # NAME, LABELS, LEASED BY, LEASE STATUS, EXPECTED RELEASE
186+
assert len(table.columns) == 5 # NAME, LABELS, LEASED BY, LEASE STATUS, RELEASE TIME
185187

186188
# Test actual table content by rendering it
187189
console = Console(file=StringIO(), width=120)
@@ -209,7 +211,7 @@ def test_exporter_without_lease_but_show_leases(self):
209211
exporter.rich_add_rows(table, options)
210212

211213
assert len(table.rows) == 1
212-
assert len(table.columns) == 5 # NAME, LABELS, LEASED BY, LEASE STATUS, EXPECTED RELEASE
214+
assert len(table.columns) == 5 # NAME, LABELS, LEASED BY, LEASE STATUS, RELEASE TIME
213215

214216
# Test actual table content by rendering it
215217
console = Console(file=StringIO(), width=120)
@@ -291,7 +293,7 @@ def test_exporter_all_features_display(self):
291293
exporter_offline_no_lease.rich_add_rows(table, options)
292294

293295
assert len(table.rows) == 2
294-
assert len(table.columns) == 6 # NAME, ONLINE, LABELS, LEASED BY, LEASE STATUS, EXPECTED RELEASE
296+
assert len(table.columns) == 6 # NAME, ONLINE, LABELS, LEASED BY, LEASE STATUS, RELEASE TIME
295297

296298
# Test actual table content by rendering it
297299
console = Console(file=StringIO(), width=150)
@@ -312,7 +314,11 @@ def test_exporter_all_features_display(self):
312314

313315
def test_exporter_lease_info_extraction(self):
314316
"""Test that lease information is correctly extracted from lease objects"""
315-
lease = self.create_test_lease(client="my-client", status="Expired")
317+
lease = self.create_test_lease(
318+
client="my-client",
319+
status="Expired",
320+
effective_end_time=datetime(2023, 1, 1, 11, 0, 0) # Ended after 1 hour
321+
)
316322
exporter = Exporter(
317323
namespace="default",
318324
name="test-exporter",
@@ -332,8 +338,16 @@ def test_exporter_lease_info_extraction(self):
332338
lease_client = exporter.lease.client
333339
lease_status = exporter.lease.get_status()
334340
expected_release = ""
335-
if exporter.lease.effective_begin_time and exporter.lease.effective_duration:
336-
release_time = exporter.lease.effective_begin_time + exporter.lease.effective_duration
341+
if exporter.lease.effective_end_time:
342+
# Ended: use actual end time
343+
expected_release = exporter.lease.effective_end_time.strftime("%Y-%m-%d %H:%M:%S")
344+
elif exporter.lease.effective_begin_time:
345+
# Active: calculate expected end
346+
release_time = exporter.lease.effective_begin_time + exporter.lease.duration
347+
expected_release = release_time.strftime("%Y-%m-%d %H:%M:%S")
348+
elif exporter.lease.begin_time:
349+
# Scheduled: calculate expected end
350+
release_time = exporter.lease.begin_time + exporter.lease.duration
337351
expected_release = release_time.strftime("%Y-%m-%d %H:%M:%S")
338352
lease_info = (lease_client, lease_status, expected_release)
339353

@@ -384,7 +398,7 @@ def test_exporter_scheduled_lease_expected_release(self):
384398
Exporter.rich_add_columns(table, options)
385399
exporter.rich_add_rows(table, options)
386400

387-
# Should have 5 columns: NAME, LABELS, LEASED BY, LEASE STATUS, EXPECTED RELEASE
401+
# Should have 5 columns: NAME, LABELS, LEASED BY, LEASE STATUS, RELEASE TIME
388402
assert len(table.columns) == 5
389403
assert len(table.rows) == 1
390404

packages/jumpstarter/jumpstarter/client/lease.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,10 @@ async def _monitor():
252252
while True:
253253
lease = await self.get()
254254
if lease.effective_begin_time and lease.effective_duration:
255-
end_time = lease.effective_begin_time + lease.effective_duration
255+
if lease.effective_end_time: # already ended
256+
end_time = lease.effective_end_time
257+
else:
258+
end_time = lease.effective_begin_time + lease.duration
256259
remain = end_time - datetime.now().astimezone()
257260
if remain < timedelta(0):
258261
# lease already expired, stopping monitor

0 commit comments

Comments
 (0)