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