diff --git a/test/e2e/common/node/framework/podresize/resize.go b/test/e2e/common/node/framework/podresize/resize.go index 157c2e8f88f..c9e9a982280 100644 --- a/test/e2e/common/node/framework/podresize/resize.go +++ b/test/e2e/common/node/framework/podresize/resize.go @@ -27,6 +27,7 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/strategicpatch" helpers "k8s.io/component-helpers/resource" "k8s.io/kubectl/pkg/util/podutils" kubeqos "k8s.io/kubernetes/pkg/kubelet/qos" @@ -59,18 +60,19 @@ type containerPatch struct { CPU string `json:"cpu,omitempty"` Memory string `json:"memory,omitempty"` EphStor string `json:"ephemeral-storage,omitempty"` - } `json:"requests"` + } `json:"requests,omitzero"` Limits struct { CPU string `json:"cpu,omitempty"` Memory string `json:"memory,omitempty"` EphStor string `json:"ephemeral-storage,omitempty"` - } `json:"limits"` + } `json:"limits,omitzero"` } `json:"resources"` } type patchSpec struct { Spec struct { - Containers []containerPatch `json:"containers"` + Containers []containerPatch `json:"containers,omitempty" patchStrategy:"merge" patchMergeKey:"name"` + InitContainers []containerPatch `json:"initContainers,omitempty" patchStrategy:"merge" patchMergeKey:"name"` } `json:"spec"` } @@ -370,7 +372,7 @@ func ExpectPodResized(ctx context.Context, f *framework.Framework, resizedPod *v } // ResizeContainerPatch generates a patch string to resize the pod container. -func ResizeContainerPatch(containers []ResizableContainerInfo) (string, error) { +func ResizeContainerPatch(containers []ResizableContainerInfo) ([]byte, error) { var patch patchSpec for _, container := range containers { @@ -381,15 +383,32 @@ func ResizeContainerPatch(containers []ResizableContainerInfo) (string, error) { cPatch.Resources.Limits.CPU = container.Resources.CPULim cPatch.Resources.Limits.Memory = container.Resources.MemLim - patch.Spec.Containers = append(patch.Spec.Containers, cPatch) + if container.InitCtr { + patch.Spec.InitContainers = append(patch.Spec.InitContainers, cPatch) + } else { + patch.Spec.Containers = append(patch.Spec.Containers, cPatch) + } } patchBytes, err := json.Marshal(patch) if err != nil { - return "", err + return nil, err } - return string(patchBytes), nil + return patchBytes, nil +} + +func MakeResizePatch(originalContainers, desiredContainers []ResizableContainerInfo) []byte { + original, err := json.Marshal(MakePodWithResizableContainers("", "", "", originalContainers)) + framework.ExpectNoError(err) + + desired, err := json.Marshal(MakePodWithResizableContainers("", "", "", desiredContainers)) + framework.ExpectNoError(err) + + patch, err := strategicpatch.CreateTwoWayMergePatch(original, desired, v1.Pod{}) + framework.ExpectNoError(err) + + return patch } // UpdateExpectedContainerRestarts updates the RestartCounts in expectedContainers by diff --git a/test/e2e/common/node/framework/podresize/resize_test.go b/test/e2e/common/node/framework/podresize/resize_test.go new file mode 100644 index 00000000000..ed859677109 --- /dev/null +++ b/test/e2e/common/node/framework/podresize/resize_test.go @@ -0,0 +1,122 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package podresize + +import ( + "encoding/json" + "testing" + + "k8s.io/apimachinery/pkg/util/strategicpatch" + "k8s.io/kubernetes/test/e2e/common/node/framework/cgroups" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMakeResizePatch(t *testing.T) { + tests := []struct { + name string + old []ResizableContainerInfo + new []ResizableContainerInfo + want string + }{ + { + name: "no change", + old: []ResizableContainerInfo{ + {Name: "c1", Resources: &cgroups.ContainerResources{CPUReq: "10m", MemReq: "10Mi"}}, + }, + new: []ResizableContainerInfo{ + {Name: "c1", Resources: &cgroups.ContainerResources{CPUReq: "10m", MemReq: "10Mi"}}, + }, + want: `{}`, + }, + { + name: "increase cpu", + old: []ResizableContainerInfo{ + {Name: "c1", Resources: &cgroups.ContainerResources{CPUReq: "10m", CPULim: "15m", MemReq: "10Mi"}}, + }, + new: []ResizableContainerInfo{ + {Name: "c1", Resources: &cgroups.ContainerResources{CPUReq: "20m", CPULim: "25m", MemReq: "10Mi"}}, + }, + want: `{"spec":{"containers":[{"name":"c1","resources":{"limits":{"cpu":"25m"},"requests":{"cpu":"20m"}}}]}}`, + }, + { + name: "add cpu", + old: []ResizableContainerInfo{ + {Name: "c1", Resources: &cgroups.ContainerResources{MemReq: "10Mi"}}, + }, + new: []ResizableContainerInfo{ + {Name: "c1", Resources: &cgroups.ContainerResources{CPUReq: "20m", CPULim: "25m", MemReq: "10Mi"}}, + }, + want: `{"spec":{"containers":[{"name":"c1","resources":{"limits":{"cpu":"25m"},"requests":{"cpu":"20m"}}}]}}`, + }, + { + name: "decrease memory", + old: []ResizableContainerInfo{ + {Name: "c1", Resources: &cgroups.ContainerResources{CPUReq: "10m", CPULim: "15m", MemReq: "20Mi", MemLim: "25Mi"}}, + }, + new: []ResizableContainerInfo{ + {Name: "c1", Resources: &cgroups.ContainerResources{CPUReq: "10m", CPULim: "15m", MemReq: "10Mi", MemLim: "15Mi"}}, + }, + want: `{"spec":{"containers":[{"name":"c1","resources":{"limits":{"memory":"15Mi"},"requests":{"memory":"10Mi"}}}]}}`, + }, + { + name: "change multiple contaniers", + old: []ResizableContainerInfo{ + {Name: "c1", Resources: &cgroups.ContainerResources{CPUReq: "10m", MemReq: "10Mi", CPULim: "15m", MemLim: "15Mi"}}, + {Name: "c2", Resources: &cgroups.ContainerResources{CPUReq: "20m", MemReq: "20Mi"}}, + }, + new: []ResizableContainerInfo{ + {Name: "c1", Resources: &cgroups.ContainerResources{CPUReq: "15m", MemReq: "10Mi", CPULim: "25m", MemLim: "25Mi"}}, + {Name: "c2", Resources: &cgroups.ContainerResources{CPUReq: "20m", MemReq: "25Mi"}}, + }, + want: `{"spec":{"containers":[{"name":"c1","resources":{"limits":{"cpu":"25m","memory":"25Mi"},"requests":{"cpu":"15m"}}},{"name":"c2","resources":{"requests":{"memory":"25Mi"}}}]}}`, + }, + { + name: "change init container", + old: []ResizableContainerInfo{ + {Name: "c1", Resources: &cgroups.ContainerResources{CPUReq: "10m", MemReq: "10Mi"}}, + {Name: "ic1", Resources: &cgroups.ContainerResources{CPUReq: "10m", MemReq: "10Mi"}, InitCtr: true}, + }, + new: []ResizableContainerInfo{ + {Name: "c1", Resources: &cgroups.ContainerResources{CPUReq: "10m", MemReq: "10Mi"}}, + {Name: "ic1", Resources: &cgroups.ContainerResources{CPUReq: "20m", MemReq: "10Mi"}, InitCtr: true}, + }, + want: `{"spec":{"initContainers":[{"name":"ic1","resources":{"requests":{"cpu":"20m"}}}]}}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + patch := MakeResizePatch(tt.old, tt.new) + + // Ignore the "$setElementOrder" directive for patch comparison. + patchMap := strategicpatch.JSONMap{} + err := json.Unmarshal(patch, &patchMap) + require.NoError(t, err) + if patchMap, ok := patchMap["spec"].(map[string]interface{}); ok { + delete(patchMap, "$setElementOrder/containers") + delete(patchMap, "$setElementOrder/initContainers") + } + patch, err = json.Marshal(patchMap) + require.NoError(t, err) + + assert.Equal(t, tt.want, string(patch)) + }) + } + +}