Adding checkpointing improvement#1062
Conversation
|
Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our release note process to remove it. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
fcd236f to
550cd46
Compare
Signed-off-by: Vishesh Tanksale <vtanksale@nvidia.com>
550cd46 to
192cff2
Compare
|
@visheshtanksale: The following test failed, say
Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. |
|
Unknown CLA label state. Rechecking for CLA labels. Send feedback to sig-contributor-experience at kubernetes/community. /check-cla |
ArangoGutierrez
left a comment
There was a problem hiding this comment.
Agreeing with the intent (explicit envelope version + unknown-field preservation is the right direction), but there's a load-bearing bug and the PR needs tests before it can merge.
- Blocker:
ToLatestVersiondiscards theothermap. The function constructslatest := &Checkpoint{}and copies onlyV2;MarshalCheckpointunconditionally callscp = cp.ToLatestVersion()on entry, andupdateCheckpointindevice_state.godoes the same before the mutate callback runs. Result: unknown fields parsed intocp.otherare dropped before the next write, defeating the preservation mechanism on every read-modify-write. Same bug in bothcmd/gpu-kubelet-plugin/checkpoint.goandcmd/compute-domain-kubelet-plugin/checkpoint.go. Inline on the GPU-plugin copy. - Blocker: no tests. A PR whose entire value proposition is schema/downgrade/round-trip safety needs: (1) round-trip marshal→unmarshal→re-marshal byte-equality; (2) downgrade round-trip — unmarshal a fixture with an unknown top-level field, re-marshal, assert it survives (this test fails against current code, which is the point); (3) V1 back-compat golden file under
testdata/; (4)DeepCopyno-alias per type, especiallydeepCopyDevice(catches a regression where someone shallow-copies theRequests/CDIDeviceIDsslices). - Follow-up (not blocking):
checkpoint.go/checkpointv.go/deepcopy.goare substantively duplicated between the two plugins. Worth an issue to lift the envelope (Checkpoint,Version,other, V1/V2 checksum plumbing) into a sharedpkg/package. Related: the atomic-write guarantee (tmp +Sync()+Rename()) lives ink8s.io/kubernetes/pkg/kubelet/util/store/filestore.go, not here — a one-line comment increateCheckpointpointing at that dependency would help a future refactor avoid silently losing atomicity.
Scoping question: what exact failure mode does #507 describe? If it's the downgrade-safety story, that's currently broken by the first blocker. If it's DeepCopy-to-avoid-aliasing, what's the observable bug the deep copy prevents?
|
|
||
| func (cp *Checkpoint) MarshalCheckpoint() ([]byte, error) { | ||
| cp = cp.ToLatestVersion() | ||
| cp.Version = CheckpointVersion |
There was a problem hiding this comment.
cp = cp.ToLatestVersion() on the line above returns a fresh &Checkpoint{} that only copies V2 — it drops Version and other. You then re-stamp Version here, but other is already gone. This defeats the whole unknown-field preservation mechanism on every write: an unknown top-level field read via UnmarshalJSON is captured into cp.other, survives until MarshalCheckpoint, and then gets discarded by ToLatestVersion before the final json.Marshal(cp). Same call pattern in updateCheckpoint in device_state.go, so the drop also happens before the mutate callback runs. Fix: inside ToLatestVersion, copy cp.other (and arguably cp.Version) into latest. Same bug exists in cmd/compute-domain-kubelet-plugin/checkpoint.go — please patch both. A round-trip test asserting an unknown field survives would have caught this.
| "k8s.io/kubernetes/pkg/kubelet/checkpointmanager/checksum" | ||
| ) | ||
|
|
||
| const CheckpointVersion = "v2" |
There was a problem hiding this comment.
"v2" collides conceptually with the inner CheckpointV2 type embedded as json:"v2". Is the envelope version tied 1:1 to the inner payload, or is it an independent envelope-schema version? A future reader has to re-derive which "v2" is which. Prefer an integer (Version int + CheckpointEnvelopeVersion = 2) or a non-colliding string ("envelope.v2", "1.0").
|
|
||
| cp.Checksum = 0 | ||
| out, err := json.Marshal(*cp) | ||
| type v1View struct { |
There was a problem hiding this comment.
v1View locks in the V1 checksum byte shape, but has no compile-time link to CheckpointV1. If someone adds a field to CheckpointV1 later, the checksum view silently diverges from the on-disk V1 payload and the error manifests only as "old driver cannot verify new file" in the field. Either add a comment pointing at CheckpointV1 as the source of truth, or derive the view from CheckpointV1 via struct tags so the compiler enforces the mirror.
|
|
||
| func (c PreparedClaimV1) DeepCopy() PreparedClaimV1 { | ||
| var status resourceapi.ResourceClaimStatus | ||
| if s := c.Status.DeepCopy(); s != nil { |
There was a problem hiding this comment.
c.Status is a value, so c.Status.DeepCopy() should not return nil — the defensive if s != nil { status = *s } implies uncertainty. Either simplify to Status: *c.Status.DeepCopy(), or leave a comment explaining the defensive path (e.g., what condition would make DeepCopy return nil here).
rajatchopra
left a comment
There was a problem hiding this comment.
/lgtm
Some non blocking suggestions.
| V1 *CheckpointV1 `json:"v1,omitempty"` | ||
| V2 *CheckpointV2 `json:"v2,omitempty"` | ||
| // other holds unknown fields from a newer checkpoint format | ||
| other map[string]json.RawMessage |
There was a problem hiding this comment.
map[string]any maybe? Why put the encoding nuance in the type.
| // MarshalJSON implements json.Marshaler, merging known fields with any | ||
| // unknown fields captured from a newer checkpoint format. | ||
| func (cp *Checkpoint) MarshalJSON() ([]byte, error) { | ||
| type Alias struct { |
There was a problem hiding this comment.
This could be type Alias Checksum itself since 'other' is an unexported field, the marshaling would work just fine. Also, any changes to Checkpoint struct will always stay in sync.
| if err := json.Unmarshal(known, &merged); err != nil { | ||
| return nil, err | ||
| } | ||
| for k, v := range cp.other { |
There was a problem hiding this comment.
We should do a quick unit test around this maybe.
|
@rajatchopra: changing LGTM is restricted to collaborators DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: rajatchopra, visheshtanksale The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
What type of PR is this?
/kind bug
What this PR does / why we need it:
Which issue(s) this PR is related to:
Fixes #507
Special notes for your reviewer:
Does this PR introduce a user-facing change?
NONE
Additional documentation (design docs, usage docs, etc.):
Checklist
make check testpasses locallymake check-generatepasses ifapi/changed (CRDs, deepcopy, informers, listers, clientset)make check-modulespasses ifgo.mod/go.sumchangeddeployments/helm) updated if flags, RBAC, or defaults changed