1
16
17 package storage
18
19 import (
20 "context"
21 "fmt"
22 "time"
23
24 v1 "k8s.io/api/core/v1"
25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 "k8s.io/apimachinery/pkg/util/uuid"
27 "k8s.io/kubernetes/test/e2e/framework"
28 e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
29 e2epodoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
30 e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
31 "k8s.io/kubernetes/test/e2e/nodefeature"
32 imageutils "k8s.io/kubernetes/test/utils/image"
33 admissionapi "k8s.io/pod-security-admission/api"
34
35 "github.com/onsi/ginkgo/v2"
36 "github.com/onsi/gomega"
37 )
38
39 var _ = SIGDescribe("Projected downwardAPI", func() {
40 f := framework.NewDefaultFramework("projected")
41 f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
42
43
44 const podLogTimeout = 2 * time.Minute
45 var podClient *e2epod.PodClient
46 ginkgo.BeforeEach(func() {
47 podClient = e2epod.NewPodClient(f)
48 })
49
50
55 framework.ConformanceIt("should provide podname only", f.WithNodeConformance(), func(ctx context.Context) {
56 podName := "downwardapi-volume-" + string(uuid.NewUUID())
57 pod := downwardAPIVolumePodForSimpleTest(podName, "/etc/podinfo/podname")
58
59 e2epodoutput.TestContainerOutput(ctx, f, "downward API volume plugin", pod, 0, []string{
60 fmt.Sprintf("%s\n", podName),
61 })
62 })
63
64
70 framework.ConformanceIt("should set DefaultMode on files [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
71 podName := "downwardapi-volume-" + string(uuid.NewUUID())
72 defaultMode := int32(0400)
73 pod := projectedDownwardAPIVolumePodForModeTest(podName, "/etc/podinfo/podname", nil, &defaultMode)
74
75 e2epodoutput.TestContainerOutput(ctx, f, "downward API volume plugin", pod, 0, []string{
76 "mode of file \"/etc/podinfo/podname\": -r--------",
77 })
78 })
79
80
86 framework.ConformanceIt("should set mode on item file [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
87 podName := "downwardapi-volume-" + string(uuid.NewUUID())
88 mode := int32(0400)
89 pod := projectedDownwardAPIVolumePodForModeTest(podName, "/etc/podinfo/podname", &mode, nil)
90
91 e2epodoutput.TestContainerOutput(ctx, f, "downward API volume plugin", pod, 0, []string{
92 "mode of file \"/etc/podinfo/podname\": -r--------",
93 })
94 })
95
96 f.It("should provide podname as non-root with fsgroup [LinuxOnly]", nodefeature.FSGroup, func(ctx context.Context) {
97
98 e2eskipper.SkipIfNodeOSDistroIs("windows")
99 podName := "metadata-volume-" + string(uuid.NewUUID())
100 gid := int64(1234)
101 pod := downwardAPIVolumePodForSimpleTest(podName, "/etc/podinfo/podname")
102 pod.Spec.SecurityContext = &v1.PodSecurityContext{
103 FSGroup: &gid,
104 }
105 setPodNonRootUser(pod)
106 e2epodoutput.TestContainerOutput(ctx, f, "downward API volume plugin", pod, 0, []string{
107 fmt.Sprintf("%s\n", podName),
108 })
109 })
110
111 f.It("should provide podname as non-root with fsgroup and defaultMode [LinuxOnly]", nodefeature.FSGroup, func(ctx context.Context) {
112
113 e2eskipper.SkipIfNodeOSDistroIs("windows")
114 podName := "metadata-volume-" + string(uuid.NewUUID())
115 gid := int64(1234)
116 mode := int32(0440)
117 pod := projectedDownwardAPIVolumePodForModeTest(podName, "/etc/podinfo/podname", &mode, nil)
118 pod.Spec.SecurityContext = &v1.PodSecurityContext{
119 FSGroup: &gid,
120 }
121 setPodNonRootUser(pod)
122 e2epodoutput.TestContainerOutput(ctx, f, "downward API volume plugin", pod, 0, []string{
123 "mode of file \"/etc/podinfo/podname\": -r--r-----",
124 })
125 })
126
127
132 framework.ConformanceIt("should update labels on modification", f.WithNodeConformance(), func(ctx context.Context) {
133 labels := map[string]string{}
134 labels["key1"] = "value1"
135 labels["key2"] = "value2"
136
137 podName := "labelsupdate" + string(uuid.NewUUID())
138 pod := projectedDownwardAPIVolumePodForUpdateTest(podName, labels, map[string]string{}, "/etc/podinfo/labels")
139 containerName := "client-container"
140 ginkgo.By("Creating the pod")
141 podClient.CreateSync(ctx, pod)
142
143 gomega.Eventually(ctx, func() (string, error) {
144 return e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, podName, containerName)
145 },
146 podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("key1=\"value1\"\n"))
147
148
149 podClient.Update(ctx, podName, func(pod *v1.Pod) {
150 pod.Labels["key3"] = "value3"
151 })
152
153 gomega.Eventually(ctx, func() (string, error) {
154 return e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, pod.Name, containerName)
155 },
156 podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("key3=\"value3\"\n"))
157 })
158
159
164 framework.ConformanceIt("should update annotations on modification", f.WithNodeConformance(), func(ctx context.Context) {
165 annotations := map[string]string{}
166 annotations["builder"] = "bar"
167 podName := "annotationupdate" + string(uuid.NewUUID())
168 pod := projectedDownwardAPIVolumePodForUpdateTest(podName, map[string]string{}, annotations, "/etc/podinfo/annotations")
169
170 containerName := "client-container"
171 ginkgo.By("Creating the pod")
172 pod = podClient.CreateSync(ctx, pod)
173
174 gomega.Eventually(ctx, func() (string, error) {
175 return e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, pod.Name, containerName)
176 },
177 podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("builder=\"bar\"\n"))
178
179
180 podClient.Update(ctx, podName, func(pod *v1.Pod) {
181 pod.Annotations["builder"] = "foo"
182 })
183
184 gomega.Eventually(ctx, func() (string, error) {
185 return e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, pod.Name, containerName)
186 },
187 podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("builder=\"foo\"\n"))
188 })
189
190
195 framework.ConformanceIt("should provide container's cpu limit", f.WithNodeConformance(), func(ctx context.Context) {
196 podName := "downwardapi-volume-" + string(uuid.NewUUID())
197 pod := downwardAPIVolumeForContainerResources(podName, "/etc/podinfo/cpu_limit")
198
199 e2epodoutput.TestContainerOutput(ctx, f, "downward API volume plugin", pod, 0, []string{
200 fmt.Sprintf("2\n"),
201 })
202 })
203
204
209 framework.ConformanceIt("should provide container's memory limit", f.WithNodeConformance(), func(ctx context.Context) {
210 podName := "downwardapi-volume-" + string(uuid.NewUUID())
211 pod := downwardAPIVolumeForContainerResources(podName, "/etc/podinfo/memory_limit")
212
213 e2epodoutput.TestContainerOutput(ctx, f, "downward API volume plugin", pod, 0, []string{
214 "134217728\n",
215 })
216 })
217
218
223 framework.ConformanceIt("should provide container's cpu request", f.WithNodeConformance(), func(ctx context.Context) {
224 podName := "downwardapi-volume-" + string(uuid.NewUUID())
225 pod := downwardAPIVolumeForContainerResources(podName, "/etc/podinfo/cpu_request")
226
227 e2epodoutput.TestContainerOutput(ctx, f, "downward API volume plugin", pod, 0, []string{
228 fmt.Sprintf("1\n"),
229 })
230 })
231
232
237 framework.ConformanceIt("should provide container's memory request", f.WithNodeConformance(), func(ctx context.Context) {
238 podName := "downwardapi-volume-" + string(uuid.NewUUID())
239 pod := downwardAPIVolumeForContainerResources(podName, "/etc/podinfo/memory_request")
240
241 e2epodoutput.TestContainerOutput(ctx, f, "downward API volume plugin", pod, 0, []string{
242 fmt.Sprintf("33554432\n"),
243 })
244 })
245
246
251 framework.ConformanceIt("should provide node allocatable (cpu) as default cpu limit if the limit is not set", f.WithNodeConformance(), func(ctx context.Context) {
252 podName := "downwardapi-volume-" + string(uuid.NewUUID())
253 pod := downwardAPIVolumeForDefaultContainerResources(podName, "/etc/podinfo/cpu_limit")
254
255 e2epodoutput.TestContainerOutputRegexp(ctx, f, "downward API volume plugin", pod, 0, []string{"[1-9]"})
256 })
257
258
263 framework.ConformanceIt("should provide node allocatable (memory) as default memory limit if the limit is not set", f.WithNodeConformance(), func(ctx context.Context) {
264 podName := "downwardapi-volume-" + string(uuid.NewUUID())
265 pod := downwardAPIVolumeForDefaultContainerResources(podName, "/etc/podinfo/memory_limit")
266
267 e2epodoutput.TestContainerOutputRegexp(ctx, f, "downward API volume plugin", pod, 0, []string{"[1-9]"})
268 })
269 })
270
271 func projectedDownwardAPIVolumePodForModeTest(name, filePath string, itemMode, defaultMode *int32) *v1.Pod {
272 pod := projectedDownwardAPIVolumeBasePod(name, nil, nil)
273
274 pod.Spec.Containers = []v1.Container{
275 {
276 Name: "client-container",
277 Image: imageutils.GetE2EImage(imageutils.Agnhost),
278 Args: []string{"mounttest", "--file_mode=" + filePath},
279 VolumeMounts: []v1.VolumeMount{
280 {
281 Name: "podinfo",
282 MountPath: "/etc/podinfo",
283 },
284 },
285 },
286 }
287 if itemMode != nil {
288 pod.Spec.Volumes[0].VolumeSource.Projected.Sources[0].DownwardAPI.Items[0].Mode = itemMode
289 }
290 if defaultMode != nil {
291 pod.Spec.Volumes[0].VolumeSource.Projected.DefaultMode = defaultMode
292 }
293
294 return pod
295 }
296
297 func projectedDownwardAPIVolumePodForUpdateTest(name string, labels, annotations map[string]string, filePath string) *v1.Pod {
298 pod := projectedDownwardAPIVolumeBasePod(name, labels, annotations)
299
300 pod.Spec.Containers = []v1.Container{
301 {
302 Name: "client-container",
303 Image: imageutils.GetE2EImage(imageutils.Agnhost),
304 Args: []string{"mounttest", "--break_on_expected_content=false", "--retry_time=1200", "--file_content_in_loop=" + filePath},
305 VolumeMounts: []v1.VolumeMount{
306 {
307 Name: "podinfo",
308 MountPath: "/etc/podinfo",
309 ReadOnly: false,
310 },
311 },
312 },
313 }
314
315 applyLabelsAndAnnotationsToProjectedDownwardAPIPod(labels, annotations, pod)
316 return pod
317 }
318
319 func projectedDownwardAPIVolumeBasePod(name string, labels, annotations map[string]string) *v1.Pod {
320 pod := &v1.Pod{
321 ObjectMeta: metav1.ObjectMeta{
322 Name: name,
323 Labels: labels,
324 Annotations: annotations,
325 },
326 Spec: v1.PodSpec{
327 Volumes: []v1.Volume{
328 {
329 Name: "podinfo",
330 VolumeSource: v1.VolumeSource{
331 Projected: &v1.ProjectedVolumeSource{
332 Sources: []v1.VolumeProjection{
333 {
334 DownwardAPI: &v1.DownwardAPIProjection{
335 Items: []v1.DownwardAPIVolumeFile{
336 {
337 Path: "podname",
338 FieldRef: &v1.ObjectFieldSelector{
339 APIVersion: "v1",
340 FieldPath: "metadata.name",
341 },
342 },
343 {
344 Path: "cpu_limit",
345 ResourceFieldRef: &v1.ResourceFieldSelector{
346 ContainerName: "client-container",
347 Resource: "limits.cpu",
348 },
349 },
350 {
351 Path: "cpu_request",
352 ResourceFieldRef: &v1.ResourceFieldSelector{
353 ContainerName: "client-container",
354 Resource: "requests.cpu",
355 },
356 },
357 {
358 Path: "memory_limit",
359 ResourceFieldRef: &v1.ResourceFieldSelector{
360 ContainerName: "client-container",
361 Resource: "limits.memory",
362 },
363 },
364 {
365 Path: "memory_request",
366 ResourceFieldRef: &v1.ResourceFieldSelector{
367 ContainerName: "client-container",
368 Resource: "requests.memory",
369 },
370 },
371 },
372 },
373 },
374 },
375 },
376 },
377 },
378 },
379 RestartPolicy: v1.RestartPolicyNever,
380 },
381 }
382
383 return pod
384 }
385
386 func applyLabelsAndAnnotationsToProjectedDownwardAPIPod(labels, annotations map[string]string, pod *v1.Pod) {
387 if len(labels) > 0 {
388 pod.Spec.Volumes[0].VolumeSource.Projected.Sources[0].DownwardAPI.Items = append(pod.Spec.Volumes[0].VolumeSource.Projected.Sources[0].DownwardAPI.Items, v1.DownwardAPIVolumeFile{
389 Path: "labels",
390 FieldRef: &v1.ObjectFieldSelector{
391 APIVersion: "v1",
392 FieldPath: "metadata.labels",
393 },
394 })
395 }
396
397 if len(annotations) > 0 {
398 pod.Spec.Volumes[0].VolumeSource.Projected.Sources[0].DownwardAPI.Items = append(pod.Spec.Volumes[0].VolumeSource.Projected.Sources[0].DownwardAPI.Items, v1.DownwardAPIVolumeFile{
399 Path: "annotations",
400 FieldRef: &v1.ObjectFieldSelector{
401 APIVersion: "v1",
402 FieldPath: "metadata.annotations",
403 },
404 })
405 }
406 }
407
View as plain text