Skip to content

Commit 8286a5d

Browse files
Merge pull request #67 from mfojtik/analyze-e2e
analyze-e2e: add openshift-dev only tool to help with e2e-aws artifacts analysis
2 parents c2519cc + 5b557a8 commit 8286a5d

File tree

5 files changed

+283
-0
lines changed

5 files changed

+283
-0
lines changed

cmd/openshift-dev-helpers/main.go

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"os"
55

6+
"github.com/openshift/must-gather/pkg/cmd/analyze-e2e"
67
"github.com/openshift/must-gather/pkg/cmd/audit"
78
"github.com/openshift/must-gather/pkg/cmd/certinspection"
89

@@ -52,6 +53,7 @@ func NewCmdDevHelpers(streams genericclioptions.IOStreams) *cobra.Command {
5253
cmd.AddCommand(audit.NewCmdAudit("openshift-dev-helpers", streams))
5354
cmd.AddCommand(mustgather.NewCmdRevisionStatus("openshift-dev-helpers", streams))
5455
cmd.AddCommand(certinspection.NewCmdCertInspection(streams))
56+
cmd.AddCommand(analyze_e2e.NewCmdAnalyze("openshift-dev-helpers", streams))
5557

5658
return cmd
5759
}

pkg/cmd/analyze-e2e/analyze-e2e.go

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package analyze_e2e
2+
3+
import (
4+
"fmt"
5+
"sort"
6+
7+
"github.com/spf13/cobra"
8+
9+
"k8s.io/cli-runtime/pkg/genericclioptions"
10+
11+
"github.com/openshift/must-gather/pkg/cmd/analyze-e2e/analyzers"
12+
)
13+
14+
var artifactsToAnalyzeList = map[string]Analyzer{
15+
"clusteroperators.json": &analyzers.ClusterOperatorsAnalyzer{},
16+
"pods.json": &analyzers.PodsAnalyzer{},
17+
}
18+
19+
var (
20+
analyzeExample = `
21+
# print out result of analysis for given artifacts directory in e2e run
22+
openshift-dev-helpers analyze-e2e https://gcsweb-ci.svc.ci.openshift.org/gcs/origin-ci-test/pr-logs/pull/openshift_cluster-kube-apiserver-operator/310/pull-ci-openshift-cluster-kube-apiserver-operator-master-e2e-aws/1559/artifacts/e2e-aws
23+
`
24+
)
25+
26+
type AnalyzeOptions struct {
27+
artifactsBaseURL string
28+
29+
genericclioptions.IOStreams
30+
}
31+
32+
func NewAnalyzeOptions(streams genericclioptions.IOStreams) *AnalyzeOptions {
33+
return &AnalyzeOptions{
34+
IOStreams: streams,
35+
}
36+
}
37+
38+
func NewCmdAnalyze(parentName string, streams genericclioptions.IOStreams) *cobra.Command {
39+
o := NewAnalyzeOptions(streams)
40+
41+
cmd := &cobra.Command{
42+
Use: "analyze-e2e URL [flags]",
43+
Short: "Inspects the artifacts gathered during e2e-aws run and analyze them.",
44+
Example: fmt.Sprintf(analyzeExample, parentName),
45+
SilenceUsage: true,
46+
RunE: func(c *cobra.Command, args []string) error {
47+
if err := o.Complete(c, args); err != nil {
48+
return err
49+
}
50+
if err := o.Validate(); err != nil {
51+
return err
52+
}
53+
if err := o.Run(); err != nil {
54+
return err
55+
}
56+
57+
return nil
58+
},
59+
}
60+
61+
return cmd
62+
}
63+
64+
func (o *AnalyzeOptions) Complete(command *cobra.Command, args []string) error {
65+
if len(args) >= 1 {
66+
o.artifactsBaseURL = args[0]
67+
}
68+
return nil
69+
}
70+
71+
func (o *AnalyzeOptions) Validate() error {
72+
if len(o.artifactsBaseURL) == 0 {
73+
return fmt.Errorf("the URL to e2e-aws artifacts must be specified")
74+
}
75+
return nil
76+
}
77+
78+
func (o *AnalyzeOptions) Run() error {
79+
results, err := GetArtifacts(o.artifactsBaseURL)
80+
if err != nil {
81+
return err
82+
}
83+
84+
sort.Slice(results, func(i, j int) bool { return results[i].ArtifactName < results[j].ArtifactName })
85+
86+
for _, result := range results {
87+
fmt.Fprintf(o.Out, "\n%s:\n\n", result.ArtifactName)
88+
if result.Error != nil {
89+
fmt.Fprintf(o.Out, "ERROR: %v\n", result.Error)
90+
continue
91+
}
92+
fmt.Fprintf(o.Out, "%s", result.Output)
93+
}
94+
fmt.Fprintf(o.Out, "\n%d files analyzed\n", len(results))
95+
return nil
96+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package analyzers
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"sort"
7+
"strings"
8+
"text/tabwriter"
9+
10+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
11+
"k8s.io/apimachinery/pkg/runtime"
12+
)
13+
14+
type ClusterOperatorsAnalyzer struct{}
15+
16+
func (*ClusterOperatorsAnalyzer) Analyze(content []byte) (string, error) {
17+
manifestObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, content)
18+
if err != nil {
19+
return "", err
20+
}
21+
manifestUnstructured := manifestObj.(*unstructured.UnstructuredList)
22+
23+
writer := &bytes.Buffer{}
24+
w := tabwriter.NewWriter(writer, 60, 0, 0, ' ', tabwriter.DiscardEmptyColumns)
25+
26+
err = manifestUnstructured.EachListItem(func(object runtime.Object) error {
27+
u := object.(*unstructured.Unstructured)
28+
conditions, _, err := unstructured.NestedSlice(u.Object, "status", "conditions")
29+
if err != nil {
30+
return err
31+
}
32+
resultConditions := []string{}
33+
for _, condition := range conditions {
34+
condType, _, err := unstructured.NestedString(condition.(map[string]interface{}), "type")
35+
if err != nil {
36+
return err
37+
}
38+
condStatus, _, err := unstructured.NestedString(condition.(map[string]interface{}), "status")
39+
if err != nil {
40+
return err
41+
}
42+
resultConditions = append(resultConditions, fmt.Sprintf("%s=%s", condType, condStatus))
43+
}
44+
sort.Strings(resultConditions)
45+
fmt.Fprintf(w, "%s\t%s\n", u.GetName(), strings.Join(resultConditions, ", "))
46+
return nil
47+
})
48+
49+
w.Flush()
50+
51+
return writer.String(), err
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package analyzers
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"strings"
7+
"text/tabwriter"
8+
9+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
10+
"k8s.io/apimachinery/pkg/runtime"
11+
)
12+
13+
type PodsAnalyzer struct{}
14+
15+
func (*PodsAnalyzer) Analyze(content []byte) (string, error) {
16+
manifestObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, content)
17+
if err != nil {
18+
return "", err
19+
}
20+
manifestUnstructured := manifestObj.(*unstructured.UnstructuredList)
21+
22+
writer := &bytes.Buffer{}
23+
w := tabwriter.NewWriter(writer, 70, 0, 0, ' ', tabwriter.DiscardEmptyColumns)
24+
25+
err = manifestUnstructured.EachListItem(func(object runtime.Object) error {
26+
u := object.(*unstructured.Unstructured)
27+
conditions, _, err := unstructured.NestedSlice(u.Object, "status", "conditions")
28+
if err != nil {
29+
return err
30+
}
31+
resultConditions := []string{}
32+
for _, condition := range conditions {
33+
condType, _, err := unstructured.NestedString(condition.(map[string]interface{}), "type")
34+
if err != nil {
35+
return err
36+
}
37+
condStatus, _, err := unstructured.NestedString(condition.(map[string]interface{}), "status")
38+
if err != nil {
39+
return err
40+
}
41+
resultConditions = append(resultConditions, fmt.Sprintf("%s=%s", condType, condStatus))
42+
}
43+
44+
resultContainers := []string{}
45+
containerStatuses, _, err := unstructured.NestedSlice(u.Object, "status", "containerStatuses")
46+
if err != nil {
47+
return err
48+
}
49+
for _, status := range containerStatuses {
50+
exitCode, exists, err := unstructured.NestedInt64(status.(map[string]interface{}), "lastState", "terminated", "exitCode")
51+
if !exists {
52+
continue
53+
}
54+
if err != nil {
55+
return err
56+
}
57+
if exitCode != 0 {
58+
message, _, err := unstructured.NestedString(status.(map[string]interface{}), "lastState", "terminated", "message")
59+
if err != nil {
60+
return err
61+
}
62+
containerName, _, err := unstructured.NestedString(status.(map[string]interface{}), "name")
63+
if err != nil {
64+
return err
65+
}
66+
resultContainers = append(resultContainers, fmt.Sprintf(" [!] Container %q exited with %d:\n %s\n", containerName, exitCode, message))
67+
}
68+
}
69+
70+
fmt.Fprintf(w, "%s\t%s\n", u.GetName(), strings.Join(resultConditions, ", "))
71+
72+
if len(resultContainers) > 0 {
73+
fmt.Fprintf(w, "%s\n", strings.Join(resultContainers, ", "))
74+
}
75+
return nil
76+
})
77+
78+
w.Flush()
79+
80+
return writer.String(), err
81+
}

