k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/common/storage/downwardapi_volume.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 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 // How long to wait for a log pod to be displayed 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 /* 51 Release: v1.9 52 Testname: DownwardAPI volume, pod name 53 Description: A Pod is configured with DownwardAPIVolumeSource and DownwardAPIVolumeFiles contains a item for the Pod name. The container runtime MUST be able to access Pod name from the specified path on the mounted volume. 54 */ 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 /* 65 Release: v1.9 66 Testname: DownwardAPI volume, volume mode 0400 67 Description: A Pod is configured with DownwardAPIVolumeSource with the volumesource mode set to -r-------- and DownwardAPIVolumeFiles contains a item for the Pod name. The container runtime MUST be able to access Pod name from the specified path on the mounted volume. 68 This test is marked LinuxOnly since Windows does not support setting specific file permissions. 69 */ 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 /* 81 Release: v1.9 82 Testname: DownwardAPI volume, file mode 0400 83 Description: A Pod is configured with DownwardAPIVolumeSource and DownwardAPIVolumeFiles contains a item for the Pod name with the file mode set to -r--------. The container runtime MUST be able to access Pod name from the specified path on the mounted volume. 84 This test is marked LinuxOnly since Windows does not support setting specific file permissions. 85 */ 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 // Windows does not support RunAsUser / FSGroup SecurityContext options. 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 // Windows does not support RunAsUser / FSGroup SecurityContext options, and it does not support setting file permissions. 113 e2eskipper.SkipIfNodeOSDistroIs("windows") 114 podName := "metadata-volume-" + string(uuid.NewUUID()) 115 gid := int64(1234) 116 mode := int32(0440) /* setting fsGroup sets mode to at least 440 */ 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 /* 128 Release: v1.9 129 Testname: DownwardAPI volume, update label 130 Description: A Pod is configured with DownwardAPIVolumeSource and DownwardAPIVolumeFiles contains list of items for each of the Pod labels. The container runtime MUST be able to access Pod labels from the specified path on the mounted volume. Update the labels by adding a new label to the running Pod. The new label MUST be available from the mounted volume. 131 */ 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 //modify labels 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 /* 160 Release: v1.9 161 Testname: DownwardAPI volume, update annotations 162 Description: A Pod is configured with DownwardAPIVolumeSource and DownwardAPIVolumeFiles contains list of items for each of the Pod annotations. The container runtime MUST be able to access Pod annotations from the specified path on the mounted volume. Update the annotations by adding a new annotation to the running Pod. The new annotation MUST be available from the mounted volume. 163 */ 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 //modify annotations 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 /* 191 Release: v1.9 192 Testname: DownwardAPI volume, CPU limits 193 Description: A Pod is configured with DownwardAPIVolumeSource and DownwardAPIVolumeFiles contains a item for the CPU limits. The container runtime MUST be able to access CPU limits from the specified path on the mounted volume. 194 */ 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 /* 205 Release: v1.9 206 Testname: DownwardAPI volume, memory limits 207 Description: A Pod is configured with DownwardAPIVolumeSource and DownwardAPIVolumeFiles contains a item for the memory limits. The container runtime MUST be able to access memory limits from the specified path on the mounted volume. 208 */ 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 /* 219 Release: v1.9 220 Testname: DownwardAPI volume, CPU request 221 Description: A Pod is configured with DownwardAPIVolumeSource and DownwardAPIVolumeFiles contains a item for the CPU request. The container runtime MUST be able to access CPU request from the specified path on the mounted volume. 222 */ 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 /* 233 Release: v1.9 234 Testname: DownwardAPI volume, memory request 235 Description: A Pod is configured with DownwardAPIVolumeSource and DownwardAPIVolumeFiles contains a item for the memory request. The container runtime MUST be able to access memory request from the specified path on the mounted volume. 236 */ 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 /* 247 Release: v1.9 248 Testname: DownwardAPI volume, CPU limit, default node allocatable 249 Description: A Pod is configured with DownwardAPIVolumeSource and DownwardAPIVolumeFiles contains a item for the CPU limits. CPU limits is not specified for the container. The container runtime MUST be able to access CPU limits from the specified path on the mounted volume and the value MUST be default node allocatable. 250 */ 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 /* 259 Release: v1.9 260 Testname: DownwardAPI volume, memory limit, default node allocatable 261 Description: A Pod is configured with DownwardAPIVolumeSource and DownwardAPIVolumeFiles contains a item for the memory limits. memory limits is not specified for the container. The container runtime MUST be able to access memory limits from the specified path on the mounted volume and the value MUST be default node allocatable. 262 */ 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 // TODO: add test-webserver example as pointed out in https://github.com/kubernetes/kubernetes/pull/5093#discussion-diff-37606771