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

Commit ffee9fa

Browse files
show lease expected release time instead of start time
"jmp get exporters --with leases" shows lease status and when is the lease expiring. That's more useful that current start time which doesn't tell you for how long it may be held. "jmp get leases" now shows lease begin time (the time it was acquired) in addition to duration. future/scheduled leases show their expected begin time and duration, currently active leases show actual begin time and actual duration so far. (cherry picked from commit f1a1aec)
1 parent 624ad12 commit ffee9fa

2 files changed

Lines changed: 101 additions & 24 deletions

File tree

packages/jumpstarter/jumpstarter/client/grpc.py

Lines changed: 38 additions & 9 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("START TIME")
35+
table.add_column("EXPECTED RELEASE")
3636

3737

3838
def add_exporter_row(table, exporter, options: WithOptions = None, lease_info: tuple[str, str, str] | None = None):
@@ -45,10 +45,10 @@ def add_exporter_row(table, exporter, options: WithOptions = None, lease_info: t
4545
row_data.append(",".join(("{}={}".format(k, v) for k, v in sorted(exporter.labels.items()))))
4646
if options.show_leases:
4747
if lease_info:
48-
lease_client, lease_status, start_time = lease_info
48+
lease_client, lease_status, expected_release = lease_info
4949
else:
50-
lease_client, lease_status, start_time = "", "Available", ""
51-
row_data.extend([lease_client, lease_status, start_time])
50+
lease_client, lease_status, expected_release = "", "Available", ""
51+
row_data.extend([lease_client, lease_status, expected_release])
5252

5353
table.add_row(*row_data)
5454

@@ -97,10 +97,15 @@ 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-
start_time = ""
101-
if self.lease.effective_begin_time:
102-
start_time = self.lease.effective_begin_time.strftime("%Y-%m-%d %H:%M:%S")
103-
lease_info = (lease_client, lease_status, start_time)
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")
104+
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)
104109
elif options and options.show_leases:
105110
lease_info = ("", "Available", "")
106111
add_exporter_row(table, self, options, lease_info)
@@ -115,6 +120,7 @@ class Lease(BaseModel):
115120
selector: str
116121
duration: timedelta
117122
effective_duration: timedelta | None = None
123+
begin_time: datetime | None = None
118124
client: str
119125
exporter: str
120126
conditions: list[kubernetes_pb2.Condition]
@@ -143,6 +149,12 @@ def from_protobuf(cls, data: client_pb2.Lease) -> Lease:
143149
if data.HasField("effective_duration"):
144150
effective_duration = data.effective_duration.ToTimedelta()
145151

152+
begin_time = None
153+
if data.HasField("begin_time"):
154+
begin_time = data.begin_time.ToDatetime(
155+
tzinfo=datetime.now().astimezone().tzinfo,
156+
)
157+
146158
effective_begin_time = None
147159
if data.HasField("effective_begin_time"):
148160
effective_begin_time = data.effective_begin_time.ToDatetime(
@@ -155,6 +167,7 @@ def from_protobuf(cls, data: client_pb2.Lease) -> Lease:
155167
selector=data.selector,
156168
duration=data.duration.ToTimedelta(),
157169
effective_duration=effective_duration,
170+
begin_time=begin_time,
158171
client=client,
159172
exporter=exporter,
160173
effective_begin_time=effective_begin_time,
@@ -165,15 +178,31 @@ def from_protobuf(cls, data: client_pb2.Lease) -> Lease:
165178
def rich_add_columns(cls, table):
166179
table.add_column("NAME", no_wrap=True)
167180
table.add_column("SELECTOR")
181+
table.add_column("BEGIN TIME")
168182
table.add_column("DURATION")
169183
table.add_column("CLIENT")
170184
table.add_column("EXPORTER")
171185

172186
def rich_add_rows(self, table):
187+
# Show effective_begin_time if active, otherwise show scheduled begin_time
188+
begin_time = ""
189+
if self.effective_begin_time:
190+
begin_time = self.effective_begin_time.strftime("%Y-%m-%d %H:%M:%S")
191+
elif self.begin_time:
192+
begin_time = self.begin_time.strftime("%Y-%m-%d %H:%M:%S")
193+
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)
200+
173201
table.add_row(
174202
self.name,
175203
self.selector,
176-
str(self.duration),
204+
begin_time,
205+
duration,
177206
self.client,
178207
self.exporter,
179208
)

packages/jumpstarter/jumpstarter/client/grpc_test.py

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import datetime
1+
from datetime import datetime, timedelta
22
from io import StringIO
33
from unittest.mock import Mock
44

@@ -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", "START TIME"]
51+
assert columns == ["NAME", "LABELS", "LEASED BY", "LEASE STATUS", "EXPECTED RELEASE"]
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", "START TIME"]
59+
assert columns == ["NAME", "ONLINE", "LABELS", "LEASED BY", "LEASE STATUS", "EXPECTED RELEASE"]
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, START TIME
94+
assert len(table.columns) == 5 # NAME, LABELS, LEASED BY, LEASE STATUS, EXPECTED RELEASE
9595

9696
def test_row_with_lease_info_available(self):
9797
table = Table()
@@ -115,15 +115,21 @@ 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, START TIME
118+
assert len(table.columns) == 6 # NAME, ONLINE, LABELS, LEASED BY, LEASE STATUS, EXPECTED RELEASE
119119

120120

