1
16
17 package storage
18
19 import (
20 "context"
21 "fmt"
22 "time"
23
24 v1 "k8s.io/api/core/v1"
25 "k8s.io/apimachinery/pkg/api/resource"
26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27 "k8s.io/apimachinery/pkg/util/uuid"
28 "k8s.io/kubernetes/test/e2e/framework"
29 e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
30 e2epodoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
31 e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
32 "k8s.io/kubernetes/test/e2e/nodefeature"
33 imageutils "k8s.io/kubernetes/test/utils/image"
34 admissionapi "k8s.io/pod-security-admission/api"
35
36 "github.com/onsi/ginkgo/v2"
37 "github.com/onsi/gomega"
38 )
39
40 var _ = SIGDescribe("Downward API volume", func() {
41
42 const podLogTimeout = 3 * time.Minute
43 f := framework.NewDefaultFramework("downward-api")
44 f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
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 := downwardAPIVolumePodForModeTest(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 := downwardAPIVolumePodForModeTest(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 := downwardAPIVolumePodForModeTest(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 := downwardAPIVolumePodForUpdateTest(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 := downwardAPIVolumePodForUpdateTest(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 downwardAPIVolumePodForModeTest(name, filePath string, itemMode, defaultMode *int32) *v1.Pod {
272 pod := downwardAPIVolumeBasePod(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.DownwardAPI.Items[0].Mode = itemMode
289 }
290 if defaultMode != nil {
291 pod.Spec.Volumes[0].VolumeSource.DownwardAPI.DefaultMode = defaultMode
292 }
293
294 return pod
295 }
296
297 func downwardAPIVolumePodForSimpleTest(name string, filePath string) *v1.Pod {
298 pod := downwardAPIVolumeBasePod(name, nil, nil)
299
300 pod.Spec.Containers = []v1.Container{
301 {
302 Name: "client-container",
303 Image: imageutils.GetE2EImage(imageutils.Agnhost),
304 Args: []string{"mounttest", "--file_content=" + filePath},
305 VolumeMounts: []v1.VolumeMount{
306 {
307 Name: "podinfo",
308 MountPath: "/etc/podinfo",
309 ReadOnly: false,
310 },
311 },
312 },
313 }
314
315 return pod
316 }
317
318 func downwardAPIVolumeForContainerResources(name string, filePath string) *v1.Pod {
319 pod := downwardAPIVolumeBasePod(name, nil, nil)
320 pod.Spec.Containers = downwardAPIVolumeBaseContainers("client-container", filePath)
321 return pod
322 }
323
324 func downwardAPIVolumeForDefaultContainerResources(name string, filePath string) *v1.Pod {
325 pod := downwardAPIVolumeBasePod(name, nil, nil)
326 pod.Spec.Containers = downwardAPIVolumeDefaultBaseContainer("client-container", filePath)
327 return pod
328 }
329
330 func downwardAPIVolumeBaseContainers(name, filePath string) []v1.Container {
331 return []v1.Container{
332 {
333 Name: name,
334 Image: imageutils.GetE2EImage(imageutils.Agnhost),
335 Args: []string{"mounttest", "--file_content=" + filePath},
336 Resources: v1.ResourceRequirements{
337 Requests: v1.ResourceList{
338 v1.ResourceCPU: resource.MustParse("250m"),
339 v1.ResourceMemory: resource.MustParse("32Mi"),
340 },
341 Limits: v1.ResourceList{
342 v1.ResourceCPU: resource.MustParse("1250m"),
343 v1.ResourceMemory: resource.MustParse("128Mi"),
344 },
345 },
346 VolumeMounts: []v1.VolumeMount{
347 {
348 Name: "podinfo",
349 MountPath: "/etc/podinfo",
350 ReadOnly: false,
351 },
352 },
353 },
354 }
355
356 }
357
358 func downwardAPIVolumeDefaultBaseContainer(name, filePath string) []v1.Container {
359 return []v1.Container{
360 {
361 Name: name,
362 Image: imageutils.GetE2EImage(imageutils.Agnhost),
363 Args: []string{"mounttest", "--file_content=" + filePath},
364 VolumeMounts: []v1.VolumeMount{
365 {
366 Name: "podinfo",
367 MountPath: "/etc/podinfo",
368 },
369 },
370 },
371 }
372
373 }
374
375 func downwardAPIVolumePodForUpdateTest(name string, labels, annotations map[string]string, filePath string) *v1.Pod {
376 pod := downwardAPIVolumeBasePod(name, labels, annotations)
377
378 pod.Spec.Containers = []v1.Container{
379 {
380 Name: "client-container",
381 Image: imageutils.GetE2EImage(imageutils.Agnhost),
382 Args: []string{"mounttest", "--break_on_expected_content=false", "--retry_time=120", "--file_content_in_loop=" + filePath},
383 VolumeMounts: []v1.VolumeMount{
384 {
385 Name: "podinfo",
386 MountPath: "/etc/podinfo",
387 ReadOnly: false,
388 },
389 },
390 },
391 }
392
393 applyLabelsAndAnnotationsToDownwardAPIPod(labels, annotations, pod)
394 return pod
395 }
396
397 func downwardAPIVolumeBasePod(name string, labels, annotations map[string]string) *v1.Pod {
398 pod := &v1.Pod{
399 ObjectMeta: metav1.ObjectMeta{
400 Name: name,
401 Labels: labels,
402 Annotations: annotations,
403 },
404 Spec: v1.PodSpec{
405 Volumes: []v1.Volume{
406 {
407 Name: "podinfo",
408 VolumeSource: v1.VolumeSource{
409 DownwardAPI: &v1.DownwardAPIVolumeSource{
410 Items: []v1.DownwardAPIVolumeFile{
411 {
412 Path: "podname",
413 FieldRef: &v1.ObjectFieldSelector{
414 APIVersion: "v1",
415 FieldPath: "metadata.name",
416 },
417 },
418 {
419 Path: "cpu_limit",
420 ResourceFieldRef: &v1.ResourceFieldSelector{
421 ContainerName: "client-container",
422 Resource: "limits.cpu",
423 },
424 },
425 {
426 Path: "cpu_request",
427 ResourceFieldRef: &v1.ResourceFieldSelector{
428 ContainerName: "client-container",
429 Resource: "requests.cpu",
430 },
431 },
432 {
433 Path: "memory_limit",
434 ResourceFieldRef: &v1.ResourceFieldSelector{
435 ContainerName: "client-container",
436 Resource: "limits.memory",
437 },
438 },
439 {
440 Path: "memory_request",
441 ResourceFieldRef: &v1.ResourceFieldSelector{
442 ContainerName: "client-container",
443 Resource: "requests.memory",
444 },
445 },
446 },
447 },
448 },
449 },
450 },
451 RestartPolicy: v1.RestartPolicyNever,
452 },
453 }
454
455 return pod
456 }
457
458 func applyLabelsAndAnnotationsToDownwardAPIPod(labels, annotations map[string]string, pod *v1.Pod) {
459 if len(labels) > 0 {
460 pod.Spec.Volumes[0].DownwardAPI.Items = append(pod.Spec.Volumes[0].DownwardAPI.Items, v1.DownwardAPIVolumeFile{
461 Path: "labels",
462 FieldRef: &v1.ObjectFieldSelector{
463 APIVersion: "v1",
464 FieldPath: "metadata.labels",
465 },
466 })
467 }
468
469 if len(annotations) > 0 {
470 pod.Spec.Volumes[0].DownwardAPI.Items = append(pod.Spec.Volumes[0].DownwardAPI.Items, v1.DownwardAPIVolumeFile{
471 Path: "annotations",
472 FieldRef: &v1.ObjectFieldSelector{
473 APIVersion: "v1",
474 FieldPath: "metadata.annotations",
475 },
476 })
477 }
478 }
479
480
481
View as plain text