k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/common/storage/configmap_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 "path" 23 24 "github.com/onsi/ginkgo/v2" 25 "github.com/onsi/gomega" 26 v1 "k8s.io/api/core/v1" 27 apierrors "k8s.io/apimachinery/pkg/api/errors" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/util/uuid" 30 "k8s.io/kubernetes/test/e2e/framework" 31 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 32 e2epodoutput "k8s.io/kubernetes/test/e2e/framework/pod/output" 33 e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" 34 "k8s.io/kubernetes/test/e2e/nodefeature" 35 imageutils "k8s.io/kubernetes/test/utils/image" 36 admissionapi "k8s.io/pod-security-admission/api" 37 ) 38 39 var _ = SIGDescribe("ConfigMap", func() { 40 f := framework.NewDefaultFramework("configmap") 41 f.NamespacePodSecurityLevel = admissionapi.LevelBaseline 42 43 /* 44 Release: v1.9 45 Testname: ConfigMap Volume, without mapping 46 Description: Create a ConfigMap, create a Pod that mounts a volume and populates the volume with data stored in the ConfigMap. The ConfigMap that is created MUST be accessible to read from the newly created Pod using the volume mount. The data content of the file MUST be readable and verified and file modes MUST default to 0x644. 47 */ 48 framework.ConformanceIt("should be consumable from pods in volume", f.WithNodeConformance(), func(ctx context.Context) { 49 doConfigMapE2EWithoutMappings(ctx, f, false, 0, nil) 50 }) 51 52 /* 53 Release: v1.9 54 Testname: ConfigMap Volume, without mapping, volume mode set 55 Description: Create a ConfigMap, create a Pod that mounts a volume and populates the volume with data stored in the ConfigMap. File mode is changed to a custom value of '0x400'. The ConfigMap that is created MUST be accessible to read from the newly created Pod using the volume mount. The data content of the file MUST be readable and verified and file modes MUST be set to the custom value of '0x400' 56 This test is marked LinuxOnly since Windows does not support setting specific file permissions. 57 */ 58 framework.ConformanceIt("should be consumable from pods in volume with defaultMode set [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) { 59 defaultMode := int32(0400) 60 doConfigMapE2EWithoutMappings(ctx, f, false, 0, &defaultMode) 61 }) 62 63 f.It("should be consumable from pods in volume as non-root with defaultMode and fsGroup set [LinuxOnly]", nodefeature.FSGroup, func(ctx context.Context) { 64 // Windows does not support RunAsUser / FSGroup SecurityContext options, and it does not support setting file permissions. 65 e2eskipper.SkipIfNodeOSDistroIs("windows") 66 defaultMode := int32(0440) /* setting fsGroup sets mode to at least 440 */ 67 doConfigMapE2EWithoutMappings(ctx, f, true, 1001, &defaultMode) 68 }) 69 70 /* 71 Release: v1.9 72 Testname: ConfigMap Volume, without mapping, non-root user 73 Description: Create a ConfigMap, create a Pod that mounts a volume and populates the volume with data stored in the ConfigMap. Pod is run as a non-root user with uid=1000. The ConfigMap that is created MUST be accessible to read from the newly created Pod using the volume mount. The file on the volume MUST have file mode set to default value of 0x644. 74 */ 75 framework.ConformanceIt("should be consumable from pods in volume as non-root", f.WithNodeConformance(), func(ctx context.Context) { 76 doConfigMapE2EWithoutMappings(ctx, f, true, 0, nil) 77 }) 78 79 f.It("should be consumable from pods in volume as non-root with FSGroup [LinuxOnly]", nodefeature.FSGroup, func(ctx context.Context) { 80 // Windows does not support RunAsUser / FSGroup SecurityContext options. 81 e2eskipper.SkipIfNodeOSDistroIs("windows") 82 doConfigMapE2EWithoutMappings(ctx, f, true, 1001, nil) 83 }) 84 85 /* 86 Release: v1.9 87 Testname: ConfigMap Volume, with mapping 88 Description: Create a ConfigMap, create a Pod that mounts a volume and populates the volume with data stored in the ConfigMap. Files are mapped to a path in the volume. The ConfigMap that is created MUST be accessible to read from the newly created Pod using the volume mount. The data content of the file MUST be readable and verified and file modes MUST default to 0x644. 89 */ 90 framework.ConformanceIt("should be consumable from pods in volume with mappings", f.WithNodeConformance(), func(ctx context.Context) { 91 doConfigMapE2EWithMappings(ctx, f, false, 0, nil) 92 }) 93 94 /* 95 Release: v1.9 96 Testname: ConfigMap Volume, with mapping, volume mode set 97 Description: Create a ConfigMap, create a Pod that mounts a volume and populates the volume with data stored in the ConfigMap. Files are mapped to a path in the volume. File mode is changed to a custom value of '0x400'. The ConfigMap that is created MUST be accessible to read from the newly created Pod using the volume mount. The data content of the file MUST be readable and verified and file modes MUST be set to the custom value of '0x400' 98 This test is marked LinuxOnly since Windows does not support setting specific file permissions. 99 */ 100 framework.ConformanceIt("should be consumable from pods in volume with mappings and Item mode set [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) { 101 mode := int32(0400) 102 doConfigMapE2EWithMappings(ctx, f, false, 0, &mode) 103 }) 104 105 /* 106 Release: v1.9 107 Testname: ConfigMap Volume, with mapping, non-root user 108 Description: Create a ConfigMap, create a Pod that mounts a volume and populates the volume with data stored in the ConfigMap. Files are mapped to a path in the volume. Pod is run as a non-root user with uid=1000. The ConfigMap that is created MUST be accessible to read from the newly created Pod using the volume mount. The file on the volume MUST have file mode set to default value of 0x644. 109 */ 110 framework.ConformanceIt("should be consumable from pods in volume with mappings as non-root", f.WithNodeConformance(), func(ctx context.Context) { 111 doConfigMapE2EWithMappings(ctx, f, true, 0, nil) 112 }) 113 114 f.It("should be consumable from pods in volume with mappings as non-root with FSGroup [LinuxOnly]", nodefeature.FSGroup, func(ctx context.Context) { 115 // Windows does not support RunAsUser / FSGroup SecurityContext options. 116 e2eskipper.SkipIfNodeOSDistroIs("windows") 117 doConfigMapE2EWithMappings(ctx, f, true, 1001, nil) 118 }) 119 120 /* 121 Release: v1.9 122 Testname: ConfigMap Volume, update 123 Description: The ConfigMap that is created MUST be accessible to read from the newly created Pod using the volume mount that is mapped to custom path in the Pod. When the ConfigMap is updated the change to the config map MUST be verified by reading the content from the mounted file in the Pod. 124 */ 125 framework.ConformanceIt("updates should be reflected in volume", f.WithNodeConformance(), func(ctx context.Context) { 126 podLogTimeout := e2epod.GetPodSecretUpdateTimeout(ctx, f.ClientSet) 127 containerTimeoutArg := fmt.Sprintf("--retry_time=%v", int(podLogTimeout.Seconds())) 128 129 name := "configmap-test-upd-" + string(uuid.NewUUID()) 130 volumeName := "configmap-volume" 131 volumeMountPath := "/etc/configmap-volume" 132 133 configMap := &v1.ConfigMap{ 134 ObjectMeta: metav1.ObjectMeta{ 135 Namespace: f.Namespace.Name, 136 Name: name, 137 }, 138 Data: map[string]string{ 139 "data-1": "value-1", 140 }, 141 } 142 143 ginkgo.By(fmt.Sprintf("Creating configMap with name %s", configMap.Name)) 144 var err error 145 if configMap, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, configMap, metav1.CreateOptions{}); err != nil { 146 framework.Failf("unable to create test configMap %s: %v", configMap.Name, err) 147 } 148 149 pod := createConfigMapVolumeMounttestPod(f.Namespace.Name, volumeName, name, volumeMountPath, 150 "--break_on_expected_content=false", containerTimeoutArg, "--file_content_in_loop=/etc/configmap-volume/data-1") 151 152 ginkgo.By("Creating the pod") 153 e2epod.NewPodClient(f).CreateSync(ctx, pod) 154 155 pollLogs := func() (string, error) { 156 return e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, pod.Name, pod.Spec.Containers[0].Name) 157 } 158 159 gomega.Eventually(ctx, pollLogs, podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("value-1")) 160 161 ginkgo.By(fmt.Sprintf("Updating configmap %v", configMap.Name)) 162 configMap.ResourceVersion = "" // to force update 163 configMap.Data["data-1"] = "value-2" 164 _, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Update(ctx, configMap, metav1.UpdateOptions{}) 165 framework.ExpectNoError(err, "Failed to update configmap %q in namespace %q", configMap.Name, f.Namespace.Name) 166 167 ginkgo.By("waiting to observe update in volume") 168 gomega.Eventually(ctx, pollLogs, podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("value-2")) 169 }) 170 171 /* 172 Release: v1.12 173 Testname: ConfigMap Volume, text data, binary data 174 Description: The ConfigMap that is created with text data and binary data MUST be accessible to read from the newly created Pod using the volume mount that is mapped to custom path in the Pod. ConfigMap's text data and binary data MUST be verified by reading the content from the mounted files in the Pod. 175 */ 176 framework.ConformanceIt("binary data should be reflected in volume", f.WithNodeConformance(), func(ctx context.Context) { 177 podLogTimeout := e2epod.GetPodSecretUpdateTimeout(ctx, f.ClientSet) 178 containerTimeoutArg := fmt.Sprintf("--retry_time=%v", int(podLogTimeout.Seconds())) 179 180 name := "configmap-test-upd-" + string(uuid.NewUUID()) 181 volumeName := "configmap-volume" 182 volumeMountPath := "/etc/configmap-volume" 183 containerName := "configmap-volume-binary-test" 184 185 configMap := &v1.ConfigMap{ 186 ObjectMeta: metav1.ObjectMeta{ 187 Namespace: f.Namespace.Name, 188 Name: name, 189 }, 190 Data: map[string]string{ 191 "data-1": "value-1", 192 }, 193 BinaryData: map[string][]byte{ 194 "dump.bin": {0xde, 0xca, 0xfe, 0xba, 0xd0, 0xfe, 0xff}, 195 }, 196 } 197 198 ginkgo.By(fmt.Sprintf("Creating configMap with name %s", configMap.Name)) 199 var err error 200 if configMap, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, configMap, metav1.CreateOptions{}); err != nil { 201 framework.Failf("unable to create test configMap %s: %v", configMap.Name, err) 202 } 203 204 pod := createConfigMapVolumeMounttestPod(f.Namespace.Name, volumeName, name, volumeMountPath, 205 "--break_on_expected_content=false", containerTimeoutArg, "--file_content_in_loop=/etc/configmap-volume/data-1") 206 pod.Spec.Containers = append(pod.Spec.Containers, v1.Container{ 207 Name: containerName, 208 Image: imageutils.GetE2EImage(imageutils.BusyBox), 209 Command: []string{"hexdump", "-C", "/etc/configmap-volume/dump.bin"}, 210 VolumeMounts: []v1.VolumeMount{ 211 { 212 Name: volumeName, 213 MountPath: volumeMountPath, 214 ReadOnly: true, 215 }, 216 }, 217 }) 218 219 ginkgo.By("Creating the pod") 220 e2epod.NewPodClient(f).Create(ctx, pod) 221 framework.ExpectNoError(e2epod.WaitForPodNameRunningInNamespace(ctx, f.ClientSet, pod.Name, f.Namespace.Name)) 222 223 pollLogs1 := func() (string, error) { 224 return e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, pod.Name, pod.Spec.Containers[0].Name) 225 } 226 pollLogs2 := func() (string, error) { 227 return e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, pod.Name, pod.Spec.Containers[1].Name) 228 } 229 230 ginkgo.By("Waiting for pod with text data") 231 gomega.Eventually(ctx, pollLogs1, podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("value-1")) 232 ginkgo.By("Waiting for pod with binary data") 233 gomega.Eventually(ctx, pollLogs2, podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("de ca fe ba d0 fe ff")) 234 }) 235 236 /* 237 Release: v1.9 238 Testname: ConfigMap Volume, create, update and delete 239 Description: The ConfigMap that is created MUST be accessible to read from the newly created Pod using the volume mount that is mapped to custom path in the Pod. When the config map is updated the change to the config map MUST be verified by reading the content from the mounted file in the Pod. Also when the item(file) is deleted from the map that MUST result in a error reading that item(file). 240 */ 241 framework.ConformanceIt("optional updates should be reflected in volume", f.WithNodeConformance(), func(ctx context.Context) { 242 podLogTimeout := e2epod.GetPodSecretUpdateTimeout(ctx, f.ClientSet) 243 containerTimeoutArg := fmt.Sprintf("--retry_time=%v", int(podLogTimeout.Seconds())) 244 trueVal := true 245 volumeMountPath := "/etc/configmap-volumes" 246 247 deleteName := "cm-test-opt-del-" + string(uuid.NewUUID()) 248 deleteContainerName := "delcm-volume-test" 249 deleteVolumeName := "deletecm-volume" 250 deleteConfigMap := &v1.ConfigMap{ 251 ObjectMeta: metav1.ObjectMeta{ 252 Namespace: f.Namespace.Name, 253 Name: deleteName, 254 }, 255 Data: map[string]string{ 256 "data-1": "value-1", 257 }, 258 } 259 260 updateName := "cm-test-opt-upd-" + string(uuid.NewUUID()) 261 updateContainerName := "updcm-volume-test" 262 updateVolumeName := "updatecm-volume" 263 updateConfigMap := &v1.ConfigMap{ 264 ObjectMeta: metav1.ObjectMeta{ 265 Namespace: f.Namespace.Name, 266 Name: updateName, 267 }, 268 Data: map[string]string{ 269 "data-1": "value-1", 270 }, 271 } 272 273 createName := "cm-test-opt-create-" + string(uuid.NewUUID()) 274 createContainerName := "createcm-volume-test" 275 createVolumeName := "createcm-volume" 276 createConfigMap := &v1.ConfigMap{ 277 ObjectMeta: metav1.ObjectMeta{ 278 Namespace: f.Namespace.Name, 279 Name: createName, 280 }, 281 Data: map[string]string{ 282 "data-1": "value-1", 283 }, 284 } 285 286 ginkgo.By(fmt.Sprintf("Creating configMap with name %s", deleteConfigMap.Name)) 287 var err error 288 if deleteConfigMap, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, deleteConfigMap, metav1.CreateOptions{}); err != nil { 289 framework.Failf("unable to create test configMap %s: %v", deleteConfigMap.Name, err) 290 } 291 292 ginkgo.By(fmt.Sprintf("Creating configMap with name %s", updateConfigMap.Name)) 293 if updateConfigMap, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, updateConfigMap, metav1.CreateOptions{}); err != nil { 294 framework.Failf("unable to create test configMap %s: %v", updateConfigMap.Name, err) 295 } 296 297 pod := &v1.Pod{ 298 ObjectMeta: metav1.ObjectMeta{ 299 Name: "pod-configmaps-" + string(uuid.NewUUID()), 300 }, 301 Spec: v1.PodSpec{ 302 Volumes: []v1.Volume{ 303 { 304 Name: deleteVolumeName, 305 VolumeSource: v1.VolumeSource{ 306 ConfigMap: &v1.ConfigMapVolumeSource{ 307 LocalObjectReference: v1.LocalObjectReference{ 308 Name: deleteName, 309 }, 310 Optional: &trueVal, 311 }, 312 }, 313 }, 314 { 315 Name: updateVolumeName, 316 VolumeSource: v1.VolumeSource{ 317 ConfigMap: &v1.ConfigMapVolumeSource{ 318 LocalObjectReference: v1.LocalObjectReference{ 319 Name: updateName, 320 }, 321 Optional: &trueVal, 322 }, 323 }, 324 }, 325 { 326 Name: createVolumeName, 327 VolumeSource: v1.VolumeSource{ 328 ConfigMap: &v1.ConfigMapVolumeSource{ 329 LocalObjectReference: v1.LocalObjectReference{ 330 Name: createName, 331 }, 332 Optional: &trueVal, 333 }, 334 }, 335 }, 336 }, 337 Containers: []v1.Container{ 338 { 339 Name: deleteContainerName, 340 Image: imageutils.GetE2EImage(imageutils.Agnhost), 341 Args: []string{"mounttest", "--break_on_expected_content=false", containerTimeoutArg, "--file_content_in_loop=/etc/configmap-volumes/delete/data-1"}, 342 VolumeMounts: []v1.VolumeMount{ 343 { 344 Name: deleteVolumeName, 345 MountPath: path.Join(volumeMountPath, "delete"), 346 ReadOnly: true, 347 }, 348 }, 349 }, 350 { 351 Name: updateContainerName, 352 Image: imageutils.GetE2EImage(imageutils.Agnhost), 353 Args: []string{"mounttest", "--break_on_expected_content=false", containerTimeoutArg, "--file_content_in_loop=/etc/configmap-volumes/update/data-3"}, 354 VolumeMounts: []v1.VolumeMount{ 355 { 356 Name: updateVolumeName, 357 MountPath: path.Join(volumeMountPath, "update"), 358 ReadOnly: true, 359 }, 360 }, 361 }, 362 { 363 Name: createContainerName, 364 Image: imageutils.GetE2EImage(imageutils.Agnhost), 365 Args: []string{"mounttest", "--break_on_expected_content=false", containerTimeoutArg, "--file_content_in_loop=/etc/configmap-volumes/create/data-1"}, 366 VolumeMounts: []v1.VolumeMount{ 367 { 368 Name: createVolumeName, 369 MountPath: path.Join(volumeMountPath, "create"), 370 ReadOnly: true, 371 }, 372 }, 373 }, 374 }, 375 RestartPolicy: v1.RestartPolicyNever, 376 }, 377 } 378 ginkgo.By("Creating the pod") 379 e2epod.NewPodClient(f).CreateSync(ctx, pod) 380 381 pollCreateLogs := func() (string, error) { 382 return e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, pod.Name, createContainerName) 383 } 384 gomega.Eventually(ctx, pollCreateLogs, podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("Error reading file /etc/configmap-volumes/create/data-1")) 385 386 pollUpdateLogs := func() (string, error) { 387 return e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, pod.Name, updateContainerName) 388 } 389 gomega.Eventually(ctx, pollUpdateLogs, podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("Error reading file /etc/configmap-volumes/update/data-3")) 390 391 pollDeleteLogs := func() (string, error) { 392 return e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, pod.Name, deleteContainerName) 393 } 394 gomega.Eventually(ctx, pollDeleteLogs, podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("value-1")) 395 396 ginkgo.By(fmt.Sprintf("Deleting configmap %v", deleteConfigMap.Name)) 397 err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Delete(ctx, deleteConfigMap.Name, metav1.DeleteOptions{}) 398 framework.ExpectNoError(err, "Failed to delete configmap %q in namespace %q", deleteConfigMap.Name, f.Namespace.Name) 399 400 ginkgo.By(fmt.Sprintf("Updating configmap %v", updateConfigMap.Name)) 401 updateConfigMap.ResourceVersion = "" // to force update 402 delete(updateConfigMap.Data, "data-1") 403 updateConfigMap.Data["data-3"] = "value-3" 404 _, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Update(ctx, updateConfigMap, metav1.UpdateOptions{}) 405 framework.ExpectNoError(err, "Failed to update configmap %q in namespace %q", updateConfigMap.Name, f.Namespace.Name) 406 407 ginkgo.By(fmt.Sprintf("Creating configMap with name %s", createConfigMap.Name)) 408 if createConfigMap, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, createConfigMap, metav1.CreateOptions{}); err != nil { 409 framework.Failf("unable to create test configMap %s: %v", createConfigMap.Name, err) 410 } 411 412 ginkgo.By("waiting to observe update in volume") 413 414 gomega.Eventually(ctx, pollCreateLogs, podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("value-1")) 415 gomega.Eventually(ctx, pollUpdateLogs, podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("value-3")) 416 gomega.Eventually(ctx, pollDeleteLogs, podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("Error reading file /etc/configmap-volumes/delete/data-1")) 417 }) 418 419 /* 420 Release: v1.9 421 Testname: ConfigMap Volume, multiple volume maps 422 Description: The ConfigMap that is created MUST be accessible to read from the newly created Pod using the volume mount that is mapped to multiple paths in the Pod. The content MUST be accessible from all the mapped volume mounts. 423 */ 424 framework.ConformanceIt("should be consumable in multiple volumes in the same pod", f.WithNodeConformance(), func(ctx context.Context) { 425 var ( 426 name = "configmap-test-volume-" + string(uuid.NewUUID()) 427 volumeName = "configmap-volume" 428 volumeMountPath = "/etc/configmap-volume" 429 volumeName2 = "configmap-volume-2" 430 volumeMountPath2 = "/etc/configmap-volume-2" 431 configMap = newConfigMap(f, name) 432 ) 433 434 ginkgo.By(fmt.Sprintf("Creating configMap with name %s", configMap.Name)) 435 var err error 436 if configMap, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, configMap, metav1.CreateOptions{}); err != nil { 437 framework.Failf("unable to create test configMap %s: %v", configMap.Name, err) 438 } 439 440 pod := &v1.Pod{ 441 ObjectMeta: metav1.ObjectMeta{ 442 Name: "pod-configmaps-" + string(uuid.NewUUID()), 443 }, 444 Spec: v1.PodSpec{ 445 Volumes: []v1.Volume{ 446 { 447 Name: volumeName, 448 VolumeSource: v1.VolumeSource{ 449 ConfigMap: &v1.ConfigMapVolumeSource{ 450 LocalObjectReference: v1.LocalObjectReference{ 451 Name: name, 452 }, 453 }, 454 }, 455 }, 456 { 457 Name: volumeName2, 458 VolumeSource: v1.VolumeSource{ 459 ConfigMap: &v1.ConfigMapVolumeSource{ 460 LocalObjectReference: v1.LocalObjectReference{ 461 Name: name, 462 }, 463 }, 464 }, 465 }, 466 }, 467 Containers: []v1.Container{ 468 { 469 Name: "configmap-volume-test", 470 Image: imageutils.GetE2EImage(imageutils.Agnhost), 471 Args: []string{"mounttest", "--file_content=/etc/configmap-volume/data-1"}, 472 VolumeMounts: []v1.VolumeMount{ 473 { 474 Name: volumeName, 475 MountPath: volumeMountPath, 476 ReadOnly: true, 477 }, 478 { 479 Name: volumeName2, 480 MountPath: volumeMountPath2, 481 ReadOnly: true, 482 }, 483 }, 484 }, 485 }, 486 RestartPolicy: v1.RestartPolicyNever, 487 }, 488 } 489 490 e2epodoutput.TestContainerOutput(ctx, f, "consume configMaps", pod, 0, []string{ 491 "content of file \"/etc/configmap-volume/data-1\": value-1", 492 }) 493 494 }) 495 496 /* 497 Release: v1.21 498 Testname: ConfigMap Volume, immutability 499 Description: Create a ConfigMap. Update it's data field, the update MUST succeed. 500 Mark the ConfigMap as immutable, the update MUST succeed. Try to update its data, the update MUST fail. 501 Try to mark the ConfigMap back as not immutable, the update MUST fail. 502 Try to update the ConfigMap`s metadata (labels), the update must succeed. 503 Try to delete the ConfigMap, the deletion must succeed. 504 */ 505 framework.ConformanceIt("should be immutable if `immutable` field is set", func(ctx context.Context) { 506 name := "immutable" 507 configMap := newConfigMap(f, name) 508 509 currentConfigMap, err := f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, configMap, metav1.CreateOptions{}) 510 framework.ExpectNoError(err, "Failed to create config map %q in namespace %q", configMap.Name, configMap.Namespace) 511 512 currentConfigMap.Data["data-4"] = "value-4" 513 currentConfigMap, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Update(ctx, currentConfigMap, metav1.UpdateOptions{}) 514 framework.ExpectNoError(err, "Failed to update config map %q in namespace %q", configMap.Name, configMap.Namespace) 515 516 // Mark config map as immutable. 517 trueVal := true 518 currentConfigMap.Immutable = &trueVal 519 currentConfigMap, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Update(ctx, currentConfigMap, metav1.UpdateOptions{}) 520 framework.ExpectNoError(err, "Failed to mark config map %q in namespace %q as immutable", configMap.Name, configMap.Namespace) 521 522 // Ensure data can't be changed now. 523 currentConfigMap.Data["data-5"] = "value-5" 524 _, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Update(ctx, currentConfigMap, metav1.UpdateOptions{}) 525 if !apierrors.IsInvalid(err) { 526 framework.Failf("expected 'invalid' as error, got instead: %v", err) 527 } 528 529 // Ensure config map can't be switched from immutable to mutable. 530 currentConfigMap, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Get(ctx, name, metav1.GetOptions{}) 531 framework.ExpectNoError(err, "Failed to get config map %q in namespace %q", configMap.Name, configMap.Namespace) 532 if !*currentConfigMap.Immutable { 533 framework.Failf("currentConfigMap %s can be switched from immutable to mutable", currentConfigMap.Name) 534 } 535 536 falseVal := false 537 currentConfigMap.Immutable = &falseVal 538 _, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Update(ctx, currentConfigMap, metav1.UpdateOptions{}) 539 if !apierrors.IsInvalid(err) { 540 framework.Failf("expected 'invalid' as error, got instead: %v", err) 541 } 542 543 // Ensure that metadata can be changed. 544 currentConfigMap, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Get(ctx, name, metav1.GetOptions{}) 545 framework.ExpectNoError(err, "Failed to get config map %q in namespace %q", configMap.Name, configMap.Namespace) 546 currentConfigMap.Labels = map[string]string{"label1": "value1"} 547 _, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Update(ctx, currentConfigMap, metav1.UpdateOptions{}) 548 framework.ExpectNoError(err, "Failed to update config map %q in namespace %q", configMap.Name, configMap.Namespace) 549 550 // Ensure that immutable config map can be deleted. 551 err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Delete(ctx, name, metav1.DeleteOptions{}) 552 framework.ExpectNoError(err, "Failed to delete config map %q in namespace %q", configMap.Name, configMap.Namespace) 553 }) 554 555 // The pod is in pending during volume creation until the configMap objects are available 556 // or until mount the configMap volume times out. There is no configMap object defined for the pod, so it should return timeout exception unless it is marked optional. 557 // Slow (~5 mins) 558 f.It("Should fail non-optional pod creation due to configMap object does not exist", f.WithSlow(), func(ctx context.Context) { 559 volumeMountPath := "/etc/configmap-volumes" 560 pod := createNonOptionalConfigMapPod(ctx, f, volumeMountPath) 561 getPod := e2epod.Get(f.ClientSet, pod) 562 gomega.Consistently(ctx, getPod).WithTimeout(f.Timeouts.PodStart).Should(e2epod.BeInPhase(v1.PodPending)) 563 }) 564 565 // ConfigMap object defined for the pod, If a key is specified which is not present in the ConfigMap, 566 // the volume setup will error unless it is marked optional, during the pod creation. 567 // Slow (~5 mins) 568 f.It("Should fail non-optional pod creation due to the key in the configMap object does not exist", f.WithSlow(), func(ctx context.Context) { 569 volumeMountPath := "/etc/configmap-volumes" 570 pod := createNonOptionalConfigMapPodWithConfig(ctx, f, volumeMountPath) 571 getPod := e2epod.Get(f.ClientSet, pod) 572 gomega.Consistently(ctx, getPod).WithTimeout(f.Timeouts.PodStart).Should(e2epod.BeInPhase(v1.PodPending)) 573 }) 574 }) 575 576 func newConfigMap(f *framework.Framework, name string) *v1.ConfigMap { 577 return &v1.ConfigMap{ 578 ObjectMeta: metav1.ObjectMeta{ 579 Namespace: f.Namespace.Name, 580 Name: name, 581 }, 582 Data: map[string]string{ 583 "data-1": "value-1", 584 "data-2": "value-2", 585 "data-3": "value-3", 586 }, 587 } 588 } 589 590 func doConfigMapE2EWithoutMappings(ctx context.Context, f *framework.Framework, asUser bool, fsGroup int64, defaultMode *int32) { 591 groupID := int64(fsGroup) 592 593 var ( 594 name = "configmap-test-volume-" + string(uuid.NewUUID()) 595 volumeName = "configmap-volume" 596 volumeMountPath = "/etc/configmap-volume" 597 configMap = newConfigMap(f, name) 598 ) 599 600 ginkgo.By(fmt.Sprintf("Creating configMap with name %s", configMap.Name)) 601 var err error 602 if configMap, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, configMap, metav1.CreateOptions{}); err != nil { 603 framework.Failf("unable to create test configMap %s: %v", configMap.Name, err) 604 } 605 606 pod := createConfigMapVolumeMounttestPod(f.Namespace.Name, volumeName, name, volumeMountPath, 607 "--file_content=/etc/configmap-volume/data-1", "--file_mode=/etc/configmap-volume/data-1") 608 one := int64(1) 609 pod.Spec.TerminationGracePeriodSeconds = &one 610 611 if asUser { 612 setPodNonRootUser(pod) 613 } 614 615 if groupID != 0 { 616 pod.Spec.SecurityContext.FSGroup = &groupID 617 } 618 619 if defaultMode != nil { 620 pod.Spec.Volumes[0].VolumeSource.ConfigMap.DefaultMode = defaultMode 621 } 622 623 fileModeRegexp := getFileModeRegex("/etc/configmap-volume/data-1", defaultMode) 624 output := []string{ 625 "content of file \"/etc/configmap-volume/data-1\": value-1", 626 fileModeRegexp, 627 } 628 e2epodoutput.TestContainerOutputRegexp(ctx, f, "consume configMaps", pod, 0, output) 629 } 630 631 func doConfigMapE2EWithMappings(ctx context.Context, f *framework.Framework, asUser bool, fsGroup int64, itemMode *int32) { 632 groupID := int64(fsGroup) 633 634 var ( 635 name = "configmap-test-volume-map-" + string(uuid.NewUUID()) 636 volumeName = "configmap-volume" 637 volumeMountPath = "/etc/configmap-volume" 638 configMap = newConfigMap(f, name) 639 ) 640 641 ginkgo.By(fmt.Sprintf("Creating configMap with name %s", configMap.Name)) 642 643 var err error 644 if configMap, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, configMap, metav1.CreateOptions{}); err != nil { 645 framework.Failf("unable to create test configMap %s: %v", configMap.Name, err) 646 } 647 648 pod := createConfigMapVolumeMounttestPod(f.Namespace.Name, volumeName, name, volumeMountPath, 649 "--file_content=/etc/configmap-volume/path/to/data-2", "--file_mode=/etc/configmap-volume/path/to/data-2") 650 one := int64(1) 651 pod.Spec.TerminationGracePeriodSeconds = &one 652 pod.Spec.Volumes[0].VolumeSource.ConfigMap.Items = []v1.KeyToPath{ 653 { 654 Key: "data-2", 655 Path: "path/to/data-2", 656 }, 657 } 658 659 if asUser { 660 setPodNonRootUser(pod) 661 } 662 663 if groupID != 0 { 664 pod.Spec.SecurityContext.FSGroup = &groupID 665 } 666 667 if itemMode != nil { 668 pod.Spec.Volumes[0].VolumeSource.ConfigMap.Items[0].Mode = itemMode 669 } 670 671 // Just check file mode if fsGroup is not set. If fsGroup is set, the 672 // final mode is adjusted and we are not testing that case. 673 output := []string{ 674 "content of file \"/etc/configmap-volume/path/to/data-2\": value-2", 675 } 676 if fsGroup == 0 { 677 fileModeRegexp := getFileModeRegex("/etc/configmap-volume/path/to/data-2", itemMode) 678 output = append(output, fileModeRegexp) 679 } 680 e2epodoutput.TestContainerOutputRegexp(ctx, f, "consume configMaps", pod, 0, output) 681 } 682 683 func createNonOptionalConfigMapPod(ctx context.Context, f *framework.Framework, volumeMountPath string) *v1.Pod { 684 podLogTimeout := e2epod.GetPodSecretUpdateTimeout(ctx, f.ClientSet) 685 containerTimeoutArg := fmt.Sprintf("--retry_time=%v", int(podLogTimeout.Seconds())) 686 falseValue := false 687 688 createName := "cm-test-opt-create-" + string(uuid.NewUUID()) 689 createVolumeName := "createcm-volume" 690 691 // creating a pod without configMap object created, by mentioning the configMap volume source's local reference name 692 pod := createConfigMapVolumeMounttestPod(f.Namespace.Name, createVolumeName, createName, path.Join(volumeMountPath, "create"), 693 "--break_on_expected_content=false", containerTimeoutArg, "--file_content_in_loop=/etc/configmap-volumes/create/data-1") 694 pod.Spec.Volumes[0].VolumeSource.ConfigMap.Optional = &falseValue 695 696 ginkgo.By("Creating the pod") 697 pod = e2epod.NewPodClient(f).Create(ctx, pod) 698 return pod 699 } 700 701 func createNonOptionalConfigMapPodWithConfig(ctx context.Context, f *framework.Framework, volumeMountPath string) *v1.Pod { 702 podLogTimeout := e2epod.GetPodSecretUpdateTimeout(ctx, f.ClientSet) 703 containerTimeoutArg := fmt.Sprintf("--retry_time=%v", int(podLogTimeout.Seconds())) 704 falseValue := false 705 706 createName := "cm-test-opt-create-" + string(uuid.NewUUID()) 707 createVolumeName := "createcm-volume" 708 configMap := newConfigMap(f, createName) 709 710 ginkgo.By(fmt.Sprintf("Creating configMap with name %s", configMap.Name)) 711 var err error 712 if configMap, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, configMap, metav1.CreateOptions{}); err != nil { 713 framework.Failf("unable to create test configMap %s: %v", configMap.Name, err) 714 } 715 // creating a pod with configMap object, but with different key which is not present in configMap object. 716 pod := createConfigMapVolumeMounttestPod(f.Namespace.Name, createVolumeName, createName, path.Join(volumeMountPath, "create"), 717 "--break_on_expected_content=false", containerTimeoutArg, "--file_content_in_loop=/etc/configmap-volumes/create/data-1") 718 pod.Spec.Volumes[0].VolumeSource.ConfigMap.Optional = &falseValue 719 pod.Spec.Volumes[0].VolumeSource.ConfigMap.Items = []v1.KeyToPath{ 720 { 721 Key: "data-4", 722 Path: "path/to/data-4", 723 }, 724 } 725 726 ginkgo.By("Creating the pod") 727 pod = e2epod.NewPodClient(f).Create(ctx, pod) 728 return pod 729 } 730 731 func createConfigMapVolumeMounttestPod(namespace, volumeName, referenceName, mountPath string, mounttestArgs ...string) *v1.Pod { 732 volumes := []v1.Volume{ 733 { 734 Name: volumeName, 735 VolumeSource: v1.VolumeSource{ 736 ConfigMap: &v1.ConfigMapVolumeSource{ 737 LocalObjectReference: v1.LocalObjectReference{ 738 Name: referenceName, 739 }, 740 }, 741 }, 742 }, 743 } 744 podName := "pod-configmaps-" + string(uuid.NewUUID()) 745 mounttestArgs = append([]string{"mounttest"}, mounttestArgs...) 746 pod := e2epod.NewAgnhostPod(namespace, podName, volumes, createMounts(volumeName, mountPath, true), nil, mounttestArgs...) 747 pod.Spec.RestartPolicy = v1.RestartPolicyNever 748 return pod 749 }