...

Source file src/k8s.io/kubernetes/test/e2e/kubectl/logs.go

Documentation: k8s.io/kubernetes/test/e2e/kubectl

     1  /*
     2  Copyright 2023 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  // OWNER = sig/cli
    18  
    19  package kubectl
    20  
    21  import (
    22  	"context"
    23  	"strconv"
    24  	"strings"
    25  	"time"
    26  
    27  	v1 "k8s.io/api/core/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/util/uuid"
    30  	clientset "k8s.io/client-go/kubernetes"
    31  	"k8s.io/kubectl/pkg/cmd/util/podcmd"
    32  	"k8s.io/kubernetes/test/e2e/framework"
    33  	e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"
    34  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    35  	e2eoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
    36  	admissionapi "k8s.io/pod-security-admission/api"
    37  
    38  	"github.com/onsi/ginkgo/v2"
    39  	"github.com/onsi/gomega"
    40  )
    41  
    42  func testingPod(name, value, defaultContainerName string) v1.Pod {
    43  	return v1.Pod{
    44  		ObjectMeta: metav1.ObjectMeta{
    45  			Name: name,
    46  			Labels: map[string]string{
    47  				"name": "foo",
    48  				"time": value,
    49  			},
    50  			Annotations: map[string]string{
    51  				podcmd.DefaultContainerAnnotationName: defaultContainerName,
    52  			},
    53  		},
    54  		Spec: v1.PodSpec{
    55  			Containers: []v1.Container{
    56  				{
    57  					Name:  "container-1",
    58  					Image: agnhostImage,
    59  					Args:  []string{"logs-generator", "--log-lines-total", "10", "--run-duration", "5s"},
    60  				},
    61  				{
    62  					Name:  defaultContainerName,
    63  					Image: agnhostImage,
    64  					Args:  []string{"logs-generator", "--log-lines-total", "20", "--run-duration", "5s"},
    65  				},
    66  			},
    67  			RestartPolicy: v1.RestartPolicyNever,
    68  		},
    69  	}
    70  }
    71  
    72  var _ = SIGDescribe("Kubectl logs", func() {
    73  	f := framework.NewDefaultFramework("kubectl-logs")
    74  	f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
    75  	defer ginkgo.GinkgoRecover()
    76  
    77  	var c clientset.Interface
    78  	var ns string
    79  	ginkgo.BeforeEach(func() {
    80  		c = f.ClientSet
    81  		ns = f.Namespace.Name
    82  	})
    83  
    84  	// Split("something\n", "\n") returns ["something", ""], so
    85  	// strip trailing newline first
    86  	lines := func(out string) []string {
    87  		return strings.Split(strings.TrimRight(out, "\n"), "\n")
    88  	}
    89  
    90  	ginkgo.Describe("logs", func() {
    91  
    92  		podName := "logs-generator"
    93  		containerName := "logs-generator"
    94  		ginkgo.BeforeEach(func() {
    95  			ginkgo.By("creating an pod")
    96  			// Agnhost image generates logs for a total of 100 lines over 20s.
    97  			e2ekubectl.RunKubectlOrDie(ns, "run", podName, "--image="+agnhostImage, "--restart=Never", podRunningTimeoutArg, "--", "logs-generator", "--log-lines-total", "100", "--run-duration", "20s")
    98  		})
    99  		ginkgo.AfterEach(func() {
   100  			e2ekubectl.RunKubectlOrDie(ns, "delete", "pod", podName)
   101  		})
   102  
   103  		/*
   104  			Release: v1.9
   105  			Testname: Kubectl, logs
   106  			Description: When a Pod is running then it MUST generate logs.
   107  			Starting a Pod should have a expected log line. Also log command options MUST work as expected and described below.
   108  				'kubectl logs -tail=1' should generate a output of one line, the last line in the log.
   109  				'kubectl --limit-bytes=1' should generate a single byte output.
   110  				'kubectl --tail=1 --timestamp should generate one line with timestamp in RFC3339 format
   111  				'kubectl --since=1s' should output logs that are only 1 second older from now
   112  				'kubectl --since=24h' should output logs that are only 1 day older from now
   113  		*/
   114  		framework.ConformanceIt("should be able to retrieve and filter logs", func(ctx context.Context) {
   115  
   116  			ginkgo.By("Waiting for log generator to start.")
   117  			if !e2epod.CheckPodsRunningReadyOrSucceeded(ctx, c, ns, []string{podName}, framework.PodStartTimeout) {
   118  				framework.Failf("Pod %s was not ready", podName)
   119  			}
   120  
   121  			ginkgo.By("checking for a matching strings")
   122  			_, err := e2eoutput.LookForStringInLog(ns, podName, containerName, "/api/v1/namespaces/kube-system", framework.PodStartTimeout)
   123  			framework.ExpectNoError(err)
   124  
   125  			ginkgo.By("limiting log lines")
   126  			out := e2ekubectl.RunKubectlOrDie(ns, "logs", podName, containerName, "--tail=1")
   127  			framework.Logf("got output %q", out)
   128  			gomega.Expect(out).NotTo(gomega.BeEmpty())
   129  			gomega.Expect(lines(out)).To(gomega.HaveLen(1))
   130  
   131  			ginkgo.By("limiting log bytes")
   132  			out = e2ekubectl.RunKubectlOrDie(ns, "logs", podName, containerName, "--limit-bytes=1")
   133  			framework.Logf("got output %q", out)
   134  			gomega.Expect(lines(out)).To(gomega.HaveLen(1))
   135  			gomega.Expect(out).To(gomega.HaveLen(1))
   136  
   137  			ginkgo.By("exposing timestamps")
   138  			out = e2ekubectl.RunKubectlOrDie(ns, "logs", podName, containerName, "--tail=1", "--timestamps")
   139  			framework.Logf("got output %q", out)
   140  			l := lines(out)
   141  			gomega.Expect(l).To(gomega.HaveLen(1))
   142  			words := strings.Split(l[0], " ")
   143  			gomega.Expect(len(words)).To(gomega.BeNumerically(">", 1))
   144  			if _, err := time.Parse(time.RFC3339Nano, words[0]); err != nil {
   145  				if _, err := time.Parse(time.RFC3339, words[0]); err != nil {
   146  					framework.Failf("expected %q to be RFC3339 or RFC3339Nano", words[0])
   147  				}
   148  			}
   149  
   150  			ginkgo.By("restricting to a time range")
   151  			// Note: we must wait at least two seconds,
   152  			// because the granularity is only 1 second and
   153  			// it could end up rounding the wrong way.
   154  			time.Sleep(2500 * time.Millisecond) // ensure that startup logs on the node are seen as older than 1s
   155  			recentOut := e2ekubectl.RunKubectlOrDie(ns, "logs", podName, containerName, "--since=1s")
   156  			recent := len(strings.Split(recentOut, "\n"))
   157  			olderOut := e2ekubectl.RunKubectlOrDie(ns, "logs", podName, containerName, "--since=24h")
   158  			older := len(strings.Split(olderOut, "\n"))
   159  			gomega.Expect(recent).To(gomega.BeNumerically("<", older), "expected recent(%v) to be less than older(%v)\nrecent lines:\n%v\nolder lines:\n%v\n", recent, older, recentOut, olderOut)
   160  		})
   161  	})
   162  
   163  	ginkgo.Describe("default container logs", func() {
   164  		ginkgo.Describe("the second container is the default-container by annotation", func() {
   165  			var pod *v1.Pod
   166  			podName := "pod" + string(uuid.NewUUID())
   167  			defaultContainerName := "container-2"
   168  			ginkgo.BeforeEach(func(ctx context.Context) {
   169  				podClient := f.ClientSet.CoreV1().Pods(ns)
   170  				ginkgo.By("constructing the pod")
   171  				value := strconv.Itoa(time.Now().Nanosecond())
   172  				podCopy := testingPod(podName, value, defaultContainerName)
   173  				pod = &podCopy
   174  				ginkgo.By("creating the pod")
   175  				_, err := podClient.Create(ctx, pod, metav1.CreateOptions{})
   176  				if err != nil {
   177  					framework.Failf("Failed to create pod: %v", err)
   178  				}
   179  			})
   180  			ginkgo.AfterEach(func() {
   181  				e2ekubectl.RunKubectlOrDie(ns, "delete", "pod", podName)
   182  			})
   183  
   184  			ginkgo.It("should log default container if not specified", func(ctx context.Context) {
   185  				ginkgo.By("Waiting for log generator to start.")
   186  				// we need to wait for pod completion, to check the generated number of lines
   187  				if err := e2epod.WaitForPodSuccessInNamespaceTimeout(ctx, c, podName, ns, framework.PodStartTimeout); err != nil {
   188  					framework.Failf("Pod %s did not finish: %v", podName, err)
   189  				}
   190  
   191  				ginkgo.By("specified container log lines")
   192  				out := e2ekubectl.RunKubectlOrDie(ns, "logs", podName, "-c", "container-1")
   193  				framework.Logf("got output %q", out)
   194  				gomega.Expect(out).NotTo(gomega.BeEmpty())
   195  				gomega.Expect(lines(out)).To(gomega.HaveLen(10))
   196  
   197  				ginkgo.By("log all containers log lines")
   198  				out = e2ekubectl.RunKubectlOrDie(ns, "logs", podName, "--all-containers")
   199  				framework.Logf("got output %q", out)
   200  				gomega.Expect(out).NotTo(gomega.BeEmpty())
   201  				gomega.Expect(lines(out)).To(gomega.HaveLen(30))
   202  
   203  				ginkgo.By("default container logs")
   204  				out = e2ekubectl.RunKubectlOrDie(ns, "logs", podName)
   205  				framework.Logf("got output %q", out)
   206  				gomega.Expect(lines(out)).To(gomega.HaveLen(20))
   207  			})
   208  		})
   209  	})
   210  
   211  })
   212  

View as plain text