k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/kubectl/logs.go (about) 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 "fmt" 24 "strconv" 25 "strings" 26 "time" 27 28 "github.com/onsi/ginkgo/v2" 29 "github.com/onsi/gomega" 30 31 appsv1 "k8s.io/api/apps/v1" 32 v1 "k8s.io/api/core/v1" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 "k8s.io/apimachinery/pkg/util/uuid" 35 clientset "k8s.io/client-go/kubernetes" 36 "k8s.io/kubectl/pkg/cmd/util/podcmd" 37 "k8s.io/kubernetes/test/e2e/framework" 38 e2edeployment "k8s.io/kubernetes/test/e2e/framework/deployment" 39 e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl" 40 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 41 e2eoutput "k8s.io/kubernetes/test/e2e/framework/pod/output" 42 imageutils "k8s.io/kubernetes/test/utils/image" 43 admissionapi "k8s.io/pod-security-admission/api" 44 ) 45 46 func testingDeployment(name, ns string, numberOfPods int32) appsv1.Deployment { 47 return appsv1.Deployment{ 48 ObjectMeta: metav1.ObjectMeta{ 49 Name: name, 50 Namespace: ns, 51 Labels: map[string]string{ 52 "name": name, 53 }, 54 }, 55 Spec: appsv1.DeploymentSpec{ 56 Replicas: &numberOfPods, 57 Selector: &metav1.LabelSelector{ 58 MatchLabels: map[string]string{ 59 "name": name, 60 }, 61 }, 62 Template: v1.PodTemplateSpec{ 63 ObjectMeta: metav1.ObjectMeta{ 64 Labels: map[string]string{ 65 "name": name, 66 }, 67 Annotations: map[string]string{ 68 podcmd.DefaultContainerAnnotationName: "container-2", 69 }, 70 }, 71 Spec: v1.PodSpec{ 72 Containers: []v1.Container{ 73 { 74 Name: "container-1", 75 Image: imageutils.GetE2EImage(imageutils.Agnhost), 76 Args: []string{"logs-generator", "--log-lines-total", "10", "--run-duration", "5s"}, 77 }, 78 { 79 Name: "container-2", 80 Image: imageutils.GetE2EImage(imageutils.Agnhost), 81 Args: []string{"logs-generator", "--log-lines-total", "20", "--run-duration", "5s"}, 82 }, 83 }, 84 RestartPolicy: v1.RestartPolicyAlways, 85 }, 86 }, 87 }, 88 } 89 } 90 91 func testingPod(name, value, defaultContainerName string) v1.Pod { 92 return v1.Pod{ 93 ObjectMeta: metav1.ObjectMeta{ 94 Name: name, 95 Labels: map[string]string{ 96 "name": "foo", 97 "time": value, 98 }, 99 Annotations: map[string]string{ 100 podcmd.DefaultContainerAnnotationName: defaultContainerName, 101 }, 102 }, 103 Spec: v1.PodSpec{ 104 Containers: []v1.Container{ 105 { 106 Name: "container-1", 107 Image: imageutils.GetE2EImage(imageutils.Agnhost), 108 Args: []string{"logs-generator", "--log-lines-total", "10", "--run-duration", "5s"}, 109 }, 110 { 111 Name: defaultContainerName, 112 Image: imageutils.GetE2EImage(imageutils.Agnhost), 113 Args: []string{"logs-generator", "--log-lines-total", "20", "--run-duration", "5s"}, 114 }, 115 }, 116 RestartPolicy: v1.RestartPolicyNever, 117 }, 118 } 119 } 120 121 var _ = SIGDescribe("Kubectl logs", func() { 122 f := framework.NewDefaultFramework("kubectl-logs") 123 f.NamespacePodSecurityLevel = admissionapi.LevelBaseline 124 defer ginkgo.GinkgoRecover() 125 126 var c clientset.Interface 127 var ns string 128 ginkgo.BeforeEach(func() { 129 c = f.ClientSet 130 ns = f.Namespace.Name 131 }) 132 133 // Split("something\n", "\n") returns ["something", ""], so 134 // strip trailing newline first 135 lines := func(out string) []string { 136 return strings.Split(strings.TrimRight(out, "\n"), "\n") 137 } 138 139 ginkgo.Describe("logs", func() { 140 141 podName := "logs-generator" 142 containerName := "logs-generator" 143 ginkgo.BeforeEach(func() { 144 ginkgo.By("creating a pod") 145 // Agnhost image generates logs for a total of 100 lines over 20s. 146 e2ekubectl.RunKubectlOrDie(ns, "run", podName, "--image="+imageutils.GetE2EImage(imageutils.Agnhost), "--restart=Never", podRunningTimeoutArg, "--", "logs-generator", "--log-lines-total", "100", "--run-duration", "20s") 147 }) 148 ginkgo.AfterEach(func() { 149 e2ekubectl.RunKubectlOrDie(ns, "delete", "pod", podName) 150 }) 151 152 /* 153 Release: v1.9 154 Testname: Kubectl, logs 155 Description: When a Pod is running then it MUST generate logs. 156 Starting a Pod should have a expected log line. Also log command options MUST work as expected and described below. 157 'kubectl logs -tail=1' should generate a output of one line, the last line in the log. 158 'kubectl --limit-bytes=1' should generate a single byte output. 159 'kubectl --tail=1 --timestamp should generate one line with timestamp in RFC3339 format 160 'kubectl --since=1s' should output logs that are only 1 second older from now 161 'kubectl --since=24h' should output logs that are only 1 day older from now 162 */ 163 framework.ConformanceIt("should be able to retrieve and filter logs", func(ctx context.Context) { 164 165 ginkgo.By("Waiting for log generator to start.") 166 if !e2epod.CheckPodsRunningReadyOrSucceeded(ctx, c, ns, []string{podName}, framework.PodStartTimeout) { 167 framework.Failf("Pod %s was not ready", podName) 168 } 169 170 ginkgo.By("checking for a matching strings") 171 _, err := e2eoutput.LookForStringInLog(ns, podName, containerName, "/api/v1/namespaces/kube-system", framework.PodStartTimeout) 172 framework.ExpectNoError(err) 173 174 ginkgo.By("limiting log lines") 175 out := e2ekubectl.RunKubectlOrDie(ns, "logs", podName, containerName, "--tail=1") 176 framework.Logf("got output %q", out) 177 gomega.Expect(out).NotTo(gomega.BeEmpty()) 178 gomega.Expect(lines(out)).To(gomega.HaveLen(1)) 179 180 ginkgo.By("limiting log bytes") 181 out = e2ekubectl.RunKubectlOrDie(ns, "logs", podName, containerName, "--limit-bytes=1") 182 framework.Logf("got output %q", out) 183 gomega.Expect(lines(out)).To(gomega.HaveLen(1)) 184 gomega.Expect(out).To(gomega.HaveLen(1)) 185 186 ginkgo.By("exposing timestamps") 187 out = e2ekubectl.RunKubectlOrDie(ns, "logs", podName, containerName, "--tail=1", "--timestamps") 188 framework.Logf("got output %q", out) 189 l := lines(out) 190 gomega.Expect(l).To(gomega.HaveLen(1)) 191 words := strings.Split(l[0], " ") 192 gomega.Expect(len(words)).To(gomega.BeNumerically(">", 1)) 193 if _, err := time.Parse(time.RFC3339Nano, words[0]); err != nil { 194 if _, err := time.Parse(time.RFC3339, words[0]); err != nil { 195 framework.Failf("expected %q to be RFC3339 or RFC3339Nano", words[0]) 196 } 197 } 198 199 ginkgo.By("restricting to a time range") 200 // Note: we must wait at least two seconds, 201 // because the granularity is only 1 second and 202 // it could end up rounding the wrong way. 203 time.Sleep(2500 * time.Millisecond) // ensure that startup logs on the node are seen as older than 1s 204 recentOut := e2ekubectl.RunKubectlOrDie(ns, "logs", podName, containerName, "--since=1s") 205 recent := len(strings.Split(recentOut, "\n")) 206 olderOut := e2ekubectl.RunKubectlOrDie(ns, "logs", podName, containerName, "--since=24h") 207 older := len(strings.Split(olderOut, "\n")) 208 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) 209 }) 210 }) 211 212 ginkgo.Describe("default container logs", func() { 213 ginkgo.Describe("the second container is the default-container by annotation", func() { 214 var pod *v1.Pod 215 podName := "pod" + string(uuid.NewUUID()) 216 defaultContainerName := "container-2" 217 ginkgo.BeforeEach(func(ctx context.Context) { 218 podClient := f.ClientSet.CoreV1().Pods(ns) 219 ginkgo.By("constructing the pod") 220 value := strconv.Itoa(time.Now().Nanosecond()) 221 podCopy := testingPod(podName, value, defaultContainerName) 222 pod = &podCopy 223 ginkgo.By("creating the pod") 224 _, err := podClient.Create(ctx, pod, metav1.CreateOptions{}) 225 if err != nil { 226 framework.Failf("Failed to create pod: %v", err) 227 } 228 }) 229 ginkgo.AfterEach(func() { 230 e2ekubectl.RunKubectlOrDie(ns, "delete", "pod", podName) 231 }) 232 233 ginkgo.It("should log default container if not specified", func(ctx context.Context) { 234 ginkgo.By("Waiting for log generator to start.") 235 // we need to wait for pod completion, to check the generated number of lines 236 if err := e2epod.WaitForPodSuccessInNamespaceTimeout(ctx, c, podName, ns, framework.PodStartTimeout); err != nil { 237 framework.Failf("Pod %s did not finish: %v", podName, err) 238 } 239 240 ginkgo.By("specified container log lines") 241 out := e2ekubectl.RunKubectlOrDie(ns, "logs", podName, "-c", "container-1") 242 framework.Logf("got output %q", out) 243 gomega.Expect(out).NotTo(gomega.BeEmpty()) 244 gomega.Expect(lines(out)).To(gomega.HaveLen(10)) 245 246 ginkgo.By("log all containers log lines") 247 out = e2ekubectl.RunKubectlOrDie(ns, "logs", podName, "--all-containers") 248 framework.Logf("got output %q", out) 249 gomega.Expect(out).NotTo(gomega.BeEmpty()) 250 gomega.Expect(lines(out)).To(gomega.HaveLen(30)) 251 252 ginkgo.By("default container logs") 253 out = e2ekubectl.RunKubectlOrDie(ns, "logs", podName) 254 framework.Logf("got output %q", out) 255 gomega.Expect(lines(out)).To(gomega.HaveLen(20)) 256 }) 257 }) 258 }) 259 260 ginkgo.Describe("all pod logs", func() { 261 ginkgo.Describe("the Deployment has 2 replicas and each pod has 2 containers", func() { 262 var deploy *appsv1.Deployment 263 deployName := "deploy-" + string(uuid.NewUUID()) 264 numberReplicas := int32(2) 265 ginkgo.BeforeEach(func(ctx context.Context) { 266 deployClient := c.AppsV1().Deployments(ns) 267 ginkgo.By("constructing the Deployment") 268 deployCopy := testingDeployment(deployName, ns, numberReplicas) 269 deploy = &deployCopy 270 ginkgo.By("creating the Deployment") 271 var err error 272 deploy, err = deployClient.Create(ctx, deploy, metav1.CreateOptions{}) 273 if err != nil { 274 framework.Failf("Failed to create Deployment: %v", err) 275 } 276 277 if err = e2edeployment.WaitForDeploymentComplete(c, deploy); err != nil { 278 framework.Failf("Failed to wait for Deployment to complete: %v", err) 279 } 280 281 }) 282 283 ginkgo.AfterEach(func() { 284 e2ekubectl.RunKubectlOrDie(ns, "delete", "deploy", deployName) 285 }) 286 287 ginkgo.It("should get logs from all pods based on default container", func(ctx context.Context) { 288 ginkgo.By("Waiting for Deployment pods to be running.") 289 290 // get the pod names 291 pods, err := e2edeployment.GetPodsForDeployment(ctx, c, deploy) 292 if err != nil { 293 framework.Failf("Failed to get pods for Deployment: %v", err) 294 } 295 296 podOne := pods.Items[0].GetName() 297 podTwo := pods.Items[1].GetName() 298 299 ginkgo.By("expecting logs from both replicas in Deployment") 300 out := e2ekubectl.RunKubectlOrDie(ns, "logs", fmt.Sprintf("deploy/%s", deployName), "--all-pods") 301 framework.Logf("got output %q", out) 302 logLines := strings.Split(out, "\n") 303 logFound := false 304 for _, line := range logLines { 305 var deployPod bool 306 if line != "" { 307 if strings.Contains(line, fmt.Sprintf("[pod/%s/container-2]", podOne)) || strings.Contains(line, fmt.Sprintf("[pod/%s/container-2]", podTwo)) { 308 logFound = true 309 deployPod = true 310 } 311 gomega.Expect(deployPod).To(gomega.BeTrueBecause("each log should be from the default container from each pod in the Deployment")) 312 } 313 314 } 315 gomega.Expect(logFound).To(gomega.BeTrueBecause("log should be present")) 316 }) 317 318 ginkgo.It("should get logs from each pod and each container in Deployment", func(ctx context.Context) { 319 ginkgo.By("Waiting for Deployment pods to be running.") 320 321 pods, err := e2edeployment.GetPodsForDeployment(ctx, c, deploy) 322 if err != nil { 323 framework.Failf("Failed to get pods for Deployment: %v", err) 324 } 325 326 podOne := pods.Items[0].GetName() 327 podTwo := pods.Items[1].GetName() 328 329 ginkgo.By("all containers and all containers") 330 out := e2ekubectl.RunKubectlOrDie(ns, "logs", fmt.Sprintf("deploy/%s", deployName), "--all-pods", "--all-containers") 331 framework.Logf("got output %q", out) 332 logLines := strings.Split(out, "\n") 333 logFound := false 334 for _, line := range logLines { 335 if line != "" { 336 var deployPodContainer bool 337 if strings.Contains(line, fmt.Sprintf("[pod/%s/container-1]", podOne)) || strings.Contains(line, fmt.Sprintf("[pod/%s/container-1]", podTwo)) || strings.Contains(line, fmt.Sprintf("[pod/%s/container-2]", podOne)) || strings.Contains(line, fmt.Sprintf("[pod/%s/container-2]", podTwo)) { 338 logFound = true 339 deployPodContainer = true 340 } 341 gomega.Expect(deployPodContainer).To(gomega.BeTrueBecause("each log should be from all containers from all pods in the Deployment")) 342 } 343 } 344 gomega.Expect(logFound).To(gomega.BeTrueBecause("log should be present")) 345 gomega.Expect(strings.Contains(out, fmt.Sprintf("[pod/%s/container-1]", podOne))).To(gomega.BeTrueBecause("pod 1 container 1 log should be present")) 346 gomega.Expect(strings.Contains(out, fmt.Sprintf("[pod/%s/container-2]", podOne))).To(gomega.BeTrueBecause("pod 1 container 2 log should be present")) 347 gomega.Expect(strings.Contains(out, fmt.Sprintf("[pod/%s/container-1]", podTwo))).To(gomega.BeTrueBecause("pod 2 container 1 log should be present")) 348 gomega.Expect(strings.Contains(out, fmt.Sprintf("[pod/%s/container-2]", podTwo))).To(gomega.BeTrueBecause("pod 2 container 2 log should be present")) 349 gomega.Expect(out).NotTo(gomega.BeEmpty()) 350 351 }) 352 353 }) 354 }) 355 356 })