badgr-cli 1.1.6 → 1.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,243 @@
1
+ """
2
+ Unit tests for node_worker.py's three additions to the customer_node BYO GPU
3
+ worker (see backend/customer_node_adapter.py / backend/node_routes.py for
4
+ the control-plane side of the same feature):
5
+ - privileged/host diagnostic execution (docker flags only, no live docker)
6
+ - hard timeout enforcement in the job-streaming loop
7
+ - structured BADGR_RESULT= parsing
8
+
9
+ No real docker/network calls are made -- subprocess.Popen/run are stubbed.
10
+ """
11
+ import os
12
+ import sys
13
+ import time
14
+
15
+ sys.path.insert(0, os.path.dirname(__file__))
16
+
17
+ import node_worker # noqa: E402
18
+
19
+
20
+ def test_parse_diagnostic_result_pass():
21
+ log = "=== RESULT ===\nBADGR_RESULT=PASS\n"
22
+ assert node_worker._parse_diagnostic_result(log) == "PASS"
23
+
24
+
25
+ def test_parse_diagnostic_result_fail():
26
+ log = "some noise\nBADGR_RESULT=FAIL\nmore noise after\n"
27
+ assert node_worker._parse_diagnostic_result(log) == "FAIL"
28
+
29
+
30
+ def test_parse_diagnostic_result_absent_for_ordinary_job():
31
+ assert node_worker._parse_diagnostic_result("epoch 1/1 done\n") is None
32
+
33
+
34
+ def test_privileged_plan_adds_host_diagnostic_docker_flags(monkeypatch):
35
+ captured_cmds = []
36
+
37
+ class _FakeProc:
38
+ pid = 4242
39
+ returncode = 0
40
+ stdout = None
41
+
42
+ def poll(self):
43
+ return 0
44
+
45
+ def fake_popen(cmd, **kwargs):
46
+ captured_cmds.append(cmd)
47
+ return _FakeProc()
48
+
49
+ def fake_run(cmd, **kwargs):
50
+ captured_cmds.append(cmd)
51
+
52
+ class _R:
53
+ returncode = 0
54
+ return _R()
55
+
56
+ reports = []
57
+ monkeypatch.setattr(node_worker.subprocess, "Popen", fake_popen)
58
+ monkeypatch.setattr(node_worker.subprocess, "run", fake_run)
59
+ monkeypatch.setattr(node_worker, "_api", lambda *a, **kw: reports.append(kw) or {})
60
+
61
+ node_worker._run_job(
62
+ "https://x/v1", "key", "node_1", "job_1",
63
+ {"image": "badgr/diagnostics:latest", "command": ["./nvlink_p2p_check.sh"], "privileged": True},
64
+ )
65
+
66
+ docker_cmd = captured_cmds[0]
67
+ assert "--privileged" in docker_cmd
68
+ assert "--pid=host" in docker_cmd
69
+ assert "/dev:/dev" in docker_cmd
70
+
71
+
72
+ def test_ordinary_job_has_no_privileged_flags(monkeypatch):
73
+ captured_cmds = []
74
+
75
+ class _FakeProc:
76
+ pid = 4242
77
+ returncode = 0
78
+ stdout = None
79
+
80
+ def poll(self):
81
+ return 0
82
+
83
+ monkeypatch.setattr(node_worker.subprocess, "Popen", lambda cmd, **kw: captured_cmds.append(cmd) or _FakeProc())
84
+ monkeypatch.setattr(node_worker, "_api", lambda *a, **kw: {})
85
+
86
+ node_worker._run_job(
87
+ "https://x/v1", "key", "node_1", "job_2",
88
+ {"image": "python:3.11-slim", "command": ["python", "train.py"]},
89
+ )
90
+
91
+ assert "--privileged" not in captured_cmds[0]
92
+
93
+
94
+ def test_nvidia_job_uses_gpus_all_flag_by_default(monkeypatch):
95
+ captured_cmds = []
96
+
97
+ class _FakeProc:
98
+ pid = 4242
99
+ returncode = 0
100
+ stdout = None
101
+
102
+ def poll(self):
103
+ return 0
104
+
105
+ monkeypatch.setattr(node_worker.subprocess, "Popen", lambda cmd, **kw: captured_cmds.append(cmd) or _FakeProc())
106
+ monkeypatch.setattr(node_worker, "_api", lambda *a, **kw: {})
107
+
108
+ node_worker._run_job(
109
+ "https://x/v1", "key", "node_1", "job_4",
110
+ {"image": "python:3.11-slim", "command": ["python", "train.py"]},
111
+ "nvidia",
112
+ )
113
+
114
+ docker_cmd = captured_cmds[0]
115
+ assert "--gpus" in docker_cmd and "all" in docker_cmd
116
+ assert "--device=/dev/kfd" not in docker_cmd
117
+
118
+
119
+ def test_amd_job_uses_rocm_device_flags(monkeypatch):
120
+ captured_cmds = []
121
+
122
+ class _FakeProc:
123
+ pid = 4242
124
+ returncode = 0
125
+ stdout = None
126
+
127
+ def poll(self):
128
+ return 0
129
+
130
+ monkeypatch.setattr(node_worker.subprocess, "Popen", lambda cmd, **kw: captured_cmds.append(cmd) or _FakeProc())
131
+ monkeypatch.setattr(node_worker, "_api", lambda *a, **kw: {})
132
+
133
+ node_worker._run_job(
134
+ "https://x/v1", "key", "node_1", "job_5",
135
+ {"image": "rocm/vllm:latest", "command": ["python", "train.py"]},
136
+ "amd",
137
+ )
138
+
139
+ docker_cmd = captured_cmds[0]
140
+ assert "--device=/dev/kfd" in docker_cmd
141
+ assert "--device=/dev/dri" in docker_cmd
142
+ assert "--gpus" not in docker_cmd
143
+
144
+
145
+ def test_gpu_docker_args_defaults_to_nvidia_flag_when_vendor_unknown():
146
+ assert node_worker._gpu_docker_args(None) == ["--gpus", "all"]
147
+
148
+
149
+ def test_mount_docker_args_builds_v_flags():
150
+ args = node_worker._mount_docker_args([
151
+ "/home/faisal/venvs/vllm:/opt/venv",
152
+ "/home/faisal/.cache:/root/.cache:ro",
153
+ ])
154
+ assert args == [
155
+ "-v", "/home/faisal/venvs/vllm:/opt/venv",
156
+ "-v", "/home/faisal/.cache:/root/.cache:ro",
157
+ ]
158
+
159
+
160
+ def test_mount_docker_args_empty_when_no_mounts():
161
+ assert node_worker._mount_docker_args(None) == []
162
+ assert node_worker._mount_docker_args([]) == []
163
+
164
+
165
+ def test_parse_mount_rejects_relative_path():
166
+ import pytest
167
+ with pytest.raises(ValueError):
168
+ node_worker._parse_mount("relative/path:/container/path")
169
+
170
+
171
+ def test_parse_mount_rejects_malformed_spec():
172
+ import pytest
173
+ with pytest.raises(ValueError):
174
+ node_worker._parse_mount("/only/one/path")
175
+
176
+
177
+ def test_run_job_includes_configured_mounts_in_docker_command(monkeypatch):
178
+ captured_cmds = []
179
+
180
+ class _FakeProc:
181
+ pid = 4242
182
+ returncode = 0
183
+ stdout = None
184
+
185
+ def poll(self):
186
+ return 0
187
+
188
+ monkeypatch.setattr(node_worker.subprocess, "Popen", lambda cmd, **kw: captured_cmds.append(cmd) or _FakeProc())
189
+ monkeypatch.setattr(node_worker, "_api", lambda *a, **kw: {})
190
+
191
+ node_worker._run_job(
192
+ "https://x/v1", "key", "node_1", "job_6",
193
+ {"image": "vllm/vllm-openai:latest", "command": ["python", "-m", "vllm.entrypoints.openai.api_server"]},
194
+ "nvidia",
195
+ ["/home/faisal/vllm-src:/opt/vllm-src", "/sync/Models:/models:ro"],
196
+ )
197
+
198
+ docker_cmd = captured_cmds[0]
199
+ assert "-v" in docker_cmd
200
+ assert "/home/faisal/vllm-src:/opt/vllm-src" in docker_cmd
201
+ assert "/sync/Models:/models:ro" in docker_cmd
202
+
203
+
204
+ def test_job_exceeding_max_runtime_is_killed_and_reported(monkeypatch):
205
+ """A hung diagnostic must not wedge the worker forever -- the loop kills
206
+ the named container once max_runtime_seconds elapses and reports a
207
+ FAIL result rather than hanging indefinitely."""
208
+ kill_calls = []
209
+
210
+ class _FakeProc:
211
+ pid = 4242
212
+ returncode = None
213
+ stdout = None
214
+
215
+ def poll(self):
216
+ return None # never exits on its own -- simulates a hang
217
+
218
+ def wait(self):
219
+ self.returncode = -9
220
+
221
+ fake_proc = _FakeProc()
222
+ monkeypatch.setattr(node_worker.subprocess, "Popen", lambda cmd, **kw: fake_proc)
223
+ monkeypatch.setattr(node_worker.subprocess, "run", lambda cmd, **kw: kill_calls.append(cmd))
224
+
225
+ # Force the deadline to already be in the past on the very first loop
226
+ # check, instead of sleeping in the test.
227
+ clock = iter([1000.0, 1000.0, 2000.0])
228
+ monkeypatch.setattr(node_worker.time, "monotonic", lambda: next(clock))
229
+
230
+ reports = []
231
+ monkeypatch.setattr(node_worker, "_api", lambda *a, **kw: reports.append(a[-1]) or {})
232
+
233
+ node_worker._run_job(
234
+ "https://x/v1", "key", "node_1", "job_3",
235
+ {"image": "badgr/diagnostics:latest", "privileged": True, "max_runtime_seconds": 5},
236
+ )
237
+
238
+ assert any("badgr-job-job_3" in cmd for cmd in kill_calls)
239
+ assert reports, "worker must report a status even on timeout"
240
+ report = reports[0]
241
+ assert report["status"] == "error"
242
+ assert "timed out" in report["error"]
243
+ assert report["result"] == "FAIL"
package/src/progress.js CHANGED
@@ -101,7 +101,7 @@ export async function pollJobUntilTerminal(callApi, config, jobId, { chalk, maxM
101
101
 
102
102
  // One-time announcement of the automatic retry-on-a-different-route —
103
103
  // printed once per retry (not every poll tick) so it reads as an event,
104
- // not a repeated status line. See job_progress.py's retrying_different_route
104
+ // not a repeated status line. See the backend's own retrying_different_route
105
105
  // stage — this is what "one safe retry" looks like from the CLI.
106
106
  if (detail.stage === 'retrying_different_route' && !announcedRetry) {
107
107
  announcedRetry = true;
@@ -148,7 +148,7 @@ function _formatElapsed(seconds) {
148
148
  * Prints the `Class:`/`Next:` lines for a failed GpuDeployment (the raw
149
149
  * /deployments/{id} path used by `badgr run`/`badgr serve`/`badgr comfyui run`)
150
150
  * using the exact same failure_class/next_action fields — computed by the
151
- * same backend/job_progress.py classify_failure()/next_step_for() functions —
151
+ * same backend classify_failure()/next_step_for() functions —
152
152
  * that renderJobClosingBlock below already prints for the productized
153
153
  * /v1/jobs path (comfy.batch, train.lora, custom.run, model.serve). One
154
154
  * failure-class vocabulary shown the same way regardless of which API path
package/src/spec.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Deployment spec: the single document that describes what `gpu up` should provision.
3
- * Mirrors the concepts in overflow_dispatch.py (gpu_type, region, workload type).
3
+ * Mirrors the backend's own deployment-dispatch concepts (gpu_type, region, workload type).
4
4
  */
5
5
 
6
6
  export const WORKLOAD_TYPES = ['endpoint', 'job'];
@@ -26,7 +26,7 @@ export function vmClassForWorkload(workloadName) {
26
26
  return workloadName === 'playwright' ? 'browser' : 'small';
27
27
  }
28
28
 
29
- // Match canonical GPU IDs used in overflow_providers.py GPU_ALIASES
29
+ // Match canonical GPU IDs used in the backend's own GPU_ALIASES
30
30
  export const GPU_TYPE_MAP = {
31
31
  'rtx-4090': 'RTX_4090', 'rtx4090': 'RTX_4090', '4090': 'RTX_4090',
32
32
  'rtx-3090': 'RTX_3090', 'rtx3090': 'RTX_3090', '3090': 'RTX_3090',