From f1093962b6d3b495dca9b0eace896528a3de89c3 Mon Sep 17 00:00:00 2001 From: Anish Shah Date: Tue, 22 Oct 2024 12:20:33 -0700 Subject: [PATCH] integration tests for pod resize --- test/integration/pods/pods_test.go | 248 +++++++++++++++++++++++ test/integration/scheduler/queue_test.go | 4 +- 2 files changed, 250 insertions(+), 2 deletions(-) diff --git a/test/integration/pods/pods_test.go b/test/integration/pods/pods_test.go index e0c0f7d7861..8b0aa1afdf6 100644 --- a/test/integration/pods/pods_test.go +++ b/test/integration/pods/pods_test.go @@ -23,6 +23,7 @@ import ( "testing" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" utilfeature "k8s.io/apiserver/pkg/util/feature" @@ -678,6 +679,253 @@ func TestPodUpdateEphemeralContainers(t *testing.T) { } } +func TestPodResize(t *testing.T) { + // Disable ServiceAccount admission plugin as we don't have serviceaccount controller running. + server := kubeapiservertesting.StartTestServerOrDie(t, nil, + append(framework.DefaultTestServerFlags(), "--feature-gates=InPlacePodVerticalScaling=true"), + framework.SharedEtcd()) + defer server.TearDownFn() + + client := clientset.NewForConfigOrDie(server.ClientConfig) + + ns := framework.CreateNamespaceOrDie(client, "pod-create-ephemeral-containers", t) + defer framework.DeleteNamespaceOrDie(client, ns, t) + + testPod := func(name string) *v1.Pod { + return &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "fake-name", + Image: "fakeimage", + }, + }, + }, + } + } + + resizeCases := []struct { + name string + originalRes v1.ResourceRequirements + resize v1.ResourceRequirements + valid bool + }{ + { + name: "cpu request change", + originalRes: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("10m"), + }, + }, + resize: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("20m"), + }, + }, + valid: true, + }, + { + name: "memory request change", + originalRes: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("1Gi"), + }, + }, + resize: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceMemory: resource.MustParse("2Gi"), + }, + }, + valid: true, + }, + { + name: "storage request change", + originalRes: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceEphemeralStorage: resource.MustParse("1Gi"), + }, + }, + resize: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceEphemeralStorage: resource.MustParse("2Gi"), + }, + }, + valid: false, + }, + } + + for _, tc := range resizeCases { + pod := testPod("resize") + pod.Spec.Containers[0].Resources = tc.originalRes + resp, err := client.CoreV1().Pods(ns.Name).Create(context.TODO(), pod, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("Unexpected error when creating pod: %v", err) + integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name) + } + + // Part 1. Resize + resp.Spec.Containers[0].Resources = tc.resize + if _, err := client.CoreV1().Pods(ns.Name).Update(context.TODO(), resp, metav1.UpdateOptions{}); err == nil { + t.Fatalf("Unexpected allowed pod update") + integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name) + } else if !strings.Contains(err.Error(), "spec: Forbidden: pod updates may not change fields other than") { + t.Fatalf("Unexpected error when updating pod container resources: %v", err) + } + + resp, err = client.CoreV1().Pods(ns.Name).Resize(context.TODO(), resp.Name, resp, metav1.UpdateOptions{}) + if tc.valid && err != nil { + t.Fatalf("Unexpected pod resize failure: %v", err) + integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name) + } + if !tc.valid && err == nil { + t.Fatalf("Unexpected pod resize success") + integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name) + } + + // Part 2. Rollback + if !tc.valid { + integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name) + continue + } + resp.Spec.Containers[0].Resources = tc.originalRes + _, err = client.CoreV1().Pods(ns.Name).Resize(context.TODO(), resp.Name, resp, metav1.UpdateOptions{}) + if tc.valid && err != nil { + t.Fatalf("Unexpected pod resize failure: %v", err) + integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name) + } + if !tc.valid && err == nil { + t.Fatalf("Unexpected pod resize success") + integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name) + } + + integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name) + } + + patchCases := []struct { + name string + originalRes v1.ResourceRequirements + patchBody string + patchType types.PatchType + valid bool + }{ + { + name: "cpu request change (strategic)", + originalRes: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("10m"), + }, + }, + patchType: types.StrategicMergePatchType, + patchBody: `{ + "spec":{ + "containers":[ + { + "name":"fake-name", + "resources": { + "requests": { + "cpu":"20m" + } + } + } + ] + } + }`, + valid: true, + }, + { + name: "cpu request change (merge)", + originalRes: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("10m"), + }, + }, + patchType: types.MergePatchType, + patchBody: `{ + "spec":{ + "containers":[ + { + "name":"fake-name", + "resources": { + "requests": { + "cpu":"20m" + } + } + } + ] + } + }`, + valid: true, + }, + { + name: "cpu request change (JSON)", + originalRes: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("10m"), + }, + }, + patchType: types.JSONPatchType, + patchBody: `[{ + "op":"add", + "path":"/spec/containers", + "value":[{ + "name":"fake-name", + "resources": { + "requests": { + "cpu":"20m" + } + } + }] + }]`, + valid: true, + }, + { + name: "storage request change (merge)", + originalRes: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("10m"), + }, + }, + patchType: types.MergePatchType, + patchBody: `{ + "spec":{ + "containers":[ + { + "name":"fake-name", + "resources": { + "requests": { + "ephemeral-storage":"20m" + } + } + } + ] + } + }`, + valid: false, + }, + } + + for _, tc := range patchCases { + pod := testPod("resize") + pod.Spec.Containers[0].Resources = tc.originalRes + if _, err := client.CoreV1().Pods(ns.Name).Create(context.TODO(), pod, metav1.CreateOptions{}); err != nil { + t.Fatalf("Unexpected error when creating pod: %v", err) + integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name) + } + + if _, err := client.CoreV1().Pods(ns.Name).Patch(context.TODO(), pod.Name, tc.patchType, []byte(tc.patchBody), metav1.PatchOptions{}, "resize"); tc.valid && err != nil { + t.Fatalf("Unexpected pod resize failure: %v", err) + integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name) + } else if !tc.valid && err == nil { + t.Fatalf("Unexpected pod resize success") + integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name) + } + integration.DeletePodOrErrorf(t, client, ns.Name, pod.Name) + } +} + func TestMutablePodSchedulingDirectives(t *testing.T) { // Disable ServiceAccount admission plugin as we don't have serviceaccount controller running. server := kubeapiservertesting.StartTestServerOrDie(t, nil, framework.DefaultTestServerFlags(), framework.SharedEtcd()) diff --git a/test/integration/scheduler/queue_test.go b/test/integration/scheduler/queue_test.go index f874c558b9d..70f21b1ac0a 100644 --- a/test/integration/scheduler/queue_test.go +++ b/test/integration/scheduler/queue_test.go @@ -406,8 +406,8 @@ func TestCoreResourceEnqueue(t *testing.T) { triggerFn: func(testCtx *testutils.TestContext) (map[framework.ClusterEvent]uint64, error) { // Trigger a PodUpdate event by reducing cpu requested by pod1. // It makes Pod1 schedulable. - if _, err := testCtx.ClientSet.CoreV1().Pods(testCtx.NS.Name).Update(testCtx.Ctx, st.MakePod().Name("pod1").Req(map[v1.ResourceName]string{v1.ResourceCPU: "2"}).Container("image").Obj(), metav1.UpdateOptions{}); err != nil { - return nil, fmt.Errorf("failed to update the pod: %w", err) + if _, err := testCtx.ClientSet.CoreV1().Pods(testCtx.NS.Name).Resize(testCtx.Ctx, "pod1", st.MakePod().Name("pod1").Req(map[v1.ResourceName]string{v1.ResourceCPU: "2"}).Container("image").Obj(), metav1.UpdateOptions{}); err != nil { + return nil, fmt.Errorf("failed to resize the pod: %w", err) } return map[framework.ClusterEvent]uint64{{Resource: unschedulablePod, ActionType: framework.UpdatePodScaleDown}: 1}, nil },