From 6da0175857768cd296549aa1cd3c6e34f9be45eb Mon Sep 17 00:00:00 2001 From: carlory Date: Tue, 29 Oct 2024 14:09:06 +0800 Subject: [PATCH 1/2] fix --- test/e2e/framework/pod/wait.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/framework/pod/wait.go b/test/e2e/framework/pod/wait.go index 6d2cfe9865a..d6feb76dee0 100644 --- a/test/e2e/framework/pod/wait.go +++ b/test/e2e/framework/pod/wait.go @@ -498,7 +498,7 @@ func WaitForPodNameUnschedulableInNamespace(ctx context.Context, c clientset.Int } } if pod.Status.Phase == v1.PodRunning || pod.Status.Phase == v1.PodSucceeded || pod.Status.Phase == v1.PodFailed { - return true, fmt.Errorf("Expected pod %q in namespace %q to be in phase Pending, but got phase: %v", podName, namespace, pod.Status.Phase) + return true, fmt.Errorf("Expected pod %q in namespace %q to be in phase Pending, but got phase: %v, pod: \n%s", podName, namespace, pod.Status.Phase, format.Object(pod, 1)) } return false, nil }) From a2b1fc0f7ad9aedddd155a613b7f3fb59839b7f4 Mon Sep 17 00:00:00 2001 From: carlory Date: Tue, 29 Oct 2024 15:30:17 +0800 Subject: [PATCH 2/2] Move PodRejectionStatus into e2e/node from e2e/common/node --- test/e2e/common/node/pod_admission.go | 79 ------------------ test/e2e/framework/pod/wait.go | 2 +- test/e2e/node/pod_admission.go | 110 ++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 80 deletions(-) create mode 100644 test/e2e/node/pod_admission.go diff --git a/test/e2e/common/node/pod_admission.go b/test/e2e/common/node/pod_admission.go index 7b0db176ca4..6a411522edf 100644 --- a/test/e2e/common/node/pod_admission.go +++ b/test/e2e/common/node/pod_admission.go @@ -20,10 +20,8 @@ import ( "context" "github.com/onsi/ginkgo/v2" - "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/kubernetes/test/e2e/framework" @@ -67,83 +65,6 @@ var _ = SIGDescribe("PodOSRejection", framework.WithNodeConformance(), func() { }) }) -var _ = SIGDescribe("PodRejectionStatus", func() { - f := framework.NewDefaultFramework("pod-rejection-status") - f.NamespacePodSecurityLevel = admissionapi.LevelBaseline - ginkgo.Context("Kubelet", func() { - ginkgo.It("should reject pod when the node didn't have enough resource", func(ctx context.Context) { - node, err := e2enode.GetRandomReadySchedulableNode(ctx, f.ClientSet) - framework.ExpectNoError(err, "Failed to get a ready schedulable node") - - // Create a pod that requests more CPU than the node has - pod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pod-out-of-cpu", - Namespace: f.Namespace.Name, - }, - Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: "pod-out-of-cpu", - Image: imageutils.GetPauseImageName(), - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceCPU: resource.MustParse("1000000000000"), // requests more CPU than any node has - }, - }, - }, - }, - }, - } - - pod = e2epod.NewPodClient(f).Create(ctx, pod) - - // Wait for the scheduler to update the pod status - err = e2epod.WaitForPodNameUnschedulableInNamespace(ctx, f.ClientSet, pod.Name, pod.Namespace) - framework.ExpectNoError(err) - - // Fetch the pod to get the latest status which should be last one observed by the scheduler - // before it rejected the pod - pod, err = f.ClientSet.CoreV1().Pods(pod.Namespace).Get(ctx, pod.Name, metav1.GetOptions{}) - framework.ExpectNoError(err) - - // force assign the Pod to a node in order to get rejection status later - binding := &v1.Binding{ - ObjectMeta: metav1.ObjectMeta{ - Name: pod.Name, - Namespace: pod.Namespace, - UID: pod.UID, - }, - Target: v1.ObjectReference{ - Kind: "Node", - Name: node.Name, - }, - } - err = f.ClientSet.CoreV1().Pods(pod.Namespace).Bind(ctx, binding, metav1.CreateOptions{}) - framework.ExpectNoError(err) - - // kubelet has rejected the pod - err = e2epod.WaitForPodFailedReason(ctx, f.ClientSet, pod, "OutOfcpu", f.Timeouts.PodStartShort) - framework.ExpectNoError(err) - - // fetch the reject Pod and compare the status - gotPod, err := f.ClientSet.CoreV1().Pods(pod.Namespace).Get(ctx, pod.Name, metav1.GetOptions{}) - framework.ExpectNoError(err) - - // This detects if there are any new fields in Status that were dropped by the pod rejection. - // These new fields either should be kept by kubelet's admission or added explicitly in the list of fields that are having a different value or must be cleared. - expectedStatus := pod.Status.DeepCopy() - expectedStatus.Phase = gotPod.Status.Phase - expectedStatus.Conditions = nil - expectedStatus.Message = gotPod.Status.Message - expectedStatus.Reason = gotPod.Status.Reason - expectedStatus.StartTime = gotPod.Status.StartTime - // expectedStatus.QOSClass keep it as is - gomega.Expect(gotPod.Status).To(gomega.Equal(*expectedStatus)) - }) - }) -}) - // findLinuxNode finds a Linux node that is Ready and Schedulable func findLinuxNode(ctx context.Context, f *framework.Framework) (v1.Node, error) { selector := labels.Set{"kubernetes.io/os": "linux"}.AsSelector() diff --git a/test/e2e/framework/pod/wait.go b/test/e2e/framework/pod/wait.go index d6feb76dee0..6d2cfe9865a 100644 --- a/test/e2e/framework/pod/wait.go +++ b/test/e2e/framework/pod/wait.go @@ -498,7 +498,7 @@ func WaitForPodNameUnschedulableInNamespace(ctx context.Context, c clientset.Int } } if pod.Status.Phase == v1.PodRunning || pod.Status.Phase == v1.PodSucceeded || pod.Status.Phase == v1.PodFailed { - return true, fmt.Errorf("Expected pod %q in namespace %q to be in phase Pending, but got phase: %v, pod: \n%s", podName, namespace, pod.Status.Phase, format.Object(pod, 1)) + return true, fmt.Errorf("Expected pod %q in namespace %q to be in phase Pending, but got phase: %v", podName, namespace, pod.Status.Phase) } return false, nil }) diff --git a/test/e2e/node/pod_admission.go b/test/e2e/node/pod_admission.go new file mode 100644 index 00000000000..bd53f33c88a --- /dev/null +++ b/test/e2e/node/pod_admission.go @@ -0,0 +1,110 @@ +/* +Copyright 2024 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 node + +import ( + "context" + + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/test/e2e/framework" + e2enode "k8s.io/kubernetes/test/e2e/framework/node" + e2epod "k8s.io/kubernetes/test/e2e/framework/pod" + imageutils "k8s.io/kubernetes/test/utils/image" + admissionapi "k8s.io/pod-security-admission/api" +) + +var _ = SIGDescribe("PodRejectionStatus", func() { + f := framework.NewDefaultFramework("pod-rejection-status") + f.NamespacePodSecurityLevel = admissionapi.LevelBaseline + ginkgo.Context("Kubelet", func() { + ginkgo.It("should reject pod when the node didn't have enough resource", func(ctx context.Context) { + node, err := e2enode.GetRandomReadySchedulableNode(ctx, f.ClientSet) + framework.ExpectNoError(err, "Failed to get a ready schedulable node") + + // Create a pod that requests more CPU than the node has + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-out-of-cpu", + Namespace: f.Namespace.Name, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "pod-out-of-cpu", + Image: imageutils.GetPauseImageName(), + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse("1000000000000"), // requests more CPU than any node has + }, + }, + }, + }, + }, + } + + pod, err = f.ClientSet.CoreV1().Pods(f.Namespace.Name).Create(ctx, pod, metav1.CreateOptions{}) + framework.ExpectNoError(err) + + // Wait for the scheduler to update the pod status + err = e2epod.WaitForPodNameUnschedulableInNamespace(ctx, f.ClientSet, pod.Name, pod.Namespace) + framework.ExpectNoError(err) + + // Fetch the pod to get the latest status which should be last one observed by the scheduler + // before it rejected the pod + pod, err = f.ClientSet.CoreV1().Pods(pod.Namespace).Get(ctx, pod.Name, metav1.GetOptions{}) + framework.ExpectNoError(err) + + // force assign the Pod to a node in order to get rejection status later + binding := &v1.Binding{ + ObjectMeta: metav1.ObjectMeta{ + Name: pod.Name, + Namespace: pod.Namespace, + UID: pod.UID, + }, + Target: v1.ObjectReference{ + Kind: "Node", + Name: node.Name, + }, + } + err = f.ClientSet.CoreV1().Pods(pod.Namespace).Bind(ctx, binding, metav1.CreateOptions{}) + framework.ExpectNoError(err) + + // kubelet has rejected the pod + err = e2epod.WaitForPodFailedReason(ctx, f.ClientSet, pod, "OutOfcpu", f.Timeouts.PodStartShort) + framework.ExpectNoError(err) + + // fetch the reject Pod and compare the status + gotPod, err := f.ClientSet.CoreV1().Pods(pod.Namespace).Get(ctx, pod.Name, metav1.GetOptions{}) + framework.ExpectNoError(err) + + // This detects if there are any new fields in Status that were dropped by the pod rejection. + // These new fields either should be kept by kubelet's admission or added explicitly in the list of fields that are having a different value or must be cleared. + expectedStatus := pod.Status.DeepCopy() + expectedStatus.Phase = gotPod.Status.Phase + expectedStatus.Conditions = nil + expectedStatus.Message = gotPod.Status.Message + expectedStatus.Reason = gotPod.Status.Reason + expectedStatus.StartTime = gotPod.Status.StartTime + // expectedStatus.QOSClass keep it as is + gomega.Expect(gotPod.Status).To(gomega.Equal(*expectedStatus)) + }) + }) +})