...

Source file src/k8s.io/kubernetes/test/e2e/common/node/expansion.go

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

     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 node
    18  
    19  import (
    20  	"context"
    21  
    22  	v1 "k8s.io/api/core/v1"
    23  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    24  	"k8s.io/apimachinery/pkg/util/uuid"
    25  	"k8s.io/kubernetes/test/e2e/feature"
    26  	"k8s.io/kubernetes/test/e2e/framework"
    27  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    28  	e2epodoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
    29  	imageutils "k8s.io/kubernetes/test/utils/image"
    30  	admissionapi "k8s.io/pod-security-admission/api"
    31  
    32  	"github.com/onsi/ginkgo/v2"
    33  	"github.com/onsi/gomega"
    34  )
    35  
    36  // These tests exercise the Kubernetes expansion syntax $(VAR).
    37  // For more information, see:
    38  // https://github.com/kubernetes/community/blob/master/contributors/design-proposals/node/expansion.md
    39  var _ = SIGDescribe("Variable Expansion", func() {
    40  	f := framework.NewDefaultFramework("var-expansion")
    41  	f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
    42  
    43  	/*
    44  		Release: v1.9
    45  		Testname: Environment variables, expansion
    46  		Description: Create a Pod with environment variables. Environment variables defined using previously defined environment variables MUST expand to proper values.
    47  	*/
    48  	framework.ConformanceIt("should allow composing env vars into new env vars", f.WithNodeConformance(), func(ctx context.Context) {
    49  		envVars := []v1.EnvVar{
    50  			{
    51  				Name:  "FOO",
    52  				Value: "foo-value",
    53  			},
    54  			{
    55  				Name:  "BAR",
    56  				Value: "bar-value",
    57  			},
    58  			{
    59  				Name:  "FOOBAR",
    60  				Value: "$(FOO);;$(BAR)",
    61  			},
    62  		}
    63  		pod := newPod([]string{"sh", "-c", "env"}, envVars, nil, nil)
    64  
    65  		e2epodoutput.TestContainerOutput(ctx, f, "env composition", pod, 0, []string{
    66  			"FOO=foo-value",
    67  			"BAR=bar-value",
    68  			"FOOBAR=foo-value;;bar-value",
    69  		})
    70  	})
    71  
    72  	/*
    73  		Release: v1.9
    74  		Testname: Environment variables, command expansion
    75  		Description: Create a Pod with environment variables and container command using them. Container command using the  defined environment variables MUST expand to proper values.
    76  	*/
    77  	framework.ConformanceIt("should allow substituting values in a container's command", f.WithNodeConformance(), func(ctx context.Context) {
    78  		envVars := []v1.EnvVar{
    79  			{
    80  				Name:  "TEST_VAR",
    81  				Value: "test-value",
    82  			},
    83  		}
    84  		pod := newPod([]string{"sh", "-c", "TEST_VAR=wrong echo \"$(TEST_VAR)\""}, envVars, nil, nil)
    85  
    86  		e2epodoutput.TestContainerOutput(ctx, f, "substitution in container's command", pod, 0, []string{
    87  			"test-value",
    88  		})
    89  	})
    90  
    91  	/*
    92  		Release: v1.9
    93  		Testname: Environment variables, command argument expansion
    94  		Description: Create a Pod with environment variables and container command arguments using them. Container command arguments using the  defined environment variables MUST expand to proper values.
    95  	*/
    96  	framework.ConformanceIt("should allow substituting values in a container's args", f.WithNodeConformance(), func(ctx context.Context) {
    97  		envVars := []v1.EnvVar{
    98  			{
    99  				Name:  "TEST_VAR",
   100  				Value: "test-value",
   101  			},
   102  		}
   103  		pod := newPod([]string{"sh", "-c"}, envVars, nil, nil)
   104  		pod.Spec.Containers[0].Args = []string{"TEST_VAR=wrong echo \"$(TEST_VAR)\""}
   105  
   106  		e2epodoutput.TestContainerOutput(ctx, f, "substitution in container's args", pod, 0, []string{
   107  			"test-value",
   108  		})
   109  	})
   110  
   111  	/*
   112  		Release: v1.19
   113  		Testname: VolumeSubpathEnvExpansion, subpath expansion
   114  		Description: Make sure a container's subpath can be set using an expansion of environment variables.
   115  	*/
   116  	framework.ConformanceIt("should allow substituting values in a volume subpath", func(ctx context.Context) {
   117  		envVars := []v1.EnvVar{
   118  			{
   119  				Name:  "POD_NAME",
   120  				Value: "foo",
   121  			},
   122  		}
   123  		mounts := []v1.VolumeMount{
   124  			{
   125  				Name:        "workdir1",
   126  				MountPath:   "/logscontainer",
   127  				SubPathExpr: "$(POD_NAME)",
   128  			},
   129  			{
   130  				Name:      "workdir1",
   131  				MountPath: "/testcontainer",
   132  			},
   133  		}
   134  		volumes := []v1.Volume{
   135  			{
   136  				Name: "workdir1",
   137  				VolumeSource: v1.VolumeSource{
   138  					EmptyDir: &v1.EmptyDirVolumeSource{},
   139  				},
   140  			},
   141  		}
   142  		pod := newPod([]string{}, envVars, mounts, volumes)
   143  		envVars[0].Value = pod.ObjectMeta.Name
   144  		pod.Spec.Containers[0].Command = []string{"sh", "-c", "test -d /testcontainer/" + pod.ObjectMeta.Name + ";echo $?"}
   145  
   146  		e2epodoutput.TestContainerOutput(ctx, f, "substitution in volume subpath", pod, 0, []string{
   147  			"0",
   148  		})
   149  	})
   150  
   151  	/*
   152  		Release: v1.19
   153  		Testname: VolumeSubpathEnvExpansion, subpath with backticks
   154  		Description: Make sure a container's subpath can not be set using an expansion of environment variables when backticks are supplied.
   155  	*/
   156  	framework.ConformanceIt("should fail substituting values in a volume subpath with backticks", f.WithSlow(), func(ctx context.Context) {
   157  
   158  		envVars := []v1.EnvVar{
   159  			{
   160  				Name:  "POD_NAME",
   161  				Value: "..",
   162  			},
   163  		}
   164  		mounts := []v1.VolumeMount{
   165  			{
   166  				Name:        "workdir1",
   167  				MountPath:   "/logscontainer",
   168  				SubPathExpr: "$(POD_NAME)",
   169  			},
   170  		}
   171  		volumes := []v1.Volume{
   172  			{
   173  				Name: "workdir1",
   174  				VolumeSource: v1.VolumeSource{
   175  					EmptyDir: &v1.EmptyDirVolumeSource{},
   176  				},
   177  			},
   178  		}
   179  		pod := newPod(nil, envVars, mounts, volumes)
   180  
   181  		// Pod should fail
   182  		testPodFailSubpath(ctx, f, pod)
   183  	})
   184  
   185  	/*
   186  		Release: v1.19
   187  		Testname: VolumeSubpathEnvExpansion, subpath with absolute path
   188  		Description: Make sure a container's subpath can not be set using an expansion of environment variables when absolute path is supplied.
   189  	*/
   190  	framework.ConformanceIt("should fail substituting values in a volume subpath with absolute path", f.WithSlow(), func(ctx context.Context) {
   191  		absolutePath := "/tmp"
   192  		if framework.NodeOSDistroIs("windows") {
   193  			// Windows does not typically have a C:\tmp folder.
   194  			absolutePath = "C:\\Users"
   195  		}
   196  
   197  		envVars := []v1.EnvVar{
   198  			{
   199  				Name:  "POD_NAME",
   200  				Value: absolutePath,
   201  			},
   202  		}
   203  		mounts := []v1.VolumeMount{
   204  			{
   205  				Name:        "workdir1",
   206  				MountPath:   "/logscontainer",
   207  				SubPathExpr: "$(POD_NAME)",
   208  			},
   209  		}
   210  		volumes := []v1.Volume{
   211  			{
   212  				Name: "workdir1",
   213  				VolumeSource: v1.VolumeSource{
   214  					EmptyDir: &v1.EmptyDirVolumeSource{},
   215  				},
   216  			},
   217  		}
   218  		pod := newPod(nil, envVars, mounts, volumes)
   219  
   220  		// Pod should fail
   221  		testPodFailSubpath(ctx, f, pod)
   222  	})
   223  
   224  	/*
   225  		Release: v1.19
   226  		Testname: VolumeSubpathEnvExpansion, subpath ready from failed state
   227  		Description: Verify that a failing subpath expansion can be modified during the lifecycle of a container.
   228  	*/
   229  	framework.ConformanceIt("should verify that a failing subpath expansion can be modified during the lifecycle of a container", f.WithSlow(), func(ctx context.Context) {
   230  
   231  		envVars := []v1.EnvVar{
   232  			{
   233  				Name:  "POD_NAME",
   234  				Value: "foo",
   235  			},
   236  			{
   237  				Name: "ANNOTATION",
   238  				ValueFrom: &v1.EnvVarSource{
   239  					FieldRef: &v1.ObjectFieldSelector{
   240  						APIVersion: "v1",
   241  						FieldPath:  "metadata.annotations['mysubpath']",
   242  					},
   243  				},
   244  			},
   245  		}
   246  		mounts := []v1.VolumeMount{
   247  			{
   248  				Name:        "workdir1",
   249  				MountPath:   "/subpath_mount",
   250  				SubPathExpr: "$(ANNOTATION)/$(POD_NAME)",
   251  			},
   252  			{
   253  				Name:      "workdir1",
   254  				MountPath: "/volume_mount",
   255  			},
   256  		}
   257  		volumes := []v1.Volume{
   258  			{
   259  				Name: "workdir1",
   260  				VolumeSource: v1.VolumeSource{
   261  					EmptyDir: &v1.EmptyDirVolumeSource{},
   262  				},
   263  			},
   264  		}
   265  		pod := newPod([]string{"sh", "-c", "tail -f /dev/null"}, envVars, mounts, volumes)
   266  		pod.ObjectMeta.Annotations = map[string]string{"notmysubpath": "mypath"}
   267  
   268  		ginkgo.By("creating the pod with failed condition")
   269  		podClient := e2epod.NewPodClient(f)
   270  		pod = podClient.Create(ctx, pod)
   271  
   272  		getPod := e2epod.Get(f.ClientSet, pod)
   273  		gomega.Consistently(ctx, getPod).WithTimeout(framework.PodStartShortTimeout).Should(e2epod.BeInPhase(v1.PodPending))
   274  
   275  		ginkgo.By("updating the pod")
   276  		podClient.Update(ctx, pod.ObjectMeta.Name, func(pod *v1.Pod) {
   277  			if pod.ObjectMeta.Annotations == nil {
   278  				pod.ObjectMeta.Annotations = make(map[string]string)
   279  			}
   280  			pod.ObjectMeta.Annotations["mysubpath"] = "mypath"
   281  		})
   282  
   283  		ginkgo.By("waiting for pod running")
   284  		err := e2epod.WaitTimeoutForPodRunningInNamespace(ctx, f.ClientSet, pod.Name, pod.Namespace, framework.PodStartShortTimeout)
   285  		framework.ExpectNoError(err, "while waiting for pod to be running")
   286  
   287  		ginkgo.By("deleting the pod gracefully")
   288  		err = e2epod.DeletePodWithWait(ctx, f.ClientSet, pod)
   289  		framework.ExpectNoError(err, "failed to delete pod")
   290  	})
   291  
   292  	/*
   293  		Release: v1.19
   294  		Testname: VolumeSubpathEnvExpansion, subpath test writes
   295  		Description: Verify that a subpath expansion can be used to write files into subpaths.
   296  		1.	valid subpathexpr starts a container running
   297  		2.	test for valid subpath writes
   298  		3.	successful expansion of the subpathexpr isn't required for volume cleanup
   299  
   300  	*/
   301  	framework.ConformanceIt("should succeed in writing subpaths in container", f.WithSlow(), func(ctx context.Context) {
   302  
   303  		envVars := []v1.EnvVar{
   304  			{
   305  				Name:  "POD_NAME",
   306  				Value: "foo",
   307  			},
   308  			{
   309  				Name: "ANNOTATION",
   310  				ValueFrom: &v1.EnvVarSource{
   311  					FieldRef: &v1.ObjectFieldSelector{
   312  						APIVersion: "v1",
   313  						FieldPath:  "metadata.annotations['mysubpath']",
   314  					},
   315  				},
   316  			},
   317  		}
   318  		mounts := []v1.VolumeMount{
   319  			{
   320  				Name:        "workdir1",
   321  				MountPath:   "/subpath_mount",
   322  				SubPathExpr: "$(ANNOTATION)/$(POD_NAME)",
   323  			},
   324  			{
   325  				Name:      "workdir1",
   326  				MountPath: "/volume_mount",
   327  			},
   328  		}
   329  		volumes := []v1.Volume{
   330  			{
   331  				Name: "workdir1",
   332  				VolumeSource: v1.VolumeSource{
   333  					EmptyDir: &v1.EmptyDirVolumeSource{},
   334  				},
   335  			},
   336  		}
   337  		pod := newPod([]string{"sh", "-c", "tail -f /dev/null"}, envVars, mounts, volumes)
   338  		pod.ObjectMeta.Annotations = map[string]string{"mysubpath": "mypath"}
   339  
   340  		ginkgo.By("creating the pod")
   341  		podClient := e2epod.NewPodClient(f)
   342  		pod = podClient.Create(ctx, pod)
   343  
   344  		ginkgo.By("waiting for pod running")
   345  		err := e2epod.WaitTimeoutForPodRunningInNamespace(ctx, f.ClientSet, pod.Name, pod.Namespace, framework.PodStartShortTimeout)
   346  		framework.ExpectNoError(err, "while waiting for pod to be running")
   347  
   348  		ginkgo.By("creating a file in subpath")
   349  		cmd := "touch /volume_mount/mypath/foo/test.log"
   350  		_, _, err = e2epod.ExecShellInPodWithFullOutput(ctx, f, pod.Name, cmd)
   351  		if err != nil {
   352  			framework.Failf("expected to be able to write to subpath")
   353  		}
   354  
   355  		ginkgo.By("test for file in mounted path")
   356  		cmd = "test -f /subpath_mount/test.log"
   357  		_, _, err = e2epod.ExecShellInPodWithFullOutput(ctx, f, pod.Name, cmd)
   358  		if err != nil {
   359  			framework.Failf("expected to be able to verify file")
   360  		}
   361  
   362  		ginkgo.By("updating the annotation value")
   363  		podClient.Update(ctx, pod.ObjectMeta.Name, func(pod *v1.Pod) {
   364  			pod.ObjectMeta.Annotations["mysubpath"] = "mynewpath"
   365  		})
   366  
   367  		ginkgo.By("waiting for annotated pod running")
   368  		err = e2epod.WaitTimeoutForPodRunningInNamespace(ctx, f.ClientSet, pod.Name, pod.Namespace, framework.PodStartShortTimeout)
   369  		framework.ExpectNoError(err, "while waiting for annotated pod to be running")
   370  
   371  		ginkgo.By("deleting the pod gracefully")
   372  		err = e2epod.DeletePodWithWait(ctx, f.ClientSet, pod)
   373  		framework.ExpectNoError(err, "failed to delete pod")
   374  	})
   375  
   376  	/*
   377  		Release: v1.30
   378  		Testname: Environment variables, expansion
   379  		Description: Create a Pod with environment variables. Environment variables defined using previously defined environment variables MUST expand to proper values.
   380  		Allow almost all printable ASCII characters in environment variables.
   381  	*/
   382  	framework.It("allow almost all printable ASCII characters as environment variable names", feature.RelaxedEnvironmentVariableValidation, func(ctx context.Context) {
   383  		envVars := []v1.EnvVar{
   384  			{
   385  				Name:  "!\"#$%&'()",
   386  				Value: "value-1",
   387  			},
   388  			{
   389  				Name:  "* +,-./0123456789",
   390  				Value: "value-2",
   391  			},
   392  			{
   393  				Name:  ":;<>?@",
   394  				Value: "value-3",
   395  			},
   396  			{
   397  				Name:  "[\\]^_`{}|~",
   398  				Value: "value-4",
   399  			},
   400  		}
   401  		pod := newPod([]string{"sh", "-c", "env"}, envVars, nil, nil)
   402  
   403  		e2epodoutput.TestContainerOutput(ctx, f, "env composition", pod, 0, []string{
   404  			"!\"#$%&'()=value-1",
   405  			"* +,-./0123456789=value-2",
   406  			":;<>?@=value-3",
   407  			"[\\]^_`{}|~=value-4",
   408  		})
   409  	})
   410  
   411  })
   412  
   413  func testPodFailSubpath(ctx context.Context, f *framework.Framework, pod *v1.Pod) {
   414  	podClient := e2epod.NewPodClient(f)
   415  	pod = podClient.Create(ctx, pod)
   416  
   417  	ginkgo.DeferCleanup(e2epod.DeletePodWithWait, f.ClientSet, pod)
   418  
   419  	err := e2epod.WaitForPodContainerToFail(ctx, f.ClientSet, pod.Namespace, pod.Name, 0, "CreateContainerConfigError", framework.PodStartShortTimeout)
   420  	framework.ExpectNoError(err, "while waiting for the pod container to fail")
   421  }
   422  
   423  func newPod(command []string, envVars []v1.EnvVar, mounts []v1.VolumeMount, volumes []v1.Volume) *v1.Pod {
   424  	podName := "var-expansion-" + string(uuid.NewUUID())
   425  	return &v1.Pod{
   426  		ObjectMeta: metav1.ObjectMeta{
   427  			Name:   podName,
   428  			Labels: map[string]string{"name": podName},
   429  		},
   430  		Spec: v1.PodSpec{
   431  			Containers:    []v1.Container{newContainer("dapi-container", command, envVars, mounts)},
   432  			RestartPolicy: v1.RestartPolicyNever,
   433  			Volumes:       volumes,
   434  		},
   435  	}
   436  }
   437  
   438  func newContainer(containerName string, command []string, envVars []v1.EnvVar, mounts []v1.VolumeMount) v1.Container {
   439  	return v1.Container{
   440  		Name:         containerName,
   441  		Image:        imageutils.GetE2EImage(imageutils.BusyBox),
   442  		Command:      command,
   443  		Env:          envVars,
   444  		VolumeMounts: mounts,
   445  	}
   446  }
   447  

View as plain text