From b969bbda8329f73326ef018ef62ae669f45e2210 Mon Sep 17 00:00:00 2001 From: Kevin Torres Date: Sat, 14 Mar 2026 19:19:35 +0000 Subject: [PATCH] CPU & Memory managers forward compatibility --- pkg/kubelet/cm/cpumanager/state/checkpoint.go | 12 ++++- .../cpumanager/state/state_checkpoint_test.go | 52 +++++++++++++++++++ .../cm/memorymanager/state/checkpoint.go | 12 ++++- .../state/state_checkpoint_test.go | 52 +++++++++++++++++++ 4 files changed, 126 insertions(+), 2 deletions(-) diff --git a/pkg/kubelet/cm/cpumanager/state/checkpoint.go b/pkg/kubelet/cm/cpumanager/state/checkpoint.go index 625dca93d0b..6903ef3e9d9 100644 --- a/pkg/kubelet/cm/cpumanager/state/checkpoint.go +++ b/pkg/kubelet/cm/cpumanager/state/checkpoint.go @@ -100,7 +100,17 @@ func (cp *CPUManagerCheckpointV1) MarshalCheckpoint() ([]byte, error) { func (cp *CPUManagerCheckpointV2) MarshalCheckpoint() ([]byte, error) { // make sure checksum wasn't set before so it doesn't affect output checksum cp.Checksum = 0 - cp.Checksum = checksum.New(cp) + + // In order to preserve rollback compatibility when the feature gate is disabled, + // we must generate a checksum using the legacy struct name "CPUManagerCheckpoint" + // instead of "CPUManagerCheckpointV2". Older Kubelets do not have the string + // replacement logic and expect the original struct name. + object := dump.ForHash(cp) + object = strings.Replace(object, "CPUManagerCheckpointV2", "CPUManagerCheckpoint", 1) + hash := fnv.New32a() + _, _ = fmt.Fprintf(hash, "%v", object) + cp.Checksum = checksum.Checksum(hash.Sum32()) + return json.Marshal(*cp) } diff --git a/pkg/kubelet/cm/cpumanager/state/state_checkpoint_test.go b/pkg/kubelet/cm/cpumanager/state/state_checkpoint_test.go index 95f79eadb27..75be69562f5 100644 --- a/pkg/kubelet/cm/cpumanager/state/state_checkpoint_test.go +++ b/pkg/kubelet/cm/cpumanager/state/state_checkpoint_test.go @@ -17,6 +17,7 @@ limitations under the License. package state import ( + "encoding/json" "os" "reflect" "strings" @@ -27,6 +28,7 @@ import ( featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/checkpointmanager" + "k8s.io/kubernetes/pkg/kubelet/checkpointmanager/checksum" "k8s.io/kubernetes/pkg/kubelet/cm/containermap" testutil "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/state/testing" "k8s.io/kubernetes/test/utils/ktesting" @@ -637,3 +639,53 @@ func AssertStateEqual(t *testing.T, sf State, sm State) { t.Errorf("State CPU assignments mismatch. Have %s, want %s", cpuassignmentSf, cpuassignmentSm) } } + +func TestCPUManagerCheckpointV2_MarshalCheckpoint_ForwardCompatibility(t *testing.T) { + // 1. Create a V2 checkpoint using the struct defined in the current codebase (1.36+) + currentCheckpoint := &CPUManagerCheckpointV2{ + PolicyName: "none", + DefaultCPUSet: "1-3", + Entries: make(map[string]map[string]string), + } + + // Marshal it using the logic that forces the "CPUManagerCheckpoint" name + data, err := currentCheckpoint.MarshalCheckpoint() + if err != nil { + t.Fatalf("Failed to marshal checkpoint: %v", err) + } + + // 2. Unmarshal the raw JSON to extract the checksum that was actually written to the file + var result map[string]interface{} + if err := json.Unmarshal(data, &result); err != nil { + t.Fatalf("Failed to unmarshal JSON: %v", err) + } + + actualChecksumFloat, ok := result["checksum"].(float64) + if !ok { + t.Fatalf("Checksum field missing or invalid type") + } + writtenChecksum := checksum.Checksum(uint64(actualChecksumFloat)) + + // 3. Reconstruct how versions 1.35 and earlier would calculate the checksum + // by defining a struct with the exact legacy name and fields. + type CPUManagerCheckpoint struct { + PolicyName string `json:"policyName"` + DefaultCPUSet string `json:"defaultCpuSet"` + Entries map[string]map[string]string `json:"entries,omitempty"` + Checksum checksum.Checksum `json:"checksum"` + } + + legacyCheckpoint := &CPUManagerCheckpoint{ + PolicyName: currentCheckpoint.PolicyName, + DefaultCPUSet: currentCheckpoint.DefaultCPUSet, + Entries: currentCheckpoint.Entries, + } + + expectedLegacyChecksum := checksum.New(legacyCheckpoint) + + // 4. Assert that the checksum written by our 1.36+ code matches + // what a 1.35 Kubelet would expect to see. + if writtenChecksum != expectedLegacyChecksum { + t.Errorf("Written Checksum %d does not match legacy calculation %d. Forward compatibility broken.", writtenChecksum, expectedLegacyChecksum) + } +} diff --git a/pkg/kubelet/cm/memorymanager/state/checkpoint.go b/pkg/kubelet/cm/memorymanager/state/checkpoint.go index be8a359cb55..445d0cf57b7 100644 --- a/pkg/kubelet/cm/memorymanager/state/checkpoint.go +++ b/pkg/kubelet/cm/memorymanager/state/checkpoint.go @@ -79,7 +79,17 @@ func newMemoryManagerCheckpointV2() *MemoryManagerCheckpointV2 { func (mp *MemoryManagerCheckpointV1) MarshalCheckpoint() ([]byte, error) { // make sure checksum wasn't set before so it doesn't affect output checksum mp.Checksum = 0 - mp.Checksum = checksum.New(mp) + + // In order to preserve rollback compatibility when the feature gate is disabled, + // we must generate a checksum using the legacy struct name "MemoryManagerCheckpoint" + // instead of "MemoryManagerCheckpointV1". Older Kubelets do not have the string + // replacement logic and expect the original struct name. + object := dump.ForHash(mp) + object = strings.Replace(object, "MemoryManagerCheckpointV1", "MemoryManagerCheckpoint", 1) + hash := fnv.New32a() + _, _ = fmt.Fprintf(hash, "%v", object) + mp.Checksum = checksum.Checksum(hash.Sum32()) + return json.Marshal(*mp) } diff --git a/pkg/kubelet/cm/memorymanager/state/state_checkpoint_test.go b/pkg/kubelet/cm/memorymanager/state/state_checkpoint_test.go index 860b9c9462e..fb4b247b881 100644 --- a/pkg/kubelet/cm/memorymanager/state/state_checkpoint_test.go +++ b/pkg/kubelet/cm/memorymanager/state/state_checkpoint_test.go @@ -17,6 +17,7 @@ limitations under the License. package state import ( + "encoding/json" "os" "strings" "testing" @@ -29,6 +30,7 @@ import ( featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/checkpointmanager" + "k8s.io/kubernetes/pkg/kubelet/checkpointmanager/checksum" testutil "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/state/testing" "k8s.io/kubernetes/test/utils/ktesting" ) @@ -592,3 +594,53 @@ func TestCheckpointStateClear(t *testing.T) { }) } } + +func TestMemoryManagerCheckpointV1_MarshalCheckpoint_ForwardCompatibility(t *testing.T) { + // 1. Create a V1 checkpoint using the struct defined in the current codebase (1.36+) + currentCheckpoint := &MemoryManagerCheckpointV1{ + PolicyName: "none", + MachineState: NUMANodeMap{}, + Entries: ContainerMemoryAssignments{}, + } + + // Marshal it using the logic that forces the "MemoryManagerCheckpoint" name + data, err := currentCheckpoint.MarshalCheckpoint() + if err != nil { + t.Fatalf("Failed to marshal checkpoint: %v", err) + } + + // 2. Unmarshal the raw JSON to extract the checksum that was actually written to the file + var result map[string]interface{} + if err := json.Unmarshal(data, &result); err != nil { + t.Fatalf("Failed to unmarshal JSON: %v", err) + } + + actualChecksumFloat, ok := result["checksum"].(float64) + if !ok { + t.Fatalf("Checksum field missing or invalid type") + } + writtenChecksum := checksum.Checksum(uint64(actualChecksumFloat)) + + // 3. Reconstruct how versions 1.35 and earlier would calculate the checksum + // by defining a struct with the exact legacy name and fields. + type MemoryManagerCheckpoint struct { + PolicyName string `json:"policyName"` + MachineState NUMANodeMap `json:"machineState"` + Entries ContainerMemoryAssignments `json:"entries,omitempty"` + Checksum checksum.Checksum `json:"checksum"` + } + + legacyCheckpoint := &MemoryManagerCheckpoint{ + PolicyName: currentCheckpoint.PolicyName, + MachineState: currentCheckpoint.MachineState, + Entries: currentCheckpoint.Entries, + } + + expectedLegacyChecksum := checksum.New(legacyCheckpoint) + + // 4. Assert that the checksum written by our 1.36+ code matches + // what a 1.35 Kubelet would expect to see. + if writtenChecksum != expectedLegacyChecksum { + t.Errorf("Written Checksum %d does not match legacy calculation %d. Forward compatibility broken.", writtenChecksum, expectedLegacyChecksum) + } +}