...

Source file src/k8s.io/kubernetes/test/e2e/common/storage/downwardapi_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  	"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
   481  

View as plain text