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