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  })