1
16
17 package output
18
19 import (
20 "context"
21 "fmt"
22 "strings"
23 "time"
24
25 "github.com/onsi/ginkgo/v2"
26 "github.com/onsi/gomega"
27 gomegatypes "github.com/onsi/gomega/types"
28
29 v1 "k8s.io/api/core/v1"
30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31 "k8s.io/apimachinery/pkg/labels"
32 clientset "k8s.io/client-go/kubernetes"
33 "k8s.io/kubectl/pkg/util/podutils"
34 "k8s.io/kubernetes/test/e2e/framework"
35 e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"
36 e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
37 )
38
39
40 const (
41
42 Poll = 2 * time.Second
43 )
44
45
46
47 func LookForStringInPodExec(ns, podName string, command []string, expectedString string, timeout time.Duration) (result string, err error) {
48 return LookForStringInPodExecToContainer(ns, podName, "", command, expectedString, timeout)
49 }
50
51
52
53 func LookForStringInPodExecToContainer(ns, podName, containerName string, command []string, expectedString string, timeout time.Duration) (result string, err error) {
54 return lookForString(expectedString, timeout, func() string {
55 args := []string{"exec", podName, fmt.Sprintf("--namespace=%v", ns)}
56 if len(containerName) > 0 {
57 args = append(args, fmt.Sprintf("--container=%s", containerName))
58 }
59 args = append(args, "--")
60 args = append(args, command...)
61 return e2ekubectl.RunKubectlOrDie(ns, args...)
62 })
63 }
64
65
66
67
68 func lookForString(expectedString string, timeout time.Duration, fn func() string) (result string, err error) {
69 for t := time.Now(); time.Since(t) < timeout; time.Sleep(Poll) {
70 result = fn()
71 if strings.Contains(result, expectedString) {
72 return
73 }
74 }
75 err = fmt.Errorf("Failed to find \"%s\", last result: \"%s\"", expectedString, result)
76 return
77 }
78
79
80
81 func RunHostCmd(ns, name, cmd string) (string, error) {
82 return e2ekubectl.RunKubectl(ns, "exec", name, "--", "/bin/sh", "-x", "-c", cmd)
83 }
84
85
86
87 func RunHostCmdWithFullOutput(ns, name, cmd string) (string, string, error) {
88 return e2ekubectl.RunKubectlWithFullOutput(ns, "exec", name, "--", "/bin/sh", "-x", "-c", cmd)
89 }
90
91
92 func RunHostCmdOrDie(ns, name, cmd string) string {
93 stdout, err := RunHostCmd(ns, name, cmd)
94 framework.Logf("stdout: %v", stdout)
95 framework.ExpectNoError(err)
96 return stdout
97 }
98
99
100
101
102 func RunHostCmdWithRetries(ns, name, cmd string, interval, timeout time.Duration) (string, error) {
103 start := time.Now()
104 for {
105 out, err := RunHostCmd(ns, name, cmd)
106 if err == nil {
107 return out, nil
108 }
109 if elapsed := time.Since(start); elapsed > timeout {
110 return out, fmt.Errorf("RunHostCmd still failed after %v: %w", elapsed, err)
111 }
112 framework.Logf("Waiting %v to retry failed RunHostCmd: %v", interval, err)
113 time.Sleep(interval)
114 }
115 }
116
117
118 func LookForStringInLog(ns, podName, container, expectedString string, timeout time.Duration) (result string, err error) {
119 return lookForString(expectedString, timeout, func() string {
120 return e2ekubectl.RunKubectlOrDie(ns, "logs", podName, container)
121 })
122 }
123
124
125 func LookForStringInLogWithoutKubectl(ctx context.Context, client clientset.Interface, ns string, podName string, container string, expectedString string, timeout time.Duration) (result string, err error) {
126 return lookForString(expectedString, timeout, func() string {
127 podLogs, err := e2epod.GetPodLogs(ctx, client, ns, podName, container)
128 framework.ExpectNoError(err)
129 return podLogs
130 })
131 }
132
133
134 func CreateEmptyFileOnPod(namespace string, podName string, filePath string) error {
135 _, err := e2ekubectl.RunKubectl(namespace, "exec", podName, "--", "/bin/sh", "-c", fmt.Sprintf("touch %s", filePath))
136 return err
137 }
138
139
140 func DumpDebugInfo(ctx context.Context, c clientset.Interface, ns string) {
141 sl, _ := c.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{LabelSelector: labels.Everything().String()})
142 for _, s := range sl.Items {
143 desc, _ := e2ekubectl.RunKubectl(ns, "describe", "po", s.Name)
144 framework.Logf("\nOutput of kubectl describe %v:\n%v", s.Name, desc)
145
146 l, _ := e2ekubectl.RunKubectl(ns, "logs", s.Name, "--tail=100")
147 framework.Logf("\nLast 100 log lines of %v:\n%v", s.Name, l)
148 }
149 }
150
151
152
153 func MatchContainerOutput(
154 ctx context.Context,
155 f *framework.Framework,
156 pod *v1.Pod,
157 containerName string,
158 expectedOutput []string,
159 matcher func(string, ...interface{}) gomegatypes.GomegaMatcher) error {
160 ns := pod.ObjectMeta.Namespace
161 if ns == "" {
162 ns = f.Namespace.Name
163 }
164 podClient := e2epod.PodClientNS(f, ns)
165
166 createdPod := podClient.Create(ctx, pod)
167 defer func() {
168 ginkgo.By("delete the pod")
169 podClient.DeleteSync(ctx, createdPod.Name, metav1.DeleteOptions{}, e2epod.DefaultPodDeletionTimeout)
170 }()
171
172
173 podErr := e2epod.WaitForPodSuccessInNamespaceTimeout(ctx, f.ClientSet, createdPod.Name, ns, f.Timeouts.PodStart)
174
175
176 podStatus, err := podClient.Get(ctx, createdPod.Name, metav1.GetOptions{})
177 if err != nil {
178 return fmt.Errorf("failed to get pod status: %w", err)
179 }
180
181 if podErr != nil {
182
183 _ = podutils.VisitContainers(&podStatus.Spec, podutils.AllContainers, func(c *v1.Container, containerType podutils.ContainerType) bool {
184 logs, err := e2epod.GetPodLogs(ctx, f.ClientSet, ns, podStatus.Name, c.Name)
185 if err != nil {
186 framework.Logf("Failed to get logs from node %q pod %q container %q: %v",
187 podStatus.Spec.NodeName, podStatus.Name, c.Name, err)
188 } else {
189 framework.Logf("Output of node %q pod %q container %q: %s", podStatus.Spec.NodeName, podStatus.Name, c.Name, logs)
190 }
191 return true
192 })
193 return fmt.Errorf("expected pod %q success: %v", createdPod.Name, podErr)
194 }
195
196 framework.Logf("Trying to get logs from node %s pod %s container %s: %v",
197 podStatus.Spec.NodeName, podStatus.Name, containerName, err)
198
199
200 logs, err := e2epod.GetPodLogs(ctx, f.ClientSet, ns, podStatus.Name, containerName)
201 if err != nil {
202 framework.Logf("Failed to get logs from node %q pod %q container %q. %v",
203 podStatus.Spec.NodeName, podStatus.Name, containerName, err)
204 return fmt.Errorf("failed to get logs from %s for %s: %w", podStatus.Name, containerName, err)
205 }
206
207 for _, expected := range expectedOutput {
208 m := matcher(expected)
209 matches, err := m.Match(logs)
210 if err != nil {
211 return fmt.Errorf("expected %q in container output: %w", expected, err)
212 } else if !matches {
213 return fmt.Errorf("expected %q in container output: %s", expected, m.FailureMessage(logs))
214 }
215 }
216
217 return nil
218 }
219
220
221
222
223 func TestContainerOutput(ctx context.Context, f *framework.Framework, scenarioName string, pod *v1.Pod, containerIndex int, expectedOutput []string) {
224 TestContainerOutputMatcher(ctx, f, scenarioName, pod, containerIndex, expectedOutput, gomega.ContainSubstring)
225 }
226
227
228
229
230 func TestContainerOutputRegexp(ctx context.Context, f *framework.Framework, scenarioName string, pod *v1.Pod, containerIndex int, expectedOutput []string) {
231 TestContainerOutputMatcher(ctx, f, scenarioName, pod, containerIndex, expectedOutput, gomega.MatchRegexp)
232 }
233
234
235
236
237 func TestContainerOutputMatcher(ctx context.Context, f *framework.Framework,
238 scenarioName string,
239 pod *v1.Pod,
240 containerIndex int,
241 expectedOutput []string,
242 matcher func(string, ...interface{}) gomegatypes.GomegaMatcher) {
243 ginkgo.By(fmt.Sprintf("Creating a pod to test %v", scenarioName))
244 if containerIndex < 0 || containerIndex >= len(pod.Spec.Containers) {
245 framework.Failf("Invalid container index: %d", containerIndex)
246 }
247 framework.ExpectNoError(MatchContainerOutput(ctx, f, pod, pod.Spec.Containers[containerIndex].Name, expectedOutput, matcher))
248 }
249
View as plain text