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	"bytes"
21	"context"
22	"fmt"
23	"strings"
24	"time"
25
26	v1 "k8s.io/api/core/v1"
27	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28	"k8s.io/apimachinery/pkg/util/uuid"
29	"k8s.io/kubernetes/test/e2e/framework"
30
31	"github.com/onsi/ginkgo"
32	"github.com/onsi/gomega"
33)
34
35var _ = SIGDescribe("Kubelet", func() {
36	f := framework.NewDefaultFramework("kubelet-test")
37	var podClient *framework.PodClient
38	ginkgo.BeforeEach(func() {
39		podClient = f.PodClient()
40	})
41	ginkgo.Context("when scheduling a busybox command in a pod", func() {
42		podName := "busybox-scheduling-" + string(uuid.NewUUID())
43
44		/*
45			Release: v1.13
46			Testname: Kubelet, log output, default
47			Description: By default the stdout and stderr from the process being executed in a pod MUST be sent to the pod's logs.
48		*/
49		framework.ConformanceIt("should print the output to logs [NodeConformance]", func() {
50			podClient.CreateSync(&v1.Pod{
51				ObjectMeta: metav1.ObjectMeta{
52					Name: podName,
53				},
54				Spec: v1.PodSpec{
55					// Don't restart the Pod since it is expected to exit
56					RestartPolicy: v1.RestartPolicyNever,
57					Containers: []v1.Container{
58						{
59							Image:   framework.BusyBoxImage,
60							Name:    podName,
61							Command: []string{"sh", "-c", "echo 'Hello World' ; sleep 240"},
62						},
63					},
64				},
65			})
66			gomega.Eventually(func() string {
67				sinceTime := metav1.NewTime(time.Now().Add(time.Duration(-1 * time.Hour)))
68				rc, err := podClient.GetLogs(podName, &v1.PodLogOptions{SinceTime: &sinceTime}).Stream(context.TODO())
69				if err != nil {
70					return ""
71				}
72				defer rc.Close()
73				buf := new(bytes.Buffer)
74				buf.ReadFrom(rc)
75				return buf.String()
76			}, time.Minute, time.Second*4).Should(gomega.Equal("Hello World\n"))
77		})
78	})
79	ginkgo.Context("when scheduling a busybox command that always fails in a pod", func() {
80		var podName string
81
82		ginkgo.BeforeEach(func() {
83			podName = "bin-false" + string(uuid.NewUUID())
84			podClient.Create(&v1.Pod{
85				ObjectMeta: metav1.ObjectMeta{
86					Name: podName,
87				},
88				Spec: v1.PodSpec{
89					// Don't restart the Pod since it is expected to exit
90					RestartPolicy: v1.RestartPolicyNever,
91					Containers: []v1.Container{
92						{
93							Image:   framework.BusyBoxImage,
94							Name:    podName,
95							Command: []string{"/bin/false"},
96						},
97					},
98				},
99			})
100		})
101
102		/*
103			Release: v1.13
104			Testname: Kubelet, failed pod, terminated reason
105			Description: Create a Pod with terminated state. Pod MUST have only one container. Container MUST be in terminated state and MUST have an terminated reason.
106		*/
107		framework.ConformanceIt("should have an terminated reason [NodeConformance]", func() {
108			gomega.Eventually(func() error {
109				podData, err := podClient.Get(context.TODO(), podName, metav1.GetOptions{})
110				if err != nil {
111					return err
112				}
113				if len(podData.Status.ContainerStatuses) != 1 {
114					return fmt.Errorf("expected only one container in the pod %q", podName)
115				}
116				contTerminatedState := podData.Status.ContainerStatuses[0].State.Terminated
117				if contTerminatedState == nil {
118					return fmt.Errorf("expected state to be terminated. Got pod status: %+v", podData.Status)
119				}
120				if contTerminatedState.ExitCode == 0 || contTerminatedState.Reason == "" {
121					return fmt.Errorf("expected non-zero exitCode and non-empty terminated state reason. Got exitCode: %+v and terminated state reason: %+v", contTerminatedState.ExitCode, contTerminatedState.Reason)
122				}
123				return nil
124			}, framework.PodStartTimeout, time.Second*4).Should(gomega.BeNil())
125		})
126
127		/*
128			Release: v1.13
129			Testname: Kubelet, failed pod, delete
130			Description: Create a Pod with terminated state. This terminated pod MUST be able to be deleted.
131		*/
132		framework.ConformanceIt("should be possible to delete [NodeConformance]", func() {
133			err := podClient.Delete(context.TODO(), podName, metav1.DeleteOptions{})
134			gomega.Expect(err).To(gomega.BeNil(), fmt.Sprintf("Error deleting Pod %v", err))
135		})
136	})
137	ginkgo.Context("when scheduling a busybox Pod with hostAliases", func() {
138		podName := "busybox-host-aliases" + string(uuid.NewUUID())
139
140		/*
141			Release: v1.13
142			Testname: Kubelet, hostAliases
143			Description: Create a Pod with hostAliases and a container with command to output /etc/hosts entries. Pod's logs MUST have matching entries of specified hostAliases to the output of /etc/hosts entries.
144			Kubernetes mounts the /etc/hosts file into its containers, however, mounting individual files is not supported on Windows Containers. For this reason, this test is marked LinuxOnly.
145		*/
146		framework.ConformanceIt("should write entries to /etc/hosts [LinuxOnly] [NodeConformance]", func() {
147			podClient.CreateSync(&v1.Pod{
148				ObjectMeta: metav1.ObjectMeta{
149					Name: podName,
150				},
151				Spec: v1.PodSpec{
152					// Don't restart the Pod since it is expected to exit
153					RestartPolicy: v1.RestartPolicyNever,
154					Containers: []v1.Container{
155						{
156							Image:   framework.BusyBoxImage,
157							Name:    podName,
158							Command: []string{"/bin/sh", "-c", "cat /etc/hosts; sleep 6000"},
159						},
160					},
161					HostAliases: []v1.HostAlias{
162						{
163							IP:        "123.45.67.89",
164							Hostnames: []string{"foo", "bar"},
165						},
166					},
167				},
168			})
169
170			gomega.Eventually(func() error {
171				rc, err := podClient.GetLogs(podName, &v1.PodLogOptions{}).Stream(context.TODO())
172				if err != nil {
173					return err
174				}
175				defer rc.Close()
176				buf := new(bytes.Buffer)
177				buf.ReadFrom(rc)
178				hostsFileContent := buf.String()
179
180				if !strings.Contains(hostsFileContent, "123.45.67.89\tfoo\tbar") {
181					return fmt.Errorf("expected hosts file to contain entries from HostAliases. Got:\n%+v", hostsFileContent)
182				}
183
184				return nil
185			}, time.Minute, time.Second*4).Should(gomega.BeNil())
186		})
187	})
188	ginkgo.Context("when scheduling a read only busybox container", func() {
189		podName := "busybox-readonly-fs" + string(uuid.NewUUID())
190
191		/*
192			Release: v1.13
193			Testname: Kubelet, pod with read only root file system
194			Description: Create a Pod with security context set with ReadOnlyRootFileSystem set to true. The Pod then tries to write to the /file on the root, write operation to the root filesystem MUST fail as expected.
195			This test is marked LinuxOnly since Windows does not support creating containers with read-only access.
196		*/
197		framework.ConformanceIt("should not write to root filesystem [LinuxOnly] [NodeConformance]", func() {
198			isReadOnly := true
199			podClient.CreateSync(&v1.Pod{
200				ObjectMeta: metav1.ObjectMeta{
201					Name: podName,
202				},
203				Spec: v1.PodSpec{
204					// Don't restart the Pod since it is expected to exit
205					RestartPolicy: v1.RestartPolicyNever,
206					Containers: []v1.Container{
207						{
208							Image:   framework.BusyBoxImage,
209							Name:    podName,
210							Command: []string{"/bin/sh", "-c", "echo test > /file; sleep 240"},
211							SecurityContext: &v1.SecurityContext{
212								ReadOnlyRootFilesystem: &isReadOnly,
213							},
214						},
215					},
216				},
217			})
218			gomega.Eventually(func() string {
219				rc, err := podClient.GetLogs(podName, &v1.PodLogOptions{}).Stream(context.TODO())
220				if err != nil {
221					return ""
222				}
223				defer rc.Close()
224				buf := new(bytes.Buffer)
225				buf.ReadFrom(rc)
226				return buf.String()
227			}, time.Minute, time.Second*4).Should(gomega.Equal("/bin/sh: can't create /file: Read-only file system\n"))
228		})
229	})
230})
231