You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Rewrite BatchSandbox pod-failure recovery on current main (supersedes #1523) #2008
#1497 is still reproducible on main. #1523 attempted a fix but has been closed: it drifted ~535 commits behind main, was in a conflicting state, and its CI never ran beyond auto-label, so the 218 lines of new tests were never executed. This issue tracks a clean rewrite against current main, preserving the parts of #1523 that were reviewed and validated.
Once a BatchSandbox reaches Failed, the steady-state path returns unconditionally. If the same Pod (same UID) later recovers to Running and Ready, the phase is never recomputed and the BatchSandbox stays Failed forever. This is exactly the scenario reported in #1497.
These were reviewed and confirmed correct; they should carry over rather than be redesigned:
Recovery keyed on Pod UID, not name. A replacement Pod can reuse the same name, so name-based recovery is unsafe. fix(kubernetes): recover BatchSandbox after Pod restart #1523 recorded the failing UIDs in a new status.failedPodUIDs field and only allowed recovery when every recorded UID was observed Ready with its main container Running.
Clearing provenance on resume failure stays terminal.applyResumingRuntimePhase deliberately clears the recorded UIDs so a resume failure cannot later be reinterpreted as a recovered transient failure.
Explicit null in the merge patch.updateStatus (batchsandbox_status.go:480) marshals the desired status and sends it as types.MergePatchType. Because the field is omitempty, assigning nil in Go omits the key instead of clearing it server-side. fix(kubernetes): recover BatchSandbox after Pod restart #1523 handled this by marshalling to map[string]any and setting statusPatch["failedPodUIDs"] = nil when the stored status has UIDs and the desired one does not. This part of updateStatus is unchanged on main, so the approach still applies.
Constraints the rewrite must satisfy
Collect provenance in summarizePodFailuresWith, not in summarizePodFailures.main refactored the summarizer into a shared summarizePodFailuresWith(pods, detectFailure, includeSampleDetail) (batchsandbox_status.go:238) with two detectors: summarizePodFailures and summarizeTerminalPodFailures (:234). applySteadyRuntimePhase routes the ""/Pending phase through the terminal detector. If UID collection is added only to the non-terminal path, a failure detected by the terminal path records no UIDs, recovery can never be established, and the BatchSandbox becomes permanently pinned in Failed — strictly worse than the current behaviour.
Account for the reordered condition writes. In the failure branch, main now sets PodFailed before ResumeFailed, and adds the terminal-summary branch at the top of the function. The FailedPodUIDs assignment has to be re-derived against this shape, not merged textually.
CRD parity is generated, not hand-edited.kubernetes/charts/ no longer exists; charts moved to manifests/charts/. After adding the field to the Go type and regenerating kubernetes/config/crd/bases/, run make -C manifests helm-gen-crds to sync manifests/charts/base/files/crds.yaml. That bundle currently contains pauseObservedGeneration but no equivalent for this field, so a hand-edit is both unnecessary and insufficient.
CI must actually run. Go unit tests and lint need to execute on the PR before review. Regressions here are silent: a wrong detector path produces a permanently Failed BatchSandbox with no error surfaced.
Acceptance criteria
A BatchSandbox that entered Failed due to a transient Pod failure returns to Succeed once the same Pod (same UID) is Ready with its main container Running, covering both the normal and terminal failure detectors.
A replacement Pod that reuses the name of a failed Pod does not trigger recovery.
A resume failure remains terminal and is not later reinterpreted as recovered.
status.failedPodUIDs is cleared server-side (observable as null in the merge patch, not merely absent) whenever the desired status no longer carries it.
make -C manifests helm-gen-crds output is committed and matches the generated CRD bases.
Summary
#1497 is still reproducible on
main. #1523 attempted a fix but has been closed: it drifted ~535 commits behindmain, was in a conflicting state, and its CI never ran beyondauto-label, so the 218 lines of new tests were never executed. This issue tracks a clean rewrite against currentmain, preserving the parts of #1523 that were reviewed and validated.The defect on
maintodaykubernetes/internal/controller/batchsandbox_status.go:362, insideapplySteadyRuntimePhase:Once a BatchSandbox reaches
Failed, the steady-state path returns unconditionally. If the same Pod (same UID) later recovers toRunningandReady, the phase is never recomputed and the BatchSandbox staysFailedforever. This is exactly the scenario reported in #1497.Prior art from #1523 worth keeping
These were reviewed and confirmed correct; they should carry over rather than be redesigned:
status.failedPodUIDsfield and only allowed recovery when every recorded UID was observed Ready with its main container Running.applyResumingRuntimePhasedeliberately clears the recorded UIDs so a resume failure cannot later be reinterpreted as a recovered transient failure.nullin the merge patch.updateStatus(batchsandbox_status.go:480) marshals the desired status and sends it astypes.MergePatchType. Because the field isomitempty, assigningnilin Go omits the key instead of clearing it server-side. fix(kubernetes): recover BatchSandbox after Pod restart #1523 handled this by marshalling tomap[string]anyand settingstatusPatch["failedPodUIDs"] = nilwhen the stored status has UIDs and the desired one does not. This part ofupdateStatusis unchanged onmain, so the approach still applies.Constraints the rewrite must satisfy
Collect provenance in
summarizePodFailuresWith, not insummarizePodFailures.mainrefactored the summarizer into a sharedsummarizePodFailuresWith(pods, detectFailure, includeSampleDetail)(batchsandbox_status.go:238) with two detectors:summarizePodFailuresandsummarizeTerminalPodFailures(:234).applySteadyRuntimePhaseroutes the""/Pendingphase through the terminal detector. If UID collection is added only to the non-terminal path, a failure detected by the terminal path records no UIDs, recovery can never be established, and the BatchSandbox becomes permanently pinned inFailed— strictly worse than the current behaviour.Account for the reordered condition writes. In the failure branch,
mainnow setsPodFailedbeforeResumeFailed, and adds the terminal-summary branch at the top of the function. TheFailedPodUIDsassignment has to be re-derived against this shape, not merged textually.CRD parity is generated, not hand-edited.
kubernetes/charts/no longer exists; charts moved tomanifests/charts/. After adding the field to the Go type and regeneratingkubernetes/config/crd/bases/, runmake -C manifests helm-gen-crdsto syncmanifests/charts/base/files/crds.yaml. That bundle currently containspauseObservedGenerationbut no equivalent for this field, so a hand-edit is both unnecessary and insufficient.CI must actually run. Go unit tests and lint need to execute on the PR before review. Regressions here are silent: a wrong detector path produces a permanently
FailedBatchSandbox with no error surfaced.Acceptance criteria
Faileddue to a transient Pod failure returns toSucceedonce the same Pod (same UID) isReadywith its main containerRunning, covering both the normal and terminal failure detectors.status.failedPodUIDsis cleared server-side (observable asnullin the merge patch, not merely absent) whenever the desired status no longer carries it.make -C manifests helm-gen-crdsoutput is committed and matches the generated CRD bases.References