121121
class TestExporterList:
122-
def create_test_lease(self, client="test-client", status="Active"):
122+
def create_test_lease(self, client="test-client", status="Active",
123+
effective_begin_time=datetime(2023, 1, 1, 10, 0, 0),
124+
effective_duration=timedelta(hours=1),
125+
begin_time=None, duration=timedelta(hours=1)):
123126
lease = Mock(spec=Lease)
124127
lease.client = client
125128
lease.get_status.return_value = status
126-
lease.effective_begin_time = datetime(2023, 1, 1, 10, 0, 0)
129+
lease.effective_begin_time = effective_begin_time
130+
lease.effective_duration = effective_duration
131+
lease.begin_time = begin_time
132+
lease.duration = duration
127133
return lease
128134

129135
def test_exporter_without_lease(self):
@@ -175,7 +181,7 @@ def test_exporter_with_lease_display(self):
175181
exporter.rich_add_rows(table, options)
176182

177183
assert len(table.rows) == 1
178-
assert len(table.columns) == 5 # NAME, LABELS, LEASED BY, LEASE STATUS, START TIME
184+
assert len(table.columns) == 5 # NAME, LABELS, LEASED BY, LEASE STATUS, EXPECTED RELEASE
179185

180186
# Test actual table content by rendering it
181187
console = Console(file=StringIO(), width=120)
@@ -187,7 +193,7 @@ def test_exporter_with_lease_display(self):
187193
assert "type=device" in output
188194
assert "test-client" in output
189195
assert "Active" in output
190-
assert "2023-01-01 10:00:00" in output
196+
assert "2023-01-01 11:00:00" in output # Expected release: begin_time (10:00:00) + duration (1h)
191197

192198
def test_exporter_without_lease_but_show_leases(self):
193199
exporter = Exporter(
@@ -203,7 +209,7 @@ def test_exporter_without_lease_but_show_leases(self):
203209
exporter.rich_add_rows(table, options)
204210

205211
assert len(table.rows) == 1
206-
assert len(table.columns) == 5 # NAME, LABELS, LEASED BY, LEASE STATUS, START TIME
212+
assert len(table.columns) == 5 # NAME, LABELS, LEASED BY, LEASE STATUS, EXPECTED RELEASE
207213

208214
# Test actual table content by rendering it
209215
console = Console(file=StringIO(), width=120)
@@ -285,7 +291,7 @@ def test_exporter_all_features_display(self):
285291
exporter_offline_no_lease.rich_add_rows(table, options)
286292

287293
assert len(table.rows) == 2
288-
assert len(table.columns) == 6 # NAME, ONLINE, LABELS, LEASED BY, LEASE STATUS, START TIME
294+
assert len(table.columns) == 6 # NAME, ONLINE, LABELS, LEASED BY, LEASE STATUS, EXPECTED RELEASE
289295

290296
# Test actual table content by rendering it
291297
console = Console(file=StringIO(), width=150)
@@ -302,7 +308,7 @@ def test_exporter_all_features_display(self):
302308
assert "full-test-client" in output # Lease client
303309
assert "Active" in output # Lease status
304310
assert "Available" in output # Available status for no lease
305-
assert "2023-01-01 10:00:00" in output # Lease start time
311+
assert "2023-01-01 11:00:00" in output # Expected release time (begin_time + duration)
306312

307313
def test_exporter_lease_info_extraction(self):
308314
"""Test that lease information is correctly extracted from lease objects"""
@@ -325,10 +331,13 @@ def test_exporter_lease_info_extraction(self):
325331
if options.show_leases and exporter.lease:
326332
lease_client = exporter.lease.client
327333
lease_status = exporter.lease.get_status()
328-
start_time = exporter.lease.effective_begin_time.strftime("%Y-%m-%d %H:%M:%S")
329-
lease_info = (lease_client, lease_status, start_time)
334+
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
337+
expected_release = release_time.strftime("%Y-%m-%d %H:%M:%S")
338+
lease_info = (lease_client, lease_status, expected_release)
330339

331-
assert lease_info == ("my-client", "Expired", "2023-01-01 10:00:00")
340+
assert lease_info == ("my-client", "Expired", "2023-01-01 11:00:00")
332341

333342
def test_exporter_no_lease_info_extraction(self):
334343
"""Test that default lease information is used when no lease exists"""
@@ -351,4 +360,43 @@ def test_exporter_no_lease_info_extraction(self):
351360
lease_info = ("", "Available", "")
352361
assert lease_info == ("", "Available", "")
353362

363+
def test_exporter_scheduled_lease_expected_release(self):
364+
"""Test that scheduled leases show expected release time"""
365+
lease = self.create_test_lease(
366+
client="my-client",
367+
status="Scheduled",
368+
effective_begin_time=None, # Not started yet
369+
effective_duration=None, # Not started yet
370+
begin_time=datetime(2023, 1, 1, 10, 0, 0),
371+
duration=timedelta(hours=1)
372+
)
373+
exporter = Exporter(
374+
namespace="default",
375+
name="test-exporter",
376+
labels={"type": "device"},
377+
online=True,
378+
lease=lease
379+
)
380+
381+
# Test the table display with scheduled lease
382+
table = Table()
383+
options = WithOptions(show_leases=True)
384+
Exporter.rich_add_columns(table, options)
385+
exporter.rich_add_rows(table, options)
386+
387+
# Should have 5 columns: NAME, LABELS, LEASED BY, LEASE STATUS, EXPECTED RELEASE
388+
assert len(table.columns) == 5
389+
assert len(table.rows) == 1
390+
391+
# Test actual table content by rendering it
392+
console = Console(file=StringIO(), width=120)
393+
console.print(table)
394+
output = console.file.getvalue()
395+
396+
# Verify the scheduled lease displays expected release time
397+
assert "test-exporter" in output
398+
assert "my-client" in output
399+
assert "Scheduled" in output
400+
assert "2023-01-01 11:00:00" in output # begin_time (10:00) + duration (1h)
401+
354402

0 commit comments

Comments
 (0)