k8s.io/kubernetes@v1.29.3/test/e2e/common/node/downwardapi.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package node
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  
    23  	v1 "k8s.io/api/core/v1"
    24  	"k8s.io/apimachinery/pkg/api/resource"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/util/uuid"
    27  	"k8s.io/kubernetes/test/e2e/framework"
    28  	e2enetwork "k8s.io/kubernetes/test/e2e/framework/network"
    29  	e2epodoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
    30  	"k8s.io/kubernetes/test/e2e/nodefeature"
    31  	imageutils "k8s.io/kubernetes/test/utils/image"
    32  	admissionapi "k8s.io/pod-security-admission/api"
    33  
    34  	"github.com/onsi/ginkgo/v2"
    35  )
    36  
    37  var _ = SIGDescribe("Downward API", func() {
    38  	f := framework.NewDefaultFramework("downward-api")
    39  	f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
    40  
    41  	/*
    42  	   Release: v1.9
    43  	   Testname: DownwardAPI, environment for name, namespace and ip
    44  	   Description: Downward API MUST expose Pod and Container fields as environment variables. Specify Pod Name, namespace and IP as environment variable in the Pod Spec are visible at runtime in the container.
    45  	*/
    46  	framework.ConformanceIt("should provide pod name, namespace and IP address as env vars", f.WithNodeConformance(), func(ctx context.Context) {
    47  		podName := "downward-api-" + string(uuid.NewUUID())
    48  		env := []v1.EnvVar{
    49  			{
    50  				Name: "POD_NAME",
    51  				ValueFrom: &v1.EnvVarSource{
    52  					FieldRef: &v1.ObjectFieldSelector{
    53  						APIVersion: "v1",
    54  						FieldPath:  "metadata.name",
    55  					},
    56  				},
    57  			},
    58  			{
    59  				Name: "POD_NAMESPACE",
    60  				ValueFrom: &v1.EnvVarSource{
    61  					FieldRef: &v1.ObjectFieldSelector{
    62  						APIVersion: "v1",
    63  						FieldPath:  "metadata.namespace",
    64  					},
    65  				},
    66  			},
    67  			{
    68  				Name: "POD_IP",
    69  				ValueFrom: &v1.EnvVarSource{
    70  					FieldRef: &v1.ObjectFieldSelector{
    71  						APIVersion: "v1",
    72  						FieldPath:  "status.podIP",
    73  					},
    74  				},
    75  			},
    76  		}
    77  
    78  		expectations := []string{
    79  			fmt.Sprintf("POD_NAME=%v", podName),
    80  			fmt.Sprintf("POD_NAMESPACE=%v", f.Namespace.Name),
    81  			fmt.Sprintf("POD_IP=%v|%v", e2enetwork.RegexIPv4, e2enetwork.RegexIPv6),
    82  		}
    83  
    84  		testDownwardAPI(ctx, f, podName, env, expectations)
    85  	})
    86  
    87  	/*
    88  	   Release: v1.9
    89  	   Testname: DownwardAPI, environment for host ip
    90  	   Description: Downward API MUST expose Pod and Container fields as environment variables. Specify host IP as environment variable in the Pod Spec are visible at runtime in the container.
    91  	*/
    92  	framework.ConformanceIt("should provide host IP as an env var", f.WithNodeConformance(), func(ctx context.Context) {
    93  		podName := "downward-api-" + string(uuid.NewUUID())
    94  		env := []v1.EnvVar{
    95  			{
    96  				Name: "HOST_IP",
    97  				ValueFrom: &v1.EnvVarSource{
    98  					FieldRef: &v1.ObjectFieldSelector{
    99  						APIVersion: "v1",
   100  						FieldPath:  "status.hostIP",
   101  					},
   102  				},
   103  			},
   104  		}
   105  
   106  		expectations := []string{
   107  			fmt.Sprintf("HOST_IP=%v|%v", e2enetwork.RegexIPv4, e2enetwork.RegexIPv6),
   108  		}
   109  
   110  		testDownwardAPI(ctx, f, podName, env, expectations)
   111  	})
   112  
   113  	ginkgo.It("should provide host IP and pod IP as an env var if pod uses host network [LinuxOnly]", func(ctx context.Context) {
   114  		podName := "downward-api-" + string(uuid.NewUUID())
   115  		env := []v1.EnvVar{
   116  			{
   117  				Name: "HOST_IP",
   118  				ValueFrom: &v1.EnvVarSource{
   119  					FieldRef: &v1.ObjectFieldSelector{
   120  						APIVersion: "v1",
   121  						FieldPath:  "status.hostIP",
   122  					},
   123  				},
   124  			},
   125  			{
   126  				Name: "POD_IP",
   127  				ValueFrom: &v1.EnvVarSource{
   128  					FieldRef: &v1.ObjectFieldSelector{
   129  						APIVersion: "v1",
   130  						FieldPath:  "status.podIP",
   131  					},
   132  				},
   133  			},
   134  		}
   135  
   136  		expectations := []string{
   137  			fmt.Sprintf("OK"),
   138  		}
   139  
   140  		pod := &v1.Pod{
   141  			ObjectMeta: metav1.ObjectMeta{
   142  				Name:   podName,
   143  				Labels: map[string]string{"name": podName},
   144  			},
   145  			Spec: v1.PodSpec{
   146  				Containers: []v1.Container{
   147  					{
   148  						Name:    "dapi-container",
   149  						Image:   imageutils.GetE2EImage(imageutils.BusyBox),
   150  						Command: []string{"sh", "-c", `[[ "${HOST_IP:?}" == "${POD_IP:?}" ]] && echo 'OK' || echo "HOST_IP: '${HOST_IP}' != POD_IP: '${POD_IP}'"`},
   151  						Env:     env,
   152  					},
   153  				},
   154  				HostNetwork:   true,
   155  				RestartPolicy: v1.RestartPolicyNever,
   156  			},
   157  		}
   158  
   159  		testDownwardAPIUsingPod(ctx, f, pod, env, expectations)
   160  
   161  	})
   162  
   163  	/*
   164  	   Release: v1.9
   165  	   Testname: DownwardAPI, environment for CPU and memory limits and requests
   166  	   Description: Downward API MUST expose CPU request and Memory request set through environment variables at runtime in the container.
   167  	*/
   168  	framework.ConformanceIt("should provide container's limits.cpu/memory and requests.cpu/memory as env vars", f.WithNodeConformance(), func(ctx context.Context) {
   169  		podName := "downward-api-" + string(uuid.NewUUID())
   170  		env := []v1.EnvVar{
   171  			{
   172  				Name: "CPU_LIMIT",
   173  				ValueFrom: &v1.EnvVarSource{
   174  					ResourceFieldRef: &v1.ResourceFieldSelector{
   175  						Resource: "limits.cpu",
   176  					},
   177  				},
   178  			},
   179  			{
   180  				Name: "MEMORY_LIMIT",
   181  				ValueFrom: &v1.EnvVarSource{
   182  					ResourceFieldRef: &v1.ResourceFieldSelector{
   183  						Resource: "limits.memory",
   184  					},
   185  				},
   186  			},
   187  			{
   188  				Name: "CPU_REQUEST",
   189  				ValueFrom: &v1.EnvVarSource{
   190  					ResourceFieldRef: &v1.ResourceFieldSelector{
   191  						Resource: "requests.cpu",
   192  					},
   193  				},
   194  			},
   195  			{
   196  				Name: "MEMORY_REQUEST",
   197  				ValueFrom: &v1.EnvVarSource{
   198  					ResourceFieldRef: &v1.ResourceFieldSelector{
   199  						Resource: "requests.memory",
   200  					},
   201  				},
   202  			},
   203  		}
   204  		expectations := []string{
   205  			"CPU_LIMIT=2",
   206  			"MEMORY_LIMIT=67108864",
   207  			"CPU_REQUEST=1",
   208  			"MEMORY_REQUEST=33554432",
   209  		}
   210  
   211  		testDownwardAPI(ctx, f, podName, env, expectations)
   212  	})
   213  
   214  	/*
   215  	   Release: v1.9
   216  	   Testname: DownwardAPI, environment for default CPU and memory limits and requests
   217  	   Description: Downward API MUST expose CPU request and Memory limits set through environment variables at runtime in the container.
   218  	*/
   219  	framework.ConformanceIt("should provide default limits.cpu/memory from node allocatable", f.WithNodeConformance(), func(ctx context.Context) {
   220  		podName := "downward-api-" + string(uuid.NewUUID())
   221  		env := []v1.EnvVar{
   222  			{
   223  				Name: "CPU_LIMIT",
   224  				ValueFrom: &v1.EnvVarSource{
   225  					ResourceFieldRef: &v1.ResourceFieldSelector{
   226  						Resource: "limits.cpu",
   227  					},
   228  				},
   229  			},
   230  			{
   231  				Name: "MEMORY_LIMIT",
   232  				ValueFrom: &v1.EnvVarSource{
   233  					ResourceFieldRef: &v1.ResourceFieldSelector{
   234  						Resource: "limits.memory",
   235  					},
   236  				},
   237  			},
   238  		}
   239  		expectations := []string{
   240  			"CPU_LIMIT=[1-9]",
   241  			"MEMORY_LIMIT=[1-9]",
   242  		}
   243  		pod := &v1.Pod{
   244  			ObjectMeta: metav1.ObjectMeta{
   245  				Name:   podName,
   246  				Labels: map[string]string{"name": podName},
   247  			},
   248  			Spec: v1.PodSpec{
   249  				Containers: []v1.Container{
   250  					{
   251  						Name:    "dapi-container",
   252  						Image:   imageutils.GetE2EImage(imageutils.BusyBox),
   253  						Command: []string{"sh", "-c", "env"},
   254  						Env:     env,
   255  					},
   256  				},
   257  				RestartPolicy: v1.RestartPolicyNever,
   258  			},
   259  		}
   260  
   261  		testDownwardAPIUsingPod(ctx, f, pod, env, expectations)
   262  	})
   263  
   264  	/*
   265  	   Release: v1.9
   266  	   Testname: DownwardAPI, environment for Pod UID
   267  	   Description: Downward API MUST expose Pod UID set through environment variables at runtime in the container.
   268  	*/
   269  	framework.ConformanceIt("should provide pod UID as env vars", f.WithNodeConformance(), func(ctx context.Context) {
   270  		podName := "downward-api-" + string(uuid.NewUUID())
   271  		env := []v1.EnvVar{
   272  			{
   273  				Name: "POD_UID",
   274  				ValueFrom: &v1.EnvVarSource{
   275  					FieldRef: &v1.ObjectFieldSelector{
   276  						APIVersion: "v1",
   277  						FieldPath:  "metadata.uid",
   278  					},
   279  				},
   280  			},
   281  		}
   282  
   283  		expectations := []string{
   284  			"POD_UID=[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}",
   285  		}
   286  
   287  		testDownwardAPI(ctx, f, podName, env, expectations)
   288  	})
   289  })
   290  
   291  var _ = SIGDescribe("Downward API", framework.WithSerial(), framework.WithDisruptive(), nodefeature.DownwardAPIHugePages, func() {
   292  	f := framework.NewDefaultFramework("downward-api")
   293  	f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
   294  
   295  	ginkgo.Context("Downward API tests for hugepages", func() {
   296  		ginkgo.It("should provide container's limits.hugepages-<pagesize> and requests.hugepages-<pagesize> as env vars", func(ctx context.Context) {
   297  			podName := "downward-api-" + string(uuid.NewUUID())
   298  			env := []v1.EnvVar{
   299  				{
   300  					Name: "HUGEPAGES_LIMIT",
   301  					ValueFrom: &v1.EnvVarSource{
   302  						ResourceFieldRef: &v1.ResourceFieldSelector{
   303  							Resource: "limits.hugepages-2Mi",
   304  						},
   305  					},
   306  				},
   307  				{
   308  					Name: "HUGEPAGES_REQUEST",
   309  					ValueFrom: &v1.EnvVarSource{
   310  						ResourceFieldRef: &v1.ResourceFieldSelector{
   311  							Resource: "requests.hugepages-2Mi",
   312  						},
   313  					},
   314  				},
   315  			}
   316  
   317  			// Important: we explicitly request no hugepages so the test can run where none are present.
   318  			expectations := []string{
   319  				fmt.Sprintf("HUGEPAGES_LIMIT=%d", 0),
   320  				fmt.Sprintf("HUGEPAGES_REQUEST=%d", 0),
   321  			}
   322  			pod := &v1.Pod{
   323  				ObjectMeta: metav1.ObjectMeta{
   324  					Name:   podName,
   325  					Labels: map[string]string{"name": podName},
   326  				},
   327  				Spec: v1.PodSpec{
   328  					Containers: []v1.Container{
   329  						{
   330  							Name:    "dapi-container",
   331  							Image:   imageutils.GetE2EImage(imageutils.BusyBox),
   332  							Command: []string{"sh", "-c", "env"},
   333  							Resources: v1.ResourceRequirements{
   334  								Requests: v1.ResourceList{
   335  									"cpu":           resource.MustParse("10m"),
   336  									"hugepages-2Mi": resource.MustParse("0Mi"),
   337  								},
   338  								Limits: v1.ResourceList{
   339  									"hugepages-2Mi": resource.MustParse("0Mi"),
   340  								},
   341  							},
   342  							Env: env,
   343  						},
   344  					},
   345  					RestartPolicy: v1.RestartPolicyNever,
   346  				},
   347  			}
   348  			testDownwardAPIUsingPod(ctx, f, pod, env, expectations)
   349  		})
   350  
   351  		ginkgo.It("should provide default limits.hugepages-<pagesize> from node allocatable", func(ctx context.Context) {
   352  			podName := "downward-api-" + string(uuid.NewUUID())
   353  			env := []v1.EnvVar{
   354  				{
   355  					Name: "HUGEPAGES_LIMIT",
   356  					ValueFrom: &v1.EnvVarSource{
   357  						ResourceFieldRef: &v1.ResourceFieldSelector{
   358  							Resource: "limits.hugepages-2Mi",
   359  						},
   360  					},
   361  				},
   362  			}
   363  			// Important: we allow for 0 so the test passes in environments where no hugepages are allocated.
   364  			expectations := []string{
   365  				"HUGEPAGES_LIMIT=((0)|([1-9][0-9]*))\n",
   366  			}
   367  			pod := &v1.Pod{
   368  				ObjectMeta: metav1.ObjectMeta{
   369  					Name:   podName,
   370  					Labels: map[string]string{"name": podName},
   371  				},
   372  				Spec: v1.PodSpec{
   373  					Containers: []v1.Container{
   374  						{
   375  							Name:    "dapi-container",
   376  							Image:   imageutils.GetE2EImage(imageutils.BusyBox),
   377  							Command: []string{"sh", "-c", "env"},
   378  							Env:     env,
   379  						},
   380  					},
   381  					RestartPolicy: v1.RestartPolicyNever,
   382  				},
   383  			}
   384  
   385  			testDownwardAPIUsingPod(ctx, f, pod, env, expectations)
   386  		})
   387  	})
   388  
   389  })
   390  
   391  func testDownwardAPI(ctx context.Context, f *framework.Framework, podName string, env []v1.EnvVar, expectations []string) {
   392  	pod := &v1.Pod{
   393  		ObjectMeta: metav1.ObjectMeta{
   394  			Name:   podName,
   395  			Labels: map[string]string{"name": podName},
   396  		},
   397  		Spec: v1.PodSpec{
   398  			Containers: []v1.Container{
   399  				{
   400  					Name:    "dapi-container",
   401  					Image:   imageutils.GetE2EImage(imageutils.BusyBox),
   402  					Command: []string{"sh", "-c", "env"},
   403  					Resources: v1.ResourceRequirements{
   404  						Requests: v1.ResourceList{
   405  							v1.ResourceCPU:    resource.MustParse("250m"),
   406  							v1.ResourceMemory: resource.MustParse("32Mi"),
   407  						},
   408  						Limits: v1.ResourceList{
   409  							v1.ResourceCPU:    resource.MustParse("1250m"),
   410  							v1.ResourceMemory: resource.MustParse("64Mi"),
   411  						},
   412  					},
   413  					Env: env,
   414  				},
   415  			},
   416  			RestartPolicy: v1.RestartPolicyNever,
   417  		},
   418  	}
   419  
   420  	testDownwardAPIUsingPod(ctx, f, pod, env, expectations)
   421  }
   422  
   423  func testDownwardAPIUsingPod(ctx context.Context, f *framework.Framework, pod *v1.Pod, env []v1.EnvVar, expectations []string) {
   424  	e2epodoutput.TestContainerOutputRegexp(ctx, f, "downward api env vars", pod, 0, expectations)
   425  }