fix(doctor): the node-capacity check compares the free memory it already computes, and tells a permanent shortage from a transient one (backend#2870) - #639
Conversation
…ady computes, and tells a permanent shortage from a transient one (backend#2870) Node capacity: batch-Job pods (the `job-name` label the CLI already uses to find a training pod) go into their own per-node sum instead of being dropped. The steady-state fit still decides the PERMANENT Fail (OverCommitted, now with the node's free figures); a node that fits beside the platform but not beside its running Jobs is the new TRANSIENT Warn (HeldByRunningJob) with the opposite remedy — wait or stop the job, do not resize. The rollup gets a matching arm so the top line says the next run waits instead of a green. Machine capacity: requestedMemory returns its read error instead of 0, so an unreadable pod list renders "unrequested: unknown" and StatusUnknown rather than the whole VM as free under a check-mark. The over-commit Warn, which needs no pod list, still fires. VERSION 0.10.24 (v0.10.23 is tagged). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
bugbot run |
…ot enough compute" (backend#2870) Bugbot High on #639: the transient shortage's own symptom is a second training pod Pending until the running job frees the room, and the stuck-Pending Fail arm matched it first -- "Not ready ... not enough free compute" with `resources set max` at exit 2, in exactly the case the HeldByRunningJob Warn was written for. A combined arm now sits above the stuck-Pending arm: when Pod health reports a pod Pending past grace AND Node capacity has measured that a running job holds the room, the top line says the next training is waiting for the running one, the remedy says wait or stop the job and that resizing will not help, and -- since checkPods cannot see WHY a pod is Pending -- what to do if the wait outlives the job. Warn, not Fail: the inference "Pending so cannot schedule" is refuted by the measured cause, and a Fail would exit 2 on healthy training (the Bugbot High on #628). A Pod-health FAIL and the OverCommitted Fail still outrank it; a Pending pod with no running job keeps the generic Fail and its sizing advice. Mutation: moving the combined arm back below the stuck-Pending arm reddens the new both-signals test with Bugbot's exact output. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit a14acfc. Configure here.
saqlainsyed007
left a comment
There was a problem hiding this comment.
Reviewed the full diff against the source. The core fix is correct: node-capacity now compares the free figure it computes (allocatable − steady-state requests) against the request, keeps running batch-Job requests in a separate per-node sum, and tells a permanent over-commit (Fail, OverCommitted) from a transient job-held shortage (Warn, HeldByRunningJob). Operands, units (millicores/bytes on both sides), and comparison direction (free >= req to fit, free < req to flag) are all right, and the equal-boundary is consistent between the fit and over-commit predicates — no off-by-one. The rollup arms are ordered correctly and the Bugbot High (Pending pod shadowing the transient Warn) is addressed by the stuckPending && heldByJob arm with a test. machine.go's fail-open (pod-list error → whole VM as "unrequested" under a green) is properly closed to StatusUnknown while preserving the pod-list-free over-commit Warn. Tests are mutation-proven (four reverting mutations each redden). CI green, Bugbot clean. LGTM.
Summary
cli doctor's Node capacity check now uses the free-memory figure it computes as the verdict, and tells a permanent shortage from a transient one. Part of tracebloc/backend#2870 (DoD part 3).What was already on
developfrom #628: the fit is evaluated per node against free (allocatable − steady-state requests), for cpu and memory, with aCantVerifyFreeComputeWarn when the pod list cannot be read. This PR closes the two gaps that were left:job-namelabel the batch/v1 controller stamps — the same labelinternal/submit/watch.goalready uses to find a training pod; no new label) now go into a separate per-node sum. Two sums, two verdicts:StatusFail, prefixOverCommitted(unchanged), now printing the node's actual free figures beside the request. Remedy: lowerRESOURCE_REQUESTS/ bigger node.StatusWarn, new prefixHeldByRunningJob. Remedy: let the job finish or stop it; resizing does not help. The rollup (summarizeDoctor) gets a matching arm so the top line says the next run waits instead of printing "Ready to run training / Everything looks good".machine.go's fourth level failed open. The line the ticket quotes (freeMem := trueCeiling - requestedMemory(ctx, cs)) hadrequestedMemoryswallow a pod-list error into0, so an unreadable list printed the whole VM as "unrequested" under a ✔. It now returns the error; the chain printsunrequested: unknown (pod list unreadable)and the check isStatusUnknownwith the read error — while the over-commit Warn (which needs no pod list) still fires.Why the comparison lives in Node capacity and not in the machine chain. A pod takes every resource it requests from one node, so schedulability is a per-node question. On a capped two-node cluster the VM can have 4 GiB unrequested while no single node has 2 GiB free — a VM-level "fits" would be wrong in the direction that goes Pending. On an honest single-node cluster the two figures are identical, so a second comparison would be a duplicate finding with different wording. The machine chain now says so in a comment and owes the operator honesty about the number, not a verdict.
Why Warn for transient, not Fail. Doctor's vocabulary: Fail = training-blocking (exit 2), Warn = a real finding, training still runs (exit 0), Unknown = no signal. A run in progress is the healthy state — failing on it was the Bugbot High on #628 — but a green hides why a second run is
waiting_for_capacity. Warn is the honest middle, and it is what the Machine-capacity over-commit already uses for the same reason.Before / after — the ticket's 8 GiB reproduction
k3d node
8126672Ki(7.75 GiB); installer envelope = allocatable − 3 GiB =5100486656B (4864.2 Mi); platform requests 3008 Mi (chart) + 140 Mi (k3s) = 3148 Mi → free 4788.2 Mi → the envelope over-asks by 76.0 Mi.developtoday (#628)a Ready node can schedule a training job (…)a Ready node is large enough … over-asks the node's FREE memory, so the pod schedules Pendinga Ready node is large enough for a training job (cpu=1, memory=5100486656) but not beside what is already running on it — the envelope over-asks the node's FREE memory (k3d-tracebloc-server-0 has cpu=4, memory=4903120Ki free beside the platform's own pods), so the pod schedules Pendinga Ready node can schedule a training job (…)a running job holds the room: a Ready node fits a training job (cpu=1, memory=5100486656) beside the platform's own pods, but running job(s) on k3d-tracebloc-server-0 hold cpu=1, memory=4980944Ki right now, so the next run waits Pending until they finish— rollup: Ready to run training — but a job is already using this machine's free compute, so the next run waits for it.… → 7.75 GiB unrequested… → 7.75 GiB unrequested… → unrequested: unknown (pod list unreadable) — could not list pods: <err>(Lines produced by running
checkNodeFit/checkMachineChainagainst fake clientsets built with exactly these numbers; no 8 GiB k3d node was available in this session, so the k3d/k3s output itself is not re-measured here.)CPU
Covered: #628 already reads the cpu request; the transient sum covers cpu and memory the same way (
transient on cpu alone is still the transient Warnin the table).Tests
internal/doctor:TestCheckNodeFitPermanentVsTransient— table-driven with the ticket's numbers: fits → OK; permanent → Fail withOverCommittedprefix and free figures; transient → Warn withHeldByRunningJobprefix; transient on cpu; job on another node → OK; permanent + running job → permanent wins; non-Job pod holding the envelope → permanent; transient + GPU absent → transient wins, GPU fact kept; pod list unreadable → not OK,CantVerifyFreeComputeprefix; and the two remedies are pinned as different. The existing "running job excluded → OK" subtest is rewritten to "never Fail, is the transient Warn" — that behaviour change is deliberate and is the point.internal/doctor:TestMachineChain_UnreadablePodListIsNotAGreen,TestMachineChain_OverCommitStillWarnsWithoutAPodList.internal/cli: rollup cases — transient Warn is a ready-Warn that says wait (noset max, no "ask for less"), verdict is Warn; the over-commit Warn still outranks it. Copy-catalog golden regenerated; the diff is exactly the eight strings this PR introduces.make ciexit 0 (vet,go test -race -cover ./..., errcheck/ineffassign/misspell/staticcheck, golangci-lint, fmt-check, schema-check, vulncheck, file-budget, deadcode, check-style, check-tool-pins);scripts/coverage-floor.shok (internal/cli85.3% ≥ 80%).internal/doctorat 89.1%.Mutation evidence (by hand, anchor asserted applied each time, then restored)
TestCheckNodeFitFreeMemoryred ×2, remedies-are-opposites redok ("a Ready node can schedule…")requestedMemoryswallows the error into 0 againunreadable pod list => OK ("… → 7.75 GiB unrequested")want ready Warn, got ok ("Ready to run training"). First attempt was inert (comment ate thecasecolon → build failed, proves nothing); redone with a compiling mutant.Docs
No prose outside the code describes the Node-capacity check (README line 16 lists
doctorgenerically;docs/has nothing on it), so "prose = code" here is the doc comments oncheckNodeFit,checkMachineChainandrequestedMemory, all updated.What this PR does NOT do
waiting_for_capacityreport (DoD 4) — client-runtime; jobs-manager keeps admitting on WAIT.VERSION→0.10.24becausev0.10.23is already tagged and this touchesinternal/*.Note
Medium Risk
Changes doctor exit codes and operator guidance for common training-wait states; logic is heavily tested but misclassification could still mislead users about resize vs wait.
Overview
tracebloc doctornow separates a permanent capacity problem (the training envelope cannot fit beside the platform’s steady-state pods) from a transient one (it fits, but a running batch job currently holds the resources). Node capacity tracks platform requests and runningjob-namepods in separate per-node sums, emits Fail with richer free-CPU/memory detail for over-commit, and a new Warn (HeldByRunningJob) that tells operators to wait or stop the job—not resize.The readiness rollup matches that behavior: when a pending pod coincides with a measured “job holds the room” warn, it surfaces a Warn (exit 0) instead of the generic stuck-pending Fail and
resources set maxadvice. Standalone transient and co-occurring cases get plain-language top lines and remedies.Machine capacity stops treating a failed pod list as “whole VM unrequested” under a green check—
requestedMemoryreturns errors, the chain shows unknown unrequested memory, and status becomes Unknown while over-commit detection still works without pod visibility.Version 0.10.24; tests and user-string goldens updated for the new messages.
Reviewed by Cursor Bugbot for commit a14acfc. Bugbot is set up for automated code reviews on this repo. Configure here.