1
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
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
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
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
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
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
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
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
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
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 })
217
218 var _ = SIGDescribe("Kubelet with pods in a privileged namespace", func() {
219 f := framework.NewDefaultFramework("kubelet-test")
220 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
221 var podClient *e2epod.PodClient
222 ginkgo.BeforeEach(func() {
223 podClient = e2epod.NewPodClient(f)
224 })
225 ginkgo.Context("when scheduling an agnhost Pod with hostAliases and hostNetwork", func() {
226 podName := "agnhost-host-aliases" + string(uuid.NewUUID())
227
232 framework.It("should write entries to /etc/hosts when hostNetwork is enabled", f.WithNodeConformance(), func(ctx context.Context) {
233 pod := e2epod.NewAgnhostPod(f.Namespace.Name, podName, nil, nil, nil, "etc-hosts")
234 pod.Spec.HostNetwork = true
235
236 pod.Spec.RestartPolicy = v1.RestartPolicyNever
237 pod.Spec.HostAliases = []v1.HostAlias{
238 {
239 IP: "123.45.67.89",
240 Hostnames: []string{"foo", "bar"},
241 },
242 }
243
244 pod = podClient.Create(ctx, pod)
245 ginkgo.By("Waiting for pod completion")
246 err := e2epod.WaitForPodNoLongerRunningInNamespace(ctx, f.ClientSet, pod.Name, f.Namespace.Name)
247 framework.ExpectNoError(err)
248
249 rc, err := podClient.GetLogs(podName, &v1.PodLogOptions{}).Stream(ctx)
250 framework.ExpectNoError(err)
251 defer rc.Close()
252 buf := new(bytes.Buffer)
253 _, err = buf.ReadFrom(rc)
254 framework.ExpectNoError(err)
255 hostsFileContent := buf.String()
256
257 errMsg := fmt.Sprintf("expected hosts file to contain entries from HostAliases. Got:\n%+v", hostsFileContent)
258 gomega.Expect(hostsFileContent).To(gomega.ContainSubstring("123.45.67.89\tfoo\tbar"), errMsg)
259 })
260 })
261 })
262
View as plain text