Add e2e tests

Signed-off-by: Itamar Holder <iholder@redhat.com>
This commit is contained in:
Itamar Holder
2025-11-05 10:16:30 +02:00
parent af66766bf5
commit 792f1643d4

View File

@@ -21,7 +21,9 @@ package e2enode
import (
"context"
"errors"
"fmt"
"os"
"strings"
"time"
@@ -31,6 +33,7 @@ import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/kubernetes/pkg/features"
kubeletevents "k8s.io/kubernetes/pkg/kubelet/events"
"k8s.io/kubernetes/pkg/kubelet/images"
"k8s.io/kubernetes/test/e2e/feature"
@@ -94,6 +97,131 @@ var _ = SIGDescribe(feature.CriProxy, framework.WithSerial(), func() {
})
framework.Context("Image volume digest error handling", feature.CriProxy, framework.WithFeatureGate(features.ImageVolumeWithDigest), func() {
ginkgo.BeforeEach(func() {
if e2eCriProxy == nil {
ginkgo.Skip("Skip the test since the CRI Proxy is undefined. Please run with --cri-proxy-enabled=true")
}
if err := resetCRIProxyInjector(e2eCriProxy); err != nil {
ginkgo.Skip("Skip the test since the CRI Proxy is undefined.")
}
ginkgo.DeferCleanup(func() error {
return resetCRIProxyInjector(e2eCriProxy)
})
})
getImageVolumePod := func() *v1.Pod {
return &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "image-vol-test-" + string(uuid.NewUUID()),
Namespace: f.Namespace.Name,
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "image-vol-container-" + string(uuid.NewUUID()),
Image: imageutils.GetPauseImageName(),
VolumeMounts: []v1.VolumeMount{
{
Name: volumeName,
MountPath: "/image-volume-" + string(uuid.NewUUID()),
},
},
},
},
Volumes: []v1.Volume{
{
Name: volumeName,
VolumeSource: v1.VolumeSource{
Image: &v1.ImageVolumeSource{
Reference: imageutils.GetPauseImageName(),
PullPolicy: v1.PullAlways,
},
},
},
},
},
}
}
waitForPodContainerStatuses := func(pod *v1.Pod) *v1.Pod {
ginkgo.By("Waiting for the pod container statuses")
var err error
gomega.Eventually(func() []v1.ContainerStatus {
pod, err = f.ClientSet.CoreV1().Pods(f.Namespace.Name).Get(context.Background(), pod.Name, metav1.GetOptions{})
framework.ExpectNoError(err)
return pod.Status.ContainerStatuses
}).WithPolling(5*time.Second).WithTimeout(2*time.Minute).Should(gomega.HaveLen(1), "couldn't find expected container status")
return pod
}
getVolumeMountStatus := func(pod *v1.Pod) v1.VolumeMountStatus {
ginkgo.By("Finding the pod volume mount status")
var volMountStatus *v1.VolumeMountStatus
containerStatus := pod.Status.ContainerStatuses[0]
for i := range pod.Status.ContainerStatuses[0].VolumeMounts {
if containerStatus.VolumeMounts[i].Name == volumeName {
volMountStatus = &containerStatus.VolumeMounts[i]
break
}
}
gomega.ExpectWithOffset(1, volMountStatus).ToNot(gomega.BeNil(), "couldn't find expected volume mount status")
return *volMountStatus
}
ginkgo.It("should expect error log when ImageStatus fails for image volume digest", func(ctx context.Context) {
const imageStatusErrMsg = "mock error message - ImageStatus failed"
err := addCRIProxyInjector(e2eCriProxy, func(apiName string) error {
if apiName == criproxy.ImageStatus {
return errors.New(imageStatusErrMsg)
}
return nil
})
framework.ExpectNoError(err)
pod := getImageVolumePod()
pod = e2epod.NewPodClient(f).Create(ctx, pod)
pod = waitForPodContainerStatuses(pod)
volMountStatus := getVolumeMountStatus(pod)
if volMountStatus.VolumeStatus.Image != nil {
ginkgo.Fail(fmt.Sprintf("ImageRef should not be set when ImageStatus fails, but got: %s", volMountStatus.VolumeStatus.Image.ImageRef))
}
ginkgo.By("Expecting an error when ImageStatus fails")
gomega.Eventually(func() error {
return verifyErrorInKubeletLogs(imageStatusErrMsg)
}).WithPolling(5*time.Second).WithTimeout(20*time.Second).ToNot(gomega.HaveOccurred(), "Could not verify error in kubelet logs")
})
ginkgo.It("should expect error log for image volume with empty Image.Image", func(ctx context.Context) {
// This test verifies error handling when imageSpec.Image is empty (curVolumeMount.Image.Image == "").
pod := getImageVolumePod()
pod = e2epod.NewPodClient(f).Create(ctx, pod)
pod = waitForPodContainerStatuses(pod)
volMountStatus := getVolumeMountStatus(pod)
if volMountStatus.VolumeStatus.Image != nil {
ginkgo.Fail(fmt.Sprintf("ImageRef should not be set when ImageStatus fails, but got: %s", volMountStatus.VolumeStatus.Image.ImageRef))
}
ginkgo.By("Expecting an error when imageSpec.Image is empty")
gomega.Eventually(func() error {
return verifyErrorInKubeletLogs("image was not found")
}).WithPolling(5*time.Second).WithTimeout(20*time.Second).ToNot(gomega.HaveOccurred(), "Could not verify error in kubelet logs")
})
})
ginkgo.Context("Inject a pull image timeout exception into the CriProxy", func() {
ginkgo.BeforeEach(func() {
if err := resetCRIProxyInjector(e2eCriProxy); err != nil {
@@ -184,3 +312,16 @@ func newPullImageAlwaysPod() *v1.Pod {
}
return pod
}
func verifyErrorInKubeletLogs(errorMsg string) error {
kubeletLog, err := os.ReadFile(framework.TestContext.ReportDir + "/kubelet.log")
if err != nil {
return fmt.Errorf("could not read kubelet logs: %w", err)
}
if !strings.Contains(string(kubeletLog), errorMsg) {
return fmt.Errorf("error message \"%s\" not found in kubelet logs", errorMsg)
}
return nil
}