pkg/cmd/analyze-e2e/get_artifacts.go

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package analyze_e2e
2+
3+
import (
4+
"crypto/tls"
5+
"fmt"
6+
"io/ioutil"
7+
"net/http"
8+
"strings"
9+
)
10+
11+
type Analyzer interface {
12+
Analyze(content []byte) (string, error)
13+
}
14+
15+
type AnalyzeResult struct {
16+
ArtifactName string
17+
Output string
18+
Error error
19+
}
20+
21+
func GetArtifacts(baseURL string) ([]AnalyzeResult, error) {
22+
out := []AnalyzeResult{}
23+
for name, analyzer := range artifactsToAnalyzeList {
24+
tr := &http.Transport{
25+
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
26+
}
27+
client := &http.Client{Transport: tr}
28+
artifactFileName := getArtifactStorageURL(baseURL, name)
29+
response, err := client.Get(artifactFileName)
30+
if err != nil {
31+
return nil, err
32+
}
33+
defer func() {
34+
if err := response.Body.Close(); err != nil {
35+
}
36+
}()
37+
if response.StatusCode != http.StatusOK {
38+
return nil, fmt.Errorf("failed to get %q, HTTP code: %d", artifactFileName, response.StatusCode)
39+
}
40+
content, err := ioutil.ReadAll(response.Body)
41+
if err != nil {
42+
return nil, err
43+
}
44+
result, err := analyzer.Analyze(content)
45+
out = append(out, AnalyzeResult{ArtifactName: name, Output: result, Error: err})
46+
}
47+
return out, nil
48+
}
49+
50+
func getArtifactStorageURL(baseURL, artifactName string) string {
51+
return strings.TrimSuffix(strings.Replace(baseURL, "gcsweb-ci.svc.ci.openshift.org/gcs", "storage.googleapis.com", 1), "/") + "/" + artifactName
52+
}

0 commit comments

Comments
 (0)