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