Skip to content

Commit 70c5acb

Browse files
committed
feat: expose status_v3 in cli
1 parent c262e78 commit 70c5acb

3 files changed

Lines changed: 119 additions & 15 deletions

File tree

‎centml/cli/cluster.py‎

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,41 @@ def _get_ready_status(cclient, deployment):
103103
return click.style(style[0], fg=style[1], bg=style[2])
104104

105105

106+
def _get_status_error_messages(cclient, deployment):
107+
if deployment.status != DeploymentStatus.ACTIVE:
108+
return []
109+
110+
try:
111+
status = cclient.get_status_v3(deployment.id)
112+
except ApiException as e:
113+
if e.status not in [400, 404]:
114+
raise
115+
116+
status = cclient.get_status(deployment.id)
117+
error_message = getattr(status, "error_message", None)
118+
return [error_message] if error_message else []
119+
120+
messages = []
121+
seen_messages = set()
122+
123+
def add_message(label, error_message):
124+
if not error_message or error_message in seen_messages:
125+
return
126+
127+
seen_messages.add(error_message)
128+
messages.append(f"{label}: {error_message}")
129+
130+
for revision in status.revision_pod_details_list or []:
131+
revision_label = f"revision {revision.revision_number}" if revision.revision_number is not None else "revision"
132+
add_message(revision_label, revision.error_message)
133+
134+
for pod in revision.pod_details_list or []:
135+
pod_label = pod.name or "pod"
136+
add_message(f"{revision_label} / {pod_label}", pod.error_message)
137+
138+
return messages
139+
140+
106141
@click.command(help="List all deployments")
107142
@click.argument("type", type=click.Choice(list(depl_name_to_type_map.keys())), required=False, default=None)
108143
def ls(type):
@@ -150,23 +185,23 @@ def get(type, id):
150185
sys.exit("Please enter correct deployment type")
151186

152187
ready_status = _get_ready_status(cclient, deployment)
188+
status_error_messages = _get_status_error_messages(cclient, deployment)
153189
_, id_to_hw_map = _get_hw_to_id_map(cclient, deployment.cluster_id)
154190
hw = id_to_hw_map[deployment.hardware_instance_id]
155-
156-
click.echo(
157-
tabulate(
158-
[
159-
("Name", deployment.name),
160-
("Status", ready_status),
161-
("Endpoint", deployment.endpoint_url),
162-
("Created at", deployment.created_at.strftime("%Y-%m-%d %H:%M:%S")),
163-
("Hardware", f"{hw.name} ({hw.num_gpu}x {hw.gpu_type})"),
164-
("Cost", f"{hw.cost_per_hr / 100} credits/hr"),
165-
],
166-
tablefmt="rounded_outline",
167-
disable_numparse=True,
168-
)
169-
)
191+
detail_rows = [
192+
("Name", deployment.name),
193+
("Status", ready_status),
194+
("Endpoint", deployment.endpoint_url),
195+
("Created at", deployment.created_at.strftime("%Y-%m-%d %H:%M:%S")),
196+
("Hardware", f"{hw.name} ({hw.num_gpu}x {hw.gpu_type})"),
197+
("Cost", f"{hw.cost_per_hr / 100} credits/hr"),
198+
]
199+
200+
click.echo(tabulate(detail_rows, tablefmt="rounded_outline", disable_numparse=True))
201+
if status_error_messages:
202+
click.echo("\nStatus errors:")
203+
for message in status_error_messages:
204+
click.echo(f"- {message}")
170205

171206
click.echo("Additional deployment configurations:")
172207
if depl_type in [DeploymentType.INFERENCE_V2, DeploymentType.INFERENCE_V3]:

‎centml/compiler/prediction/__init__.py‎

Whitespace-only changes.

‎tests/test_cli_cluster.py‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from types import SimpleNamespace
2+
3+
import pytest
4+
5+
from centml.cli.cluster import DeploymentStatus, _get_status_error_messages
6+
from centml.sdk import ApiException
7+
8+
9+
def test_status_error_messages_include_revision_and_pod_messages():
10+
cclient = SimpleNamespace(
11+
get_status_v3=lambda _id: SimpleNamespace(
12+
revision_pod_details_list=[
13+
SimpleNamespace(
14+
revision_number=3,
15+
error_message="revision failed",
16+
pod_details_list=[
17+
SimpleNamespace(name="pod-a", error_message="image pull failed"),
18+
SimpleNamespace(name="pod-b", error_message=None),
19+
],
20+
)
21+
]
22+
)
23+
)
24+
deployment = SimpleNamespace(id=123, status=DeploymentStatus.ACTIVE)
25+
26+
messages = _get_status_error_messages(cclient, deployment)
27+
28+
assert messages == ["revision 3: revision failed", "revision 3 / pod-a: image pull failed"]
29+
30+
31+
def test_status_error_messages_do_not_repeat_duplicate_messages():
32+
duplicate_message = "one or more objects failed to apply"
33+
cclient = SimpleNamespace(
34+
get_status_v3=lambda _id: SimpleNamespace(
35+
revision_pod_details_list=[
36+
SimpleNamespace(
37+
revision_number=None,
38+
error_message=duplicate_message,
39+
pod_details_list=[SimpleNamespace(name=None, error_message=duplicate_message)],
40+
)
41+
]
42+
)
43+
)
44+
deployment = SimpleNamespace(id=123, status=DeploymentStatus.ACTIVE)
45+
46+
assert _get_status_error_messages(cclient, deployment) == [f"revision: {duplicate_message}"]
47+
48+
49+
def test_status_error_messages_fall_back_to_legacy_status_message():
50+
legacy_status = SimpleNamespace(error_message="legacy service failure")
51+
52+
def get_status_v3(_id):
53+
raise ApiException(status=404)
54+
55+
cclient = SimpleNamespace(get_status_v3=get_status_v3, get_status=lambda _id: legacy_status)
56+
deployment = SimpleNamespace(id=123, status=DeploymentStatus.ACTIVE)
57+
58+
assert _get_status_error_messages(cclient, deployment) == ["legacy service failure"]
59+
60+
61+
def test_status_error_messages_reraises_unexpected_v3_errors():
62+
def get_status_v3(_id):
63+
raise ApiException(status=500)
64+
65+
cclient = SimpleNamespace(get_status_v3=get_status_v3)
66+
deployment = SimpleNamespace(id=123, status=DeploymentStatus.ACTIVE)
67+
68+
with pytest.raises(ApiException):
69+
_get_status_error_messages(cclient, deployment)

0 commit comments

Comments
 (0)