...

Source file src/k8s.io/kubernetes/test/e2e/common/storage/configmap_volume.go

Documentation: k8s.io/kubernetes/test/e2e/common/storage

     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  }
   750  

View as plain text