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

Commit d9a76af

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.
1 parent 758e89b commit d9a76af

2 files changed

Lines changed: 51 additions & 22 deletions

File tree

packages/jumpstarter/jumpstarter/client/grpc.py

Lines changed: 34 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,11 @@ 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+
lease_info = (lease_client, lease_status, expected_release)
104105
elif options and options.show_leases:
105106
lease_info = ("", "Available", "")
106107
add_exporter_row(table, self, options, lease_info)
@@ -115,6 +116,7 @@ class Lease(BaseModel):
115116
selector: str
116117
duration: timedelta
117118
effective_duration: timedelta | None = None
119+
begin_time: datetime | None = None
118120
client: str
119121
exporter: str
120122
conditions: list[kubernetes_pb2.Condition]
@@ -143,6 +145,12 @@ def from_protobuf(cls, data: client_pb2.Lease) -> Lease:
143145
if data.effective_duration:
144146
effective_duration = data.effective_duration.ToTimedelta()
145147

148+
begin_time = None
149+
if data.HasField("begin_time"):
150+
begin_time = data.begin_time.ToDatetime(
151+
tzinfo=datetime.now().astimezone().tzinfo,
152+
)
153+
146154
effective_begin_time = None
147155
if data.effective_begin_time:
148156
effective_begin_time = data.effective_begin_time.ToDatetime(
@@ -155,6 +163,7 @@ def from_protobuf(cls, data: client_pb2.Lease) -> Lease:
155163
selector=data.selector,
156164
duration=data.duration.ToTimedelta(),
157165
effective_duration=effective_duration,
166+
begin_time=begin_time,
158167
client=client,
159168
exporter=exporter,
160169
effective_begin_time=effective_begin_time,
@@ -165,15 +174,31 @@ def from_protobuf(cls, data: client_pb2.Lease) -> Lease:
165174
def rich_add_columns(cls, table):
166175
table.add_column("NAME", no_wrap=True)
167176
table.add_column("SELECTOR")
177+
table.add_column("BEGIN TIME")
168178
table.add_column("DURATION")
169179
table.add_column("CLIENT")
170180
table.add_column("EXPORTER")
171181

172182
def rich_add_rows(self, table):
183+
# Show effective_begin_time if active, otherwise show scheduled begin_time
184+
begin_time = ""
185+
if self.effective_begin_time:
186+
begin_time = self.effective_begin_time.strftime("%Y-%m-%d %H:%M:%S")
187+
elif self.begin_time:
188+
begin_time = self.begin_time.strftime("%Y-%m-%d %H:%M:%S")
189+
190+
# Show effective_duration if available, otherwise show requested duration
191+
duration = ""
192+
if self.effective_duration:
193+
duration = str(self.effective_duration)
194+
elif self.duration:
195+
duration = str(self.duration)
196+
173197
table.add_row(
174198
self.name,
175199
self.selector,
176-
str(self.duration),
200+
begin_time,
201+
duration,
177202
self.client,
178203
self.exporter,
179204
)

packages/jumpstarter/jumpstarter/client/grpc_test.py

Lines changed: 17 additions & 13 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,7 +115,7 @@ 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:
@@ -124,6 +124,7 @@ def create_test_lease(self, client="test-client", status="Active"):
124124
lease.client = client
125125
lease.get_status.return_value = status
126126
lease.effective_begin_time = datetime(2023, 1, 1, 10, 0, 0)
127+
lease.effective_duration = timedelta(hours=1)
127128
return lease
128129

129130
def test_exporter_without_lease(self):
@@ -175,7 +176,7 @@ def test_exporter_with_lease_display(self):
175176
exporter.rich_add_rows(table, options)
176177

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

180181
# Test actual table content by rendering it
181182
console = Console(file=StringIO(), width=120)
@@ -187,7 +188,7 @@ def test_exporter_with_lease_display(self):
187188
assert "type=device" in output
188189
assert "test-client" in output
189190
assert "Active" in output
190-
assert "2023-01-01 10:00:00" in output
191+
assert "2023-01-01 11:00:00" in output # Expected release: begin_time (10:00:00) + duration (1h)
191192

192193
def test_exporter_without_lease_but_show_leases(self):
193194
exporter = Exporter(
@@ -203,7 +204,7 @@ def test_exporter_without_lease_but_show_leases(self):
203204
exporter.rich_add_rows(table, options)
204205

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

208209
# Test actual table content by rendering it
209210
console = Console(file=StringIO(), width=120)
@@ -285,7 +286,7 @@ def test_exporter_all_features_display(self):
285286
exporter_offline_no_lease.rich_add_rows(table, options)
286287

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

290291
# Test actual table content by rendering it
291292
console = Console(file=StringIO(), width=150)
@@ -302,7 +303,7 @@ def test_exporter_all_features_display(self):
302303
assert "full-test-client" in output # Lease client
303304
assert "Active" in output # Lease status
304305
assert "Available" in output # Available status for no lease
305-
assert "2023-01-01 10:00:00" in output # Lease start time
306+
assert "2023-01-01 11:00:00" in output # Expected release time (begin_time + duration)
306307

307308
def test_exporter_lease_info_extraction(self):
308309
"""Test that lease information is correctly extracted from lease objects"""
@@ -325,10 +326,13 @@ def test_exporter_lease_info_extraction(self):
325326
if options.show_leases and exporter.lease:
326327
lease_client = exporter.lease.client
327328
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)
329+
expected_release = ""
330+
if exporter.lease.effective_begin_time and exporter.lease.effective_duration:
331+
release_time = exporter.lease.effective_begin_time + exporter.lease.effective_duration
332+
expected_release = release_time.strftime("%Y-%m-%d %H:%M:%S")
333+
lease_info = (lease_client, lease_status, expected_release)
330334

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

333337
def test_exporter_no_lease_info_extraction(self):
334338
"""Test that default lease information is used when no lease exists"""

0 commit comments

Comments
 (0)