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