Skip to content

Commit debace2

Browse files
Merge pull request #30015 from saschagrunert/image-volume-e2e
OCPNODE-3004: Add ImageVolume e2e tests
2 parents 48d56f2 + 3aaa9ad commit debace2

File tree

3 files changed

+214
-0
lines changed

3 files changed

+214
-0
lines changed

test/extended/node/image_volume.go

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package node
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
g "github.com/onsi/ginkgo/v2"
8+
o "github.com/onsi/gomega"
9+
10+
v1 "k8s.io/api/core/v1"
11+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12+
"k8s.io/kubernetes/pkg/kubelet/kuberuntime"
13+
"k8s.io/kubernetes/test/e2e/framework"
14+
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
15+
admissionapi "k8s.io/pod-security-admission/api"
16+
17+
exutil "github.com/openshift/origin/test/extended/util"
18+
)
19+
20+
var _ = g.Describe("[sig-node] [FeatureGate:ImageVolume] ImageVolume", func() {
21+
defer g.GinkgoRecover()
22+
23+
f := framework.NewDefaultFramework("image-volume")
24+
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
25+
26+
var (
27+
oc = exutil.NewCLI("image-volume")
28+
podName = "image-volume-test"
29+
image = "image-registry.openshift-image-registry.svc:5000/openshift/cli:latest"
30+
)
31+
32+
g.BeforeEach(func() {
33+
// Skip if ImageVolume feature is not enabled
34+
if !exutil.IsTechPreviewNoUpgrade(context.TODO(), oc.AdminConfigClient()) {
35+
g.Skip("skipping, this feature is only supported on TechPreviewNoUpgrade clusters")
36+
}
37+
})
38+
39+
g.It("should succeed with pod and pull policy of Always", func(ctx context.Context) {
40+
pod := buildPodWithImageVolume(f.Namespace.Name, "", podName, image)
41+
createPodAndWaitForRunning(ctx, oc, pod)
42+
verifyVolumeMounted(f, pod, "ls", "/mnt/image/bin/oc")
43+
})
44+
45+
g.It("should handle multiple image volumes", func(ctx context.Context) {
46+
pod := buildPodWithMultipleImageVolumes(f.Namespace.Name, "", podName, image, image)
47+
createPodAndWaitForRunning(ctx, oc, pod)
48+
verifyVolumeMounted(f, pod, "ls", "/mnt/image/bin/oc")
49+
verifyVolumeMounted(f, pod, "ls", "/mnt/image2/bin/oc")
50+
})
51+
52+
g.It("should fail when image does not exist", func(ctx context.Context) {
53+
pod := buildPodWithImageVolume(f.Namespace.Name, "", podName, "nonexistent:latest")
54+
55+
g.By("Creating a pod with non-existent image volume")
56+
_, err := oc.AdminKubeClient().CoreV1().Pods(f.Namespace.Name).Create(ctx, pod, metav1.CreateOptions{})
57+
o.Expect(err).NotTo(o.HaveOccurred())
58+
59+
g.By("Waiting for pod to be ImagePullBackOff")
60+
err = e2epod.WaitForPodCondition(ctx, oc.AdminKubeClient(), pod.Namespace, pod.Name, "ImagePullBackOff", 60*time.Second, func(pod *v1.Pod) (bool, error) {
61+
return len(pod.Status.ContainerStatuses) > 0 &&
62+
pod.Status.ContainerStatuses[0].State.Waiting != nil &&
63+
pod.Status.ContainerStatuses[0].State.Waiting.Reason == "ImagePullBackOff", nil
64+
})
65+
o.Expect(err).NotTo(o.HaveOccurred())
66+
})
67+
68+
g.It("should succeed if image volume is not existing but unused", func(ctx context.Context) {
69+
pod := buildPodWithImageVolume(f.Namespace.Name, "", podName, "nonexistent:latest")
70+
pod.Spec.Containers[0].VolumeMounts = []v1.VolumeMount{}
71+
createPodAndWaitForRunning(ctx, oc, pod)
72+
// The container has no image volume mount, so just checking running is enough
73+
})
74+
75+
g.It("should succeed with multiple pods and same image on the same node", func(ctx context.Context) {
76+
pod1 := buildPodWithImageVolume(f.Namespace.Name, "", podName, image)
77+
pod1 = createPodAndWaitForRunning(ctx, oc, pod1)
78+
79+
pod2 := buildPodWithImageVolume(f.Namespace.Name, pod1.Spec.NodeName, podName+"-2", image)
80+
pod2 = createPodAndWaitForRunning(ctx, oc, pod2)
81+
82+
verifyVolumeMounted(f, pod1, "ls", "/mnt/image/bin/oc")
83+
verifyVolumeMounted(f, pod2, "ls", "/mnt/image/bin/oc")
84+
})
85+
86+
g.Context("when subPath is used", func() {
87+
g.It("should handle image volume with subPath", func(ctx context.Context) {
88+
pod := buildPodWithImageVolumeSubPath(f.Namespace.Name, "", podName, image, "bin")
89+
createPodAndWaitForRunning(ctx, oc, pod)
90+
verifyVolumeMounted(f, pod, "ls", "/mnt/image/oc")
91+
})
92+
93+
g.It("should fail to mount image volume with invalid subPath", func(ctx context.Context) {
94+
pod := buildPodWithImageVolumeSubPath(f.Namespace.Name, "", podName, image, "noexist")
95+
g.By("Creating a pod with image volume and subPath")
96+
_, err := oc.AdminKubeClient().CoreV1().Pods(f.Namespace.Name).Create(ctx, pod, metav1.CreateOptions{})
97+
o.Expect(err).NotTo(o.HaveOccurred())
98+
99+
g.By("Waiting for a pod to fail")
100+
err = e2epod.WaitForPodContainerToFail(ctx, oc.AdminKubeClient(), pod.Namespace, pod.Name, 0, kuberuntime.ErrCreateContainer.Error(), 60*time.Second)
101+
o.Expect(err).NotTo(o.HaveOccurred())
102+
})
103+
})
104+
})
105+
106+
func createPodAndWaitForRunning(ctx context.Context, oc *exutil.CLI, pod *v1.Pod) *v1.Pod {
107+
g.By("Creating a pod")
108+
_, err := oc.AdminKubeClient().CoreV1().Pods(pod.Namespace).Create(ctx, pod, metav1.CreateOptions{})
109+
o.Expect(err).NotTo(o.HaveOccurred())
110+
111+
g.By("Waiting for pod to be running")
112+
err = e2epod.WaitForPodRunningInNamespace(ctx, oc.AdminKubeClient(), pod)
113+
o.Expect(err).NotTo(o.HaveOccurred())
114+
115+
created, err := oc.AdminKubeClient().CoreV1().Pods(pod.Namespace).Get(ctx, pod.Name, metav1.GetOptions{})
116+
o.Expect(err).NotTo(o.HaveOccurred())
117+
return created
118+
}
119+
120+
func verifyVolumeMounted(f *framework.Framework, pod *v1.Pod, commands ...string) {
121+
g.By("Verifying image volume in pod is mounted")
122+
stdout := e2epod.ExecCommandInContainer(f, pod.Name, pod.Spec.Containers[0].Name, commands...)
123+
o.Expect(stdout).NotTo(o.BeEmpty())
124+
}
125+
126+
func buildPodWithImageVolume(namespace, nodeName, podName, image string) *v1.Pod {
127+
pod := &v1.Pod{
128+
ObjectMeta: metav1.ObjectMeta{
129+
Name: podName,
130+
Namespace: namespace,
131+
},
132+
Spec: v1.PodSpec{
133+
NodeName: nodeName,
134+
Containers: []v1.Container{
135+
{
136+
Name: "test-container",
137+
Image: "image-registry.openshift-image-registry.svc:5000/openshift/tools:latest",
138+
Command: []string{"sh", "-c", "trap 'exit 0' TERM INT; sleep infinity & wait"},
139+
VolumeMounts: []v1.VolumeMount{
140+
{
141+
Name: "image-vol",
142+
MountPath: "/mnt/image",
143+
},
144+
},
145+
},
146+
},
147+
Volumes: []v1.Volume{
148+
{
149+
Name: "image-vol",
150+
VolumeSource: v1.VolumeSource{
151+
Image: &v1.ImageVolumeSource{
152+
Reference: image,
153+
},
154+
},
155+
},
156+
},
157+
RestartPolicy: v1.RestartPolicyNever,
158+
},
159+
}
160+
return pod
161+
}
162+
163+
func buildPodWithImageVolumeSubPath(namespace, nodeName, podName, image, subPath string) *v1.Pod {
164+
pod := buildPodWithImageVolume(namespace, nodeName, podName, image)
165+
pod.Spec.Containers[0].VolumeMounts[0].SubPath = subPath
166+
return pod
167+
}
168+
169+
func buildPodWithMultipleImageVolumes(namespace, nodeName, podName, image1, image2 string) *v1.Pod {
170+
pod := buildPodWithImageVolume(namespace, nodeName, podName, image1)
171+
pod.Spec.Volumes = append(pod.Spec.Volumes, v1.Volume{
172+
Name: "image-vol-2",
173+
VolumeSource: v1.VolumeSource{
174+
Image: &v1.ImageVolumeSource{
175+
Reference: image2,
176+
},
177+
},
178+
})
179+
pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, v1.VolumeMount{
180+
Name: "image-vol-2",
181+
MountPath: "/mnt/image2",
182+
})
183+
return pod
184+
}

test/extended/util/annotate/generated/zz_generated.annotations.go

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

zz_generated.manifests/test-reporting.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,22 @@ spec:
412412
- testName: '[sig-imageregistry][OCPFeatureGate:ImageStreamImportMode][Serial]
413413
ImageStream API import mode should be PreserveOriginal or Legacy depending
414414
on desired.architecture field in the CV [apigroup:image.openshift.io]'
415+
- featureGate: ImageVolume
416+
tests:
417+
- testName: '[sig-node] [FeatureGate:ImageVolume] ImageVolume should fail when
418+
image does not exist'
419+
- testName: '[sig-node] [FeatureGate:ImageVolume] ImageVolume should handle multiple
420+
image volumes'
421+
- testName: '[sig-node] [FeatureGate:ImageVolume] ImageVolume should succeed if
422+
image volume is not existing but unused'
423+
- testName: '[sig-node] [FeatureGate:ImageVolume] ImageVolume should succeed with
424+
multiple pods and same image on the same node'
425+
- testName: '[sig-node] [FeatureGate:ImageVolume] ImageVolume should succeed with
426+
pod and pull policy of Always'
427+
- testName: '[sig-node] [FeatureGate:ImageVolume] ImageVolume when subPath is
428+
used should fail to mount image volume with invalid subPath'
429+
- testName: '[sig-node] [FeatureGate:ImageVolume] ImageVolume when subPath is
430+
used should handle image volume with subPath'
415431
- featureGate: InPlacePodVerticalScaling
416432
tests:
417433
- testName: '[sig-node] Pod InPlace Resize Container [FeatureGate:InPlacePodVerticalScaling]

0 commit comments

Comments
 (0)