k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/common/storage/projected_downwardapi.go (about) 1 /* 2 Copyright 2014 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 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 // How long to wait for a log pod to be displayed 44 const podLogTimeout = 2 * time.Minute 45 var podClient *e2epod.PodClient 46 ginkgo.BeforeEach(func() { 47 podClient = e2epod.NewPodClient(f) 48 }) 49 50 /* 51 Release: v1.9 52 Testname: Projected Volume, DownwardAPI, pod name 53 Description: A Pod is created with a projected volume source for downwardAPI with pod name, cpu and memory limits and cpu and memory requests. Pod MUST be able to read the pod name from the mounted DownwardAPIVolumeFiles. 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: Projected Volume, DownwardAPI, volume mode 0400 67 Description: A Pod is created with a projected volume source for downwardAPI with pod name, cpu and memory limits and cpu and memory requests. The default mode for the volume mount is set to 0400. Pod MUST be able to read the pod name from the mounted DownwardAPIVolumeFiles and the volume mode must be -r--------. 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 := 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 /* 81 Release: v1.9 82 Testname: Projected Volume, DownwardAPI, volume mode 0400 83 Description: A Pod is created with a projected volume source for downwardAPI with pod name, cpu and memory limits and cpu and memory requests. The default mode for the volume mount is set to 0400. Pod MUST be able to read the pod name from the mounted DownwardAPIVolumeFiles and the volume mode must be -r--------. 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 := 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 // 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 := 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 /* 128 Release: v1.9 129 Testname: Projected Volume, DownwardAPI, update labels 130 Description: A Pod is created with a projected volume source for downwardAPI with pod name, cpu and memory limits and cpu and memory requests and label items. Pod MUST be able to read the labels from the mounted DownwardAPIVolumeFiles. Labels are then updated. Pod MUST be able to read the updated values for the Labels. 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 := 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 //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: Projected Volume, DownwardAPI, update annotation 162 Description: A Pod is created with a projected volume source for downwardAPI with pod name, cpu and memory limits and cpu and memory requests and annotation items. Pod MUST be able to read the annotations from the mounted DownwardAPIVolumeFiles. Annotations are then updated. Pod MUST be able to read the updated values for the Annotations. 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 := 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 //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: Projected Volume, DownwardAPI, CPU limits 193 Description: A Pod is created with a projected volume source for downwardAPI with pod name, cpu and memory limits and cpu and memory requests. Pod MUST be able to read the cpu limits from the mounted DownwardAPIVolumeFiles. 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: Projected Volume, DownwardAPI, memory limits 207 Description: A Pod is created with a projected volume source for downwardAPI with pod name, cpu and memory limits and cpu and memory requests. Pod MUST be able to read the memory limits from the mounted DownwardAPIVolumeFiles. 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: Projected Volume, DownwardAPI, CPU request 221 Description: A Pod is created with a projected volume source for downwardAPI with pod name, cpu and memory limits and cpu and memory requests. Pod MUST be able to read the cpu request from the mounted DownwardAPIVolumeFiles. 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: Projected Volume, DownwardAPI, memory request 235 Description: A Pod is created with a projected volume source for downwardAPI with pod name, cpu and memory limits and cpu and memory requests. Pod MUST be able to read the memory request from the mounted DownwardAPIVolumeFiles. 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: Projected Volume, DownwardAPI, CPU limit, node allocatable 249 Description: A Pod is created with a projected volume source for downwardAPI with pod name, cpu and memory limits and cpu and memory requests. The CPU and memory resources for requests and limits are NOT specified for the container. Pod MUST be able to read the default cpu limits from the mounted DownwardAPIVolumeFiles. 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: Projected Volume, DownwardAPI, memory limit, node allocatable 261 Description: A Pod is created with a projected volume source for downwardAPI with pod name, cpu and memory limits and cpu and memory requests. The CPU and memory resources for requests and limits are NOT specified for the container. Pod MUST be able to read the default memory limits from the mounted DownwardAPIVolumeFiles. 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 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 }