...

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

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

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package storage
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	v1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/util/uuid"
    27  	"k8s.io/kubernetes/test/e2e/framework"
    28  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    29  	e2epodoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
    30  	e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
    31  	"k8s.io/kubernetes/test/e2e/nodefeature"
    32  	imageutils "k8s.io/kubernetes/test/utils/image"
    33  	admissionapi "k8s.io/pod-security-admission/api"
    34  
    35  	"github.com/onsi/ginkgo/v2"
    36  	"github.com/onsi/gomega"
    37  )
    38  
    39  var _ = SIGDescribe("Projected downwardAPI", func() {
    40  	f := framework.NewDefaultFramework("projected")
    41  	f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
    42  
    43  	// How long to wait for a log pod to be displayed
    44  	const podLogTimeout = 2 * time.Minute
    45  	var podClient *e2epod.PodClient
    46  	ginkgo.BeforeEach(func() {
    47  		podClient = e2epod.NewPodClient(f)
    48  	})
    49  
    50  	/*
    51  	   Release: v1.9
    52  	   Testname: Projected Volume, DownwardAPI, pod name
    53  	   Description: A Pod is created with a projected volume source for downwardAPI with pod name, cpu and memory limits and cpu and memory requests. Pod MUST be able to read the pod name from the mounted DownwardAPIVolumeFiles.
    54  	*/
    55  	framework.ConformanceIt("should provide podname only", f.WithNodeConformance(), func(ctx context.Context) {
    56  		podName := "downwardapi-volume-" + string(uuid.NewUUID())
    57  		pod := downwardAPIVolumePodForSimpleTest(podName, "/etc/podinfo/podname")
    58  
    59  		e2epodoutput.TestContainerOutput(ctx, f, "downward API volume plugin", pod, 0, []string{
    60  			fmt.Sprintf("%s\n", podName),
    61  		})
    62  	})
    63  
    64  	/*
    65  	   Release: v1.9
    66  	   Testname: Projected Volume, DownwardAPI, volume mode 0400
    67  	   Description: A Pod is created with a projected volume source for downwardAPI with pod name, cpu and memory limits and cpu and memory requests. The default mode for the volume mount is set to 0400. Pod MUST be able to read the pod name from the mounted DownwardAPIVolumeFiles and the volume mode must be -r--------.
    68  	   This test is marked LinuxOnly since Windows does not support setting specific file permissions.
    69  	*/
    70  	framework.ConformanceIt("should set DefaultMode on files [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
    71  		podName := "downwardapi-volume-" + string(uuid.NewUUID())
    72  		defaultMode := int32(0400)
    73  		pod := projectedDownwardAPIVolumePodForModeTest(podName, "/etc/podinfo/podname", nil, &defaultMode)
    74  
    75  		e2epodoutput.TestContainerOutput(ctx, f, "downward API volume plugin", pod, 0, []string{
    76  			"mode of file \"/etc/podinfo/podname\": -r--------",
    77  		})
    78  	})
    79  
    80  	/*
    81  	   Release: v1.9
    82  	   Testname: Projected Volume, DownwardAPI, volume mode 0400
    83  	   Description: A Pod is created with a projected volume source for downwardAPI with pod name, cpu and memory limits and cpu and memory requests. The default mode for the volume mount is set to 0400. Pod MUST be able to read the pod name from the mounted DownwardAPIVolumeFiles and the volume mode must be -r--------.
    84  	   This test is marked LinuxOnly since Windows does not support setting specific file permissions.
    85  	*/
    86  	framework.ConformanceIt("should set mode on item file [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
    87  		podName := "downwardapi-volume-" + string(uuid.NewUUID())
    88  		mode := int32(0400)
    89  		pod := projectedDownwardAPIVolumePodForModeTest(podName, "/etc/podinfo/podname", &mode, nil)
    90  
    91  		e2epodoutput.TestContainerOutput(ctx, f, "downward API volume plugin", pod, 0, []string{
    92  			"mode of file \"/etc/podinfo/podname\": -r--------",
    93  		})
    94  	})
    95  
    96  	f.It("should provide podname as non-root with fsgroup [LinuxOnly]", nodefeature.FSGroup, func(ctx context.Context) {
    97  		// Windows does not support RunAsUser / FSGroup SecurityContext options.
    98  		e2eskipper.SkipIfNodeOSDistroIs("windows")
    99  		podName := "metadata-volume-" + string(uuid.NewUUID())
   100  		gid := int64(1234)
   101  		pod := downwardAPIVolumePodForSimpleTest(podName, "/etc/podinfo/podname")
   102  		pod.Spec.SecurityContext = &v1.PodSecurityContext{
   103  			FSGroup: &gid,
   104  		}
   105  		setPodNonRootUser(pod)
   106  		e2epodoutput.TestContainerOutput(ctx, f, "downward API volume plugin", pod, 0, []string{
   107  			fmt.Sprintf("%s\n", podName),
   108  		})
   109  	})
   110  
   111  	f.It("should provide podname as non-root with fsgroup and defaultMode [LinuxOnly]", nodefeature.FSGroup, func(ctx context.Context) {
   112  		// Windows does not support RunAsUser / FSGroup SecurityContext options, and it does not support setting file permissions.
   113  		e2eskipper.SkipIfNodeOSDistroIs("windows")
   114  		podName := "metadata-volume-" + string(uuid.NewUUID())
   115  		gid := int64(1234)
   116  		mode := int32(0440) /* setting fsGroup sets mode to at least 440 */
   117  		pod := projectedDownwardAPIVolumePodForModeTest(podName, "/etc/podinfo/podname", &mode, nil)
   118  		pod.Spec.SecurityContext = &v1.PodSecurityContext{
   119  			FSGroup: &gid,
   120  		}
   121  		setPodNonRootUser(pod)
   122  		e2epodoutput.TestContainerOutput(ctx, f, "downward API volume plugin", pod, 0, []string{
   123  			"mode of file \"/etc/podinfo/podname\": -r--r-----",
   124  		})
   125  	})
   126  
   127  	/*
   128  	   Release: v1.9
   129  	   Testname: Projected Volume, DownwardAPI, update labels
   130  	   Description: A Pod is created with a projected volume source for downwardAPI with pod name, cpu and memory limits and cpu and memory requests and label items. Pod MUST be able to read the labels from the mounted DownwardAPIVolumeFiles. Labels are then updated. Pod MUST be able to read the updated values for the Labels.
   131  	*/
   132  	framework.ConformanceIt("should update labels on modification", f.WithNodeConformance(), func(ctx context.Context) {
   133  		labels := map[string]string{}
   134  		labels["key1"] = "value1"
   135  		labels["key2"] = "value2"
   136  
   137  		podName := "labelsupdate" + string(uuid.NewUUID())
   138  		pod := projectedDownwardAPIVolumePodForUpdateTest(podName, labels, map[string]string{}, "/etc/podinfo/labels")
   139  		containerName := "client-container"
   140  		ginkgo.By("Creating the pod")
   141  		podClient.CreateSync(ctx, pod)
   142  
   143  		gomega.Eventually(ctx, func() (string, error) {
   144  			return e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, podName, containerName)
   145  		},
   146  			podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("key1=\"value1\"\n"))
   147  
   148  		//modify labels
   149  		podClient.Update(ctx, podName, func(pod *v1.Pod) {
   150  			pod.Labels["key3"] = "value3"
   151  		})
   152  
   153  		gomega.Eventually(ctx, func() (string, error) {
   154  			return e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, pod.Name, containerName)
   155  		},
   156  			podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("key3=\"value3\"\n"))
   157  	})
   158  
   159  	/*
   160  	   Release: v1.9
   161  	   Testname: Projected Volume, DownwardAPI, update annotation
   162  	   Description: A Pod is created with a projected volume source for downwardAPI with pod name, cpu and memory limits and cpu and memory requests and annotation items. Pod MUST be able to read the annotations from the mounted DownwardAPIVolumeFiles. Annotations are then updated. Pod MUST be able to read the updated values for the Annotations.
   163  	*/
   164  	framework.ConformanceIt("should update annotations on modification", f.WithNodeConformance(), func(ctx context.Context) {
   165  		annotations := map[string]string{}
   166  		annotations["builder"] = "bar"
   167  		podName := "annotationupdate" + string(uuid.NewUUID())
   168  		pod := projectedDownwardAPIVolumePodForUpdateTest(podName, map[string]string{}, annotations, "/etc/podinfo/annotations")
   169  
   170  		containerName := "client-container"
   171  		ginkgo.By("Creating the pod")
   172  		pod = podClient.CreateSync(ctx, pod)
   173  
   174  		gomega.Eventually(ctx, func() (string, error) {
   175  			return e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, pod.Name, containerName)
   176  		},
   177  			podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("builder=\"bar\"\n"))
   178  
   179  		//modify annotations
   180  		podClient.Update(ctx, podName, func(pod *v1.Pod) {
   181  			pod.Annotations["builder"] = "foo"
   182  		})
   183  
   184  		gomega.Eventually(ctx, func() (string, error) {
   185  			return e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, pod.Name, containerName)
   186  		},
   187  			podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("builder=\"foo\"\n"))
   188  	})
   189  
   190  	/*
   191  	   Release: v1.9
   192  	   Testname: Projected Volume, DownwardAPI, CPU limits
   193  	   Description: A Pod is created with a projected volume source for downwardAPI with pod name, cpu and memory limits and cpu and memory requests. Pod MUST be able to read the cpu limits from the mounted DownwardAPIVolumeFiles.
   194  	*/
   195  	framework.ConformanceIt("should provide container's cpu limit", f.WithNodeConformance(), func(ctx context.Context) {
   196  		podName := "downwardapi-volume-" + string(uuid.NewUUID())
   197  		pod := downwardAPIVolumeForContainerResources(podName, "/etc/podinfo/cpu_limit")
   198  
   199  		e2epodoutput.TestContainerOutput(ctx, f, "downward API volume plugin", pod, 0, []string{
   200  			fmt.Sprintf("2\n"),
   201  		})
   202  	})
   203  
   204  	/*
   205  	   Release: v1.9
   206  	   Testname: Projected Volume, DownwardAPI, memory limits
   207  	   Description: A Pod is created with a projected volume source for downwardAPI with pod name, cpu and memory limits and cpu and memory requests. Pod MUST be able to read the memory limits from the mounted DownwardAPIVolumeFiles.
   208  	*/
   209  	framework.ConformanceIt("should provide container's memory limit", f.WithNodeConformance(), func(ctx context.Context) {
   210  		podName := "downwardapi-volume-" + string(uuid.NewUUID())
   211  		pod := downwardAPIVolumeForContainerResources(podName, "/etc/podinfo/memory_limit")
   212  
   213  		e2epodoutput.TestContainerOutput(ctx, f, "downward API volume plugin", pod, 0, []string{
   214  			"134217728\n",
   215  		})
   216  	})
   217  
   218  	/*
   219  	   Release: v1.9
   220  	   Testname: Projected Volume, DownwardAPI, CPU request
   221  	   Description: A Pod is created with a projected volume source for downwardAPI with pod name, cpu and memory limits and cpu and memory requests. Pod MUST be able to read the cpu request from the mounted DownwardAPIVolumeFiles.
   222  	*/
   223  	framework.ConformanceIt("should provide container's cpu request", f.WithNodeConformance(), func(ctx context.Context) {
   224  		podName := "downwardapi-volume-" + string(uuid.NewUUID())
   225  		pod := downwardAPIVolumeForContainerResources(podName, "/etc/podinfo/cpu_request")
   226  
   227  		e2epodoutput.TestContainerOutput(ctx, f, "downward API volume plugin", pod, 0, []string{
   228  			fmt.Sprintf("1\n"),
   229  		})
   230  	})
   231  
   232  	/*
   233  	   Release: v1.9
   234  	   Testname: Projected Volume, DownwardAPI, memory request
   235  	   Description: A Pod is created with a projected volume source for downwardAPI with pod name, cpu and memory limits and cpu and memory requests. Pod MUST be able to read the memory request from the mounted DownwardAPIVolumeFiles.
   236  	*/
   237  	framework.ConformanceIt("should provide container's memory request", f.WithNodeConformance(), func(ctx context.Context) {
   238  		podName := "downwardapi-volume-" + string(uuid.NewUUID())
   239  		pod := downwardAPIVolumeForContainerResources(podName, "/etc/podinfo/memory_request")
   240  
   241  		e2epodoutput.TestContainerOutput(ctx, f, "downward API volume plugin", pod, 0, []string{
   242  			fmt.Sprintf("33554432\n"),
   243  		})
   244  	})
   245  
   246  	/*
   247  	   Release: v1.9
   248  	   Testname: Projected Volume, DownwardAPI, CPU limit, node allocatable
   249  	   Description: A Pod is created with a projected volume source for downwardAPI with pod name, cpu and memory limits and cpu and memory requests.  The CPU and memory resources for requests and limits are NOT specified for the container. Pod MUST be able to read the default cpu limits from the mounted DownwardAPIVolumeFiles.
   250  	*/
   251  	framework.ConformanceIt("should provide node allocatable (cpu) as default cpu limit if the limit is not set", f.WithNodeConformance(), func(ctx context.Context) {
   252  		podName := "downwardapi-volume-" + string(uuid.NewUUID())
   253  		pod := downwardAPIVolumeForDefaultContainerResources(podName, "/etc/podinfo/cpu_limit")
   254  
   255  		e2epodoutput.TestContainerOutputRegexp(ctx, f, "downward API volume plugin", pod, 0, []string{"[1-9]"})
   256  	})
   257  
   258  	/*
   259  	   Release: v1.9
   260  	   Testname: Projected Volume, DownwardAPI, memory limit, node allocatable
   261  	   Description: A Pod is created with a projected volume source for downwardAPI with pod name, cpu and memory limits and cpu and memory requests.  The CPU and memory resources for requests and limits are NOT specified for the container. Pod MUST be able to read the default memory limits from the mounted DownwardAPIVolumeFiles.
   262  	*/
   263  	framework.ConformanceIt("should provide node allocatable (memory) as default memory limit if the limit is not set", f.WithNodeConformance(), func(ctx context.Context) {
   264  		podName := "downwardapi-volume-" + string(uuid.NewUUID())
   265  		pod := downwardAPIVolumeForDefaultContainerResources(podName, "/etc/podinfo/memory_limit")
   266  
   267  		e2epodoutput.TestContainerOutputRegexp(ctx, f, "downward API volume plugin", pod, 0, []string{"[1-9]"})
   268  	})
   269  })
   270  
   271  func projectedDownwardAPIVolumePodForModeTest(name, filePath string, itemMode, defaultMode *int32) *v1.Pod {
   272  	pod := projectedDownwardAPIVolumeBasePod(name, nil, nil)
   273  
   274  	pod.Spec.Containers = []v1.Container{
   275  		{
   276  			Name:  "client-container",
   277  			Image: imageutils.GetE2EImage(imageutils.Agnhost),
   278  			Args:  []string{"mounttest", "--file_mode=" + filePath},
   279  			VolumeMounts: []v1.VolumeMount{
   280  				{
   281  					Name:      "podinfo",
   282  					MountPath: "/etc/podinfo",
   283  				},
   284  			},
   285  		},
   286  	}
   287  	if itemMode != nil {
   288  		pod.Spec.Volumes[0].VolumeSource.Projected.Sources[0].DownwardAPI.Items[0].Mode = itemMode
   289  	}
   290  	if defaultMode != nil {
   291  		pod.Spec.Volumes[0].VolumeSource.Projected.DefaultMode = defaultMode
   292  	}
   293  
   294  	return pod
   295  }
   296  
   297  func projectedDownwardAPIVolumePodForUpdateTest(name string, labels, annotations map[string]string, filePath string) *v1.Pod {
   298  	pod := projectedDownwardAPIVolumeBasePod(name, labels, annotations)
   299  
   300  	pod.Spec.Containers = []v1.Container{
   301  		{
   302  			Name:  "client-container",
   303  			Image: imageutils.GetE2EImage(imageutils.Agnhost),
   304  			Args:  []string{"mounttest", "--break_on_expected_content=false", "--retry_time=1200", "--file_content_in_loop=" + filePath},
   305  			VolumeMounts: []v1.VolumeMount{
   306  				{
   307  					Name:      "podinfo",
   308  					MountPath: "/etc/podinfo",
   309  					ReadOnly:  false,
   310  				},
   311  			},
   312  		},
   313  	}
   314  
   315  	applyLabelsAndAnnotationsToProjectedDownwardAPIPod(labels, annotations, pod)
   316  	return pod
   317  }
   318  
   319  func projectedDownwardAPIVolumeBasePod(name string, labels, annotations map[string]string) *v1.Pod {
   320  	pod := &v1.Pod{
   321  		ObjectMeta: metav1.ObjectMeta{
   322  			Name:        name,
   323  			Labels:      labels,
   324  			Annotations: annotations,
   325  		},
   326  		Spec: v1.PodSpec{
   327  			Volumes: []v1.Volume{
   328  				{
   329  					Name: "podinfo",
   330  					VolumeSource: v1.VolumeSource{
   331  						Projected: &v1.ProjectedVolumeSource{
   332  							Sources: []v1.VolumeProjection{
   333  								{
   334  									DownwardAPI: &v1.DownwardAPIProjection{
   335  										Items: []v1.DownwardAPIVolumeFile{
   336  											{
   337  												Path: "podname",
   338  												FieldRef: &v1.ObjectFieldSelector{
   339  													APIVersion: "v1",
   340  													FieldPath:  "metadata.name",
   341  												},
   342  											},
   343  											{
   344  												Path: "cpu_limit",
   345  												ResourceFieldRef: &v1.ResourceFieldSelector{
   346  													ContainerName: "client-container",
   347  													Resource:      "limits.cpu",
   348  												},
   349  											},
   350  											{
   351  												Path: "cpu_request",
   352  												ResourceFieldRef: &v1.ResourceFieldSelector{
   353  													ContainerName: "client-container",
   354  													Resource:      "requests.cpu",
   355  												},
   356  											},
   357  											{
   358  												Path: "memory_limit",
   359  												ResourceFieldRef: &v1.ResourceFieldSelector{
   360  													ContainerName: "client-container",
   361  													Resource:      "limits.memory",
   362  												},
   363  											},
   364  											{
   365  												Path: "memory_request",
   366  												ResourceFieldRef: &v1.ResourceFieldSelector{
   367  													ContainerName: "client-container",
   368  													Resource:      "requests.memory",
   369  												},
   370  											},
   371  										},
   372  									},
   373  								},
   374  							},
   375  						},
   376  					},
   377  				},
   378  			},
   379  			RestartPolicy: v1.RestartPolicyNever,
   380  		},
   381  	}
   382  
   383  	return pod
   384  }
   385  
   386  func applyLabelsAndAnnotationsToProjectedDownwardAPIPod(labels, annotations map[string]string, pod *v1.Pod) {
   387  	if len(labels) > 0 {
   388  		pod.Spec.Volumes[0].VolumeSource.Projected.Sources[0].DownwardAPI.Items = append(pod.Spec.Volumes[0].VolumeSource.Projected.Sources[0].DownwardAPI.Items, v1.DownwardAPIVolumeFile{
   389  			Path: "labels",
   390  			FieldRef: &v1.ObjectFieldSelector{
   391  				APIVersion: "v1",
   392  				FieldPath:  "metadata.labels",
   393  			},
   394  		})
   395  	}
   396  
   397  	if len(annotations) > 0 {
   398  		pod.Spec.Volumes[0].VolumeSource.Projected.Sources[0].DownwardAPI.Items = append(pod.Spec.Volumes[0].VolumeSource.Projected.Sources[0].DownwardAPI.Items, v1.DownwardAPIVolumeFile{
   399  			Path: "annotations",
   400  			FieldRef: &v1.ObjectFieldSelector{
   401  				APIVersion: "v1",
   402  				FieldPath:  "metadata.annotations",
   403  			},
   404  		})
   405  	}
   406  }
   407  

View as plain text