Skip to content

Commit 1ab6360

Browse files
authored
Merge pull request kubernetes#84524 from johnSchnake/customReporter
Print progress updates to stdout and publish to URL
2 parents 709a0c4 + ed1d527 commit 1ab6360

File tree

5 files changed

+192
-1
lines changed

5 files changed

+192
-1
lines changed

test/e2e/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ go_library(
7676
"//test/e2e/framework/providers/vsphere:go_default_library",
7777
"//test/e2e/framework/testfiles:go_default_library",
7878
"//test/e2e/manifest:go_default_library",
79+
"//test/e2e/reporters:go_default_library",
7980
"//test/utils:go_default_library",
8081
"//vendor/github.com/onsi/ginkgo:go_default_library",
8182
"//vendor/github.com/onsi/ginkgo/config:go_default_library",
@@ -113,6 +114,7 @@ filegroup(
113114
"//test/e2e/network:all-srcs",
114115
"//test/e2e/node:all-srcs",
115116
"//test/e2e/perftype:all-srcs",
117+
"//test/e2e/reporters:all-srcs",
116118
"//test/e2e/scheduling:all-srcs",
117119
"//test/e2e/servicecatalog:all-srcs",
118120
"//test/e2e/storage:all-srcs",

test/e2e/e2e.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import (
4141
e2enode "k8s.io/kubernetes/test/e2e/framework/node"
4242
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
4343
"k8s.io/kubernetes/test/e2e/manifest"
44+
e2ereporters "k8s.io/kubernetes/test/e2e/reporters"
4445
testutils "k8s.io/kubernetes/test/utils"
4546
utilnet "k8s.io/utils/net"
4647

@@ -100,8 +101,11 @@ func RunE2ETests(t *testing.T) {
100101
r = append(r, reporters.NewJUnitReporter(path.Join(framework.TestContext.ReportDir, fmt.Sprintf("junit_%v%02d.xml", framework.TestContext.ReportPrefix, config.GinkgoConfig.ParallelNode))))
101102
}
102103
}
103-
klog.Infof("Starting e2e run %q on Ginkgo node %d", framework.RunID, config.GinkgoConfig.ParallelNode)
104104

105+
// Stream the progress to stdout and optionally a URL accepting progress updates.
106+
r = append(r, e2ereporters.NewProgressReporter(framework.TestContext.ProgressReportURL))
107+
108+
klog.Infof("Starting e2e run %q on Ginkgo node %d", framework.RunID, config.GinkgoConfig.ParallelNode)
105109
ginkgo.RunSpecsWithDefaultAndCustomReporters(t, "Kubernetes e2e suite", r)
106110
}
107111

test/e2e/framework/test_context.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@ type TestContextType struct {
166166

167167
// NonblockingTaints is the comma-delimeted string given by the user to specify taints which should not stop the test framework from running tests.
168168
NonblockingTaints string
169+
170+
// ProgressReportURL is the URL which progress updates will be posted to as tests complete. If empty, no updates are sent.
171+
ProgressReportURL string
169172
}
170173

171174
// NodeKillerConfig describes configuration of NodeKiller -- a utility to
@@ -292,6 +295,8 @@ func RegisterCommonFlags(flags *flag.FlagSet) {
292295

293296
flags.BoolVar(&TestContext.ListImages, "list-images", false, "If true, will show list of images used for runnning tests.")
294297
flags.StringVar(&TestContext.KubectlPath, "kubectl-path", "kubectl", "The kubectl binary to use. For development, you might use 'cluster/kubectl.sh' here.")
298+
299+
flags.StringVar(&TestContext.ProgressReportURL, "progress-report-url", "", "The URL to POST progress updates to as the suite runs to assist in aiding integrations. If empty, no messages sent.")
295300
}
296301

297302
// RegisterClusterFlags registers flags specific to the cluster e2e test suite.

test/e2e/reporters/BUILD

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library")
2+
3+
go_library(
4+
name = "go_default_library",
5+
srcs = ["progress.go"],
6+
importpath = "k8s.io/kubernetes/test/e2e/reporters",
7+
visibility = ["//visibility:public"],
8+
deps = [
9+
"//vendor/github.com/onsi/ginkgo/config:go_default_library",
10+
"//vendor/github.com/onsi/ginkgo/types:go_default_library",
11+
"//vendor/k8s.io/klog:go_default_library",
12+
],
13+
)
14+
15+
filegroup(
16+
name = "package-srcs",
17+
srcs = glob(["**"]),
18+
tags = ["automanaged"],
19+
visibility = ["//visibility:private"],
20+
)
21+
22+
filegroup(
23+
name = "all-srcs",
24+
srcs = [":package-srcs"],
25+
tags = ["automanaged"],
26+
visibility = ["//visibility:public"],
27+
)

test/e2e/reporters/progress.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*
2+
Copyright 2019 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 reporters
18+
19+
import (
20+
"bytes"
21+
"encoding/json"
22+
"fmt"
23+
"io/ioutil"
24+
"net/http"
25+
"strings"
26+
"time"
27+
28+
"k8s.io/klog"
29+
30+
"github.com/onsi/ginkgo/config"
31+
"github.com/onsi/ginkgo/types"
32+
)
33+
34+
// ProgressReporter is a ginkgo reporter which tracks the total number of tests to be run/passed/failed/skipped.
35+
// As new tests are completed it updates the values and prints them to stdout and optionally, sends the updates
36+
// to the configured URL.
37+
type ProgressReporter struct {
38+
LastMsg string `json:"msg"`
39+
40+
TestsTotal int `json:"total"`
41+
TestsCompleted int `json:"completed"`
42+
TestsSkipped int `json:"skipped"`
43+
TestsFailed int `json:"failed"`
44+
45+
Failures []string `json:"failures,omitempty"`
46+
47+
progressURL string
48+
client *http.Client
49+
}
50+
51+
// NewProgressReporter returns a progress reporter which posts updates to the given URL.
52+
func NewProgressReporter(progressReportURL string) *ProgressReporter {
53+
rep := &ProgressReporter{
54+
Failures: []string{},
55+
progressURL: progressReportURL,
56+
}
57+
if len(progressReportURL) > 0 {
58+
rep.client = &http.Client{
59+
Timeout: time.Second * 10,
60+
}
61+
}
62+
return rep
63+
}
64+
65+
// SpecSuiteWillBegin is invoked by ginkgo when the suite is about to start and is the first point in which we can
66+
// antipate the number of tests which will be run.
67+
func (reporter *ProgressReporter) SpecSuiteWillBegin(cfg config.GinkgoConfigType, summary *types.SuiteSummary) {
68+
reporter.TestsTotal = summary.NumberOfSpecsThatWillBeRun
69+
reporter.LastMsg = "Test Suite starting"
70+
reporter.sendUpdates()
71+
}
72+
73+
// SpecSuiteDidEnd is the last method invoked by Ginkgo after all the specs are run.
74+
func (reporter *ProgressReporter) SpecSuiteDidEnd(summary *types.SuiteSummary) {
75+
reporter.LastMsg = "Test Suite completed"
76+
reporter.sendUpdates()
77+
}
78+
79+
// SpecDidComplete is invoked by Ginkgo each time a spec is completed (including skipped specs).
80+
func (reporter *ProgressReporter) SpecDidComplete(specSummary *types.SpecSummary) {
81+
testname := strings.Join(specSummary.ComponentTexts[1:], " ")
82+
switch specSummary.State {
83+
case types.SpecStateFailed:
84+
if len(specSummary.ComponentTexts) > 0 {
85+
reporter.Failures = append(reporter.Failures, testname)
86+
} else {
87+
reporter.Failures = append(reporter.Failures, "Unknown test name")
88+
}
89+
reporter.TestsFailed++
90+
reporter.LastMsg = fmt.Sprintf("FAILED %v", testname)
91+
case types.SpecStatePassed:
92+
reporter.TestsCompleted++
93+
reporter.LastMsg = fmt.Sprintf("PASSED %v", testname)
94+
case types.SpecStateSkipped:
95+
reporter.TestsSkipped++
96+
return
97+
default:
98+
return
99+
}
100+
101+
reporter.sendUpdates()
102+
}
103+
104+
// sendUpdates serializes the current progress and prints it to stdout and also posts it to the configured endpoint if set.
105+
func (reporter *ProgressReporter) sendUpdates() {
106+
b := reporter.serialize()
107+
fmt.Println(string(b))
108+
go reporter.postProgressToURL(b)
109+
}
110+
111+
func (reporter *ProgressReporter) postProgressToURL(b []byte) {
112+
// If a progressURL and client is set/available then POST to it. Noop otherwise.
113+
if reporter.client == nil || len(reporter.progressURL) == 0 {
114+
return
115+
}
116+
117+
resp, err := reporter.client.Post(reporter.progressURL, "application/json", bytes.NewReader(b))
118+
if err != nil {
119+
klog.Errorf("Failed to post progress update to %v: %v", reporter.progressURL, err)
120+
return
121+
}
122+
if resp.StatusCode >= 400 {
123+
klog.Errorf("Unexpected response when posting progress update to %v: %v", reporter.progressURL, resp.StatusCode)
124+
if resp.Body != nil {
125+
defer resp.Body.Close()
126+
respBody, err := ioutil.ReadAll(resp.Body)
127+
if err != nil {
128+
klog.Errorf("Failed to read response body from posting progress: %v", err)
129+
return
130+
}
131+
klog.Errorf("Response body from posting progress update: %v", respBody)
132+
}
133+
134+
return
135+
}
136+
}
137+
138+
func (reporter *ProgressReporter) serialize() []byte {
139+
b, err := json.Marshal(reporter)
140+
if err != nil {
141+
return []byte(fmt.Sprintf(`{"msg":"%v", "error":"%v"}`, reporter.LastMsg, err))
142+
}
143+
return b
144+
}
145+
146+
// SpecWillRun is implemented as a noop to satisfy the reporter interface for ginkgo.
147+
func (reporter *ProgressReporter) SpecWillRun(specSummary *types.SpecSummary) {}
148+
149+
// BeforeSuiteDidRun is implemented as a noop to satisfy the reporter interface for ginkgo.
150+
func (reporter *ProgressReporter) BeforeSuiteDidRun(setupSummary *types.SetupSummary) {}
151+
152+
// AfterSuiteDidRun is implemented as a noop to satisfy the reporter interface for ginkgo.
153+
func (reporter *ProgressReporter) AfterSuiteDidRun(setupSummary *types.SetupSummary) {}

0 commit comments

Comments
 (0)