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