Repository navigation
fix(kubernetes): fail rootfs snapshots whose commit result lacks digests - #2482
Pangjiping merged 4 commits into
Conversation
updateSnapshotStatusFromSucceededCommitJob published a rootfs-v1 SandboxSnapshot as Succeed even when the commit Job's Pod, and therefore the termination message that carries every container digest, was already gone. completePause then deleted the runtime Pods, and every later resume failed permanently with ResumeFailed/InvalidSnapshotImage because continueResume restores rootfs-v1 containers exclusively by digest. Treat a missing or incomplete commit result as a failed commit, matching the existing QEMU branch: the snapshot becomes Failed with CommitResultUnavailable/CommitResultIncomplete, syncPauseOrClear reports PauseFailed, and the Pods are kept so the pause can be retried. Legacy snapshots (Format == "") still restore by URI and keep their behavior.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 162e44cf63
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…cache lags The informer cache can observe the commit Job status before the commit Pod status. A cache miss was therefore indistinguishable from a lost commit result, and the snapshot was moved to a terminal Failed state that no later reconcile can recover, failing the pause of an otherwise healthy sandbox. The Job is reported Succeeded only after its Pod reached a terminal state, so the Pod termination message is already persisted when the Job status is written. Reading the Pod straight from the API server on a cache miss keeps a lagging cache from being mistaken for a lost result. Addresses the P1 review finding on opensandbox-group#2482.
|
Addressed the P1 finding in @codex review |
|
Codex Review: Didn't find any major issues. Nice work! Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
Summary
updateSnapshotStatusFromSucceededCommitJobpublished arootfs-v1SandboxSnapshotasSucceedeven when the commit result was unavailable. The result is the only place the per-container image digests are written (the commit Pod's termination message), and since #1584 (1ea4e385) resume restoresrootfs-v1containers exclusively byImageURI@sha256:<digest>. The consequence was silent and permanent: pause reported success,completePausedeleted the runtime Pods, and every later resume failed withResumeFailed/InvalidSnapshotImage: invalid sha256 digest "".The commit result can be gone while the Job still reports success: a completed-Pod cleanup policy, node drain/eviction, or a controller restart/leader loss that reads the Job after the Pod was removed leaves
Job.Status.Succeeded > 0with no Pod to read. (The Job's own 300s TTL deletes the Job as well, which makes the controller recreate it and re-commit — that path heals itself.) Nothing retried the digest, because it was never persisted.This change treats a missing or incomplete commit result as a failed commit for
rootfs-v1, mirroring what theqemu-v1branch already does:found == false) →Failed/CommitResultUnavailableFailed/CommitResultIncomplete, naming the containers in the messagefound == falsemeans the result is gone from the API server, not only from the informer cache. The commit Pod is read through the uncachedAPIReaderwhenever the cached lookup comes back empty, because the Job controller writesJob.status.succeededonly after the commit Pod reached a terminal state — the Pod status carrying the termination message is persisted before the Job status is — so the result already exists in the API server when the Job reports success, and only the cache may lag behind it. Without that read, a lagging cache would fail the pause of a healthy sandbox.syncPauseOrClearthen reportsPauseFailedon theBatchSandbox, the Pods are not deleted, and the sandbox stays running so pause can be retried. Legacy snapshots (Format == "") still restore by URI and keep their current behavior; an alreadySucceedsnapshot is never downgraded.docs/guides/pause-resume.mdnow documents whenSucceedis published and the two failure reasons.Fixes #2481.
Testing
New controller tests in
kubernetes/internal/controller/sandboxsnapshot_controller_test.go:TestSandboxSnapshotHandleCommitting_RootfsCommitWithoutResultFails—rootfs-v1, Succeeded Job, no Pod →Failed/CommitResultUnavailableTestSandboxSnapshotHandleCommitting_RootfsCommitWithIncompleteResultFails— termination message coversmainbut notsidecar→Failed/CommitResultIncompleteTestSandboxSnapshotHandleCommitting_RootfsCommitWithAllDigestsSucceeds— positive control; stillSucceedwith digests recordedTestSandboxSnapshotHandleCommitting_StaleCacheReadsResultFromAPIServer— the informer cache returns an empty PodList while the API server still holds the commit Pod →Succeedwith the digest, not a terminalFailedThe two rejection tests fail on unmodified
97031ea8(the production file replaced throughgo test -overlay, tree untouched) and pass at the PR head:At the PR head:
Also run (Windows, Go 1.25.9; the package has pre-existing platform failures):
go test ./internal/controller/— failure set identical to the97031ea8baseline (14 lines:TestControllers,TestReconcileDeletingPod,TestReconcileDeletingTaskSandbox,TestReconcilePodRecovery, and the QEMU cases ofTestSandboxSnapshotHandlePending_ImageURITemplate)go test ./internal/snapshot/— same single pre-existingTestContractFromPodParsesQEMUfailure as baselinego vet ./internal/controller/ ./cmd/controller/— cleanGOOS=linux GOARCH=amd64 go build ./internal/controller/ ./cmd/controller/— cleangofmt -don the changed Go files — empty;git diff --check— cleancd docs && pnpm docs:build— build complete in 13.80s, zero errorsBreaking Changes
Behavior change is limited to the failure path: a pause whose commit result was lost now reports
PauseFailedand keeps the Pods instead of reporting success and making resume impossible.Checklist
docs/guides/pause-resume.mdstates whenSucceedis published and the two failure reasons.Format == ""snapshots and existingSucceedstatuses are untouched.This change was drafted with AI assistance at my request, under my direction and review.