CPU & Memory managers forward compatibility

This commit is contained in:
Kevin Torres
2026-03-14 19:19:35 +00:00
parent 9ad8603033
commit b969bbda83
4 changed files with 126 additions and 2 deletions

View File

@@ -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)
}

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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)
}
}