From 992723c2c66cc706c22f170642631fae766f491a Mon Sep 17 00:00:00 2001 From: liornoy Date: Wed, 12 Jul 2023 12:28:59 +0300 Subject: [PATCH] Utilize the k8s-reporter in the e2e tests Modify the k8sreporter/reporter.go to utilize the latest external k8s-reporter package. The new reporter still outputs the same output as before plus the CRDs: SriovOperatorConfig and SriovNetwork. It is used by the conformance and validation suites, consumes the flag "-report" as the path to where the dir "sriov_failure_report.log" will be created and hold all of the failed tests logs. Also removed the unused `-junit` flag, as we're using Ginkgo's built it --junit-report tool. Added the `-report` flag to the run-e2e-conformance.sh script. Signed-off-by: Lior Noy --- go.mod | 3 +- go.sum | 6 +- hack/run-e2e-conformance.sh | 2 +- test/conformance/test_suite_test.go | 73 ++---- test/util/k8sreporter/reporter.go | 151 +++--------- test/validation/test_suite_test.go | 63 +++-- .../Masterminds/semver/v3/.golangci.yml | 3 + .../Mellanox/sriovnet/.golangci.yml | 4 + .../go-openapi/jsonreference/.golangci.yml | 3 + .../openshift-kni/k8sreporter/LICENSE | 201 +++++++++++++++ .../openshift-kni/k8sreporter/README.md | 62 +++++ .../openshift-kni/k8sreporter/client.go | 51 ++++ .../openshift-kni/k8sreporter/reporter.go | 232 ++++++++++++++++++ vendor/github.com/spf13/cobra/.golangci.yml | 4 + vendor/modules.txt | 5 +- 15 files changed, 656 insertions(+), 207 deletions(-) create mode 100644 vendor/github.com/openshift-kni/k8sreporter/LICENSE create mode 100644 vendor/github.com/openshift-kni/k8sreporter/README.md create mode 100644 vendor/github.com/openshift-kni/k8sreporter/client.go create mode 100644 vendor/github.com/openshift-kni/k8sreporter/reporter.go diff --git a/go.mod b/go.mod index 2a401e50c..8ad943e7a 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/k8snetworkplumbingwg/sriov-network-device-plugin v0.0.0-20221127172732-a5a7395122e3 github.com/onsi/ginkgo/v2 v2.9.5 github.com/onsi/gomega v1.27.7 + github.com/openshift-kni/k8sreporter v1.0.4 github.com/openshift/api v0.0.0-20221220162201-efeef9d83325 github.com/openshift/client-go v0.0.0-20220831193253-4950ae70c8ea github.com/openshift/machine-config-operator v0.0.1-0.20230118083703-fc27a2bdaa85 @@ -35,7 +36,7 @@ require ( k8s.io/client-go v0.27.4 k8s.io/code-generator v0.27.4 k8s.io/kubectl v0.27.4 - k8s.io/utils v0.0.0-20230209194617-a36077c30491 + k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 sigs.k8s.io/controller-runtime v0.15.2 ) diff --git a/go.sum b/go.sum index efbb35220..f4abcecfe 100644 --- a/go.sum +++ b/go.sum @@ -354,6 +354,8 @@ github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= +github.com/openshift-kni/k8sreporter v1.0.4 h1:jEwX6Pqei60kO1U0JLo+ePjQaP7DNn/M6d63KCS2tS0= +github.com/openshift-kni/k8sreporter v1.0.4/go.mod h1:fg8HI9yxiKAi6UzR6NTtrmQmA2WKzUqmkRUHwQ1+Bj8= github.com/openshift/api v0.0.0-20221220162201-efeef9d83325 h1:tUmCk1IW44nT8YjgNCFa6r8lq/jlRrsfb8PLcFEsyb8= github.com/openshift/api v0.0.0-20221220162201-efeef9d83325/go.mod h1:OW9hi5XDXOQWm/kRqUww6RVxZSf0nqrS4heerSmHBC4= github.com/openshift/client-go v0.0.0-20220831193253-4950ae70c8ea h1:7JbjIzWt3Q75ErY1PAZ+gCA+bErI6HSlpffHFmMMzqM= @@ -862,8 +864,8 @@ k8s.io/kubectl v0.27.4 h1:RV1TQLIbtL34+vIM+W7HaS3KfAbqvy9lWn6pWB9els4= k8s.io/kubectl v0.27.4/go.mod h1:qtc1s3BouB9KixJkriZMQqTsXMc+OAni6FeKAhq7q14= k8s.io/kubelet v0.25.1 h1:FBGOmIM4qR4Ov+RU90VXnxO/hvvniUMTGUriVOa9FfY= k8s.io/kubelet v0.25.1/go.mod h1:mXo8HjxCrwVduGBk4tzuhegJYPvNwPyycRf39H4KKqE= -k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= -k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 h1:xMMXJlJbsU8w3V5N2FLDQ8YgU8s1EoULdbQBcAeNJkY= +k8s.io/utils v0.0.0-20230313181309-38a27ef9d749/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/hack/run-e2e-conformance.sh b/hack/run-e2e-conformance.sh index 01eb254bf..bc4a45950 100755 --- a/hack/run-e2e-conformance.sh +++ b/hack/run-e2e-conformance.sh @@ -18,4 +18,4 @@ GOPATH="${GOPATH:-~/go}" JUNIT_OUTPUT="${JUNIT_OUTPUT:-/tmp/artifacts}" export PATH=$PATH:$GOPATH/bin -GOFLAGS=-mod=vendor ginkgo -output-dir=$JUNIT_OUTPUT --junit-report "unit_report.xml" "$SUITE" +GOFLAGS=-mod=vendor ginkgo -output-dir=$JUNIT_OUTPUT --junit-report "unit_report.xml" "$SUITE" -- -report=$JUNIT_OUTPUT diff --git a/test/conformance/test_suite_test.go b/test/conformance/test_suite_test.go index b43938ede..0bda0c0e4 100644 --- a/test/conformance/test_suite_test.go +++ b/test/conformance/test_suite_test.go @@ -2,81 +2,60 @@ package conformance import ( "flag" - "fmt" - "os" + "log" "path" "testing" - - "github.com/k8snetworkplumbingwg/sriov-network-operator/test/util/clean" + "time" . "github.com/onsi/ginkgo/v2" - "github.com/onsi/ginkgo/v2/reporters" - "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" - testclient "github.com/k8snetworkplumbingwg/sriov-network-operator/test/util/client" + kniK8sReporter "github.com/openshift-kni/k8sreporter" // Test files in this package must not end with `_test.go` suffix, as they are imported as go package _ "github.com/k8snetworkplumbingwg/sriov-network-operator/test/conformance/tests" + "github.com/k8snetworkplumbingwg/sriov-network-operator/test/util/clean" "github.com/k8snetworkplumbingwg/sriov-network-operator/test/util/k8sreporter" ) var ( - junitPath *string - dumpOutput *bool - reporterFile string - customReporter *k8sreporter.KubernetesReporter + customReporter *kniK8sReporter.KubernetesReporter + err error + reportPath *string ) func init() { - dumpOutput = flag.Bool("dump", false, "dump informations for failed tests") - junitPath = flag.String("junit", "", "the path for the junit format report") + reportPath = flag.String("report", "", "the path of the report directory containing details for failed tests") } func TestTest(t *testing.T) { - RegisterFailHandler(Fail) - - reporterFile = os.Getenv("REPORTER_OUTPUT") - - clients := testclient.New("") + // We want to collect logs before any resource is deleted in AfterEach, so we register the global fail handler + // in a way such that the reporter's Dump is always called before the default Fail. + RegisterFailHandler( + func(message string, callerSkip ...int) { + if customReporter != nil { + customReporter.Dump(10*time.Minute, CurrentSpecReport().FullText()) + } + + // Ensure failing line location is not affected by this wrapper + for i := range callerSkip { + callerSkip[i]++ + } + Fail(message, callerSkip...) + }) - if reporterFile != "" { - f, err := os.OpenFile(reporterFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if *reportPath != "" { + reportFile := path.Join(*reportPath, "sriov_failure_report.log") + customReporter, err = k8sreporter.New(reportFile) if err != nil { - fmt.Fprintf(os.Stderr, "failed to open the file: %v\n", err) - return + log.Fatalf("Failed to create the k8s reporter %s", err) } - defer f.Close() - customReporter = k8sreporter.New(clients, f) - } else if *dumpOutput { - customReporter = k8sreporter.New(clients, os.Stdout) } RunSpecs(t, "SRIOV Operator conformance tests") } -var _ = ReportAfterSuite("conformance", func(report types.Report) { - if *junitPath != "" { - junitFile := path.Join(*junitPath, "junit_sriov_conformance.xml") - reporters.GenerateJUnitReportWithConfig(report, junitFile, reporters.JunitReportConfig{ - OmitTimelinesForSpecState: types.SpecStatePassed | types.SpecStateSkipped, - OmitLeafNodeType: true, - OmitSuiteSetupNodes: true, - }) - } -}) - -var _ = ReportAfterEach(func(sr types.SpecReport) { - if sr.Failed() == false { - return - } - - if reporterFile != "" || *dumpOutput { - customReporter.Report(sr) - } -}) - var _ = BeforeSuite(func() { err := clean.All() Expect(err).NotTo(HaveOccurred()) diff --git a/test/util/k8sreporter/reporter.go b/test/util/k8sreporter/reporter.go index aba84de9f..d4d9e873f 100644 --- a/test/util/k8sreporter/reporter.go +++ b/test/util/k8sreporter/reporter.go @@ -1,144 +1,53 @@ package k8sreporter import ( - "context" - "encoding/json" - "fmt" - "io" + "errors" "os" "strings" - "sync" - "github.com/onsi/ginkgo/v2/types" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" + kniK8sReporter "github.com/openshift-kni/k8sreporter" + "k8s.io/apimachinery/pkg/runtime" sriovv1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1" - testclient "github.com/k8snetworkplumbingwg/sriov-network-operator/test/util/client" "github.com/k8snetworkplumbingwg/sriov-network-operator/test/util/namespaces" ) -type KubernetesReporter struct { - sync.Mutex - clients *testclient.ClientSet - dumpOutput io.Writer -} - -func New(clients *testclient.ClientSet, dumpDestination io.Writer) *KubernetesReporter { - return &KubernetesReporter{clients: clients, dumpOutput: dumpDestination} -} - -func (r *KubernetesReporter) Report(sr types.SpecReport) { - r.Lock() - defer r.Unlock() - fmt.Fprintln(r.dumpOutput, "Starting dump for failed spec", sr.ContainerHierarchyTexts) - r.dump() - fmt.Fprintln(r.dumpOutput, "Finished dump for failed spec") -} - -func (r *KubernetesReporter) dump() { - r.logNodes() - r.logPods("openshift-sriov-network-operator") - r.logPods(namespaces.Test) - r.logLogs(func(p *corev1.Pod) bool { - return !strings.HasPrefix(p.Name, "sriov-") - }) - r.logSriovNodeState() - r.logNetworkPolicies() -} - -func (r *KubernetesReporter) logPods(namespace string) { - fmt.Fprintf(r.dumpOutput, "Logging pods for %s", namespace) - - pods, err := r.clients.Pods(namespace).List(context.Background(), metav1.ListOptions{}) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to fetch pods: %v\n", err) - return - } - - j, err := json.MarshalIndent(pods, "", " ") - if err != nil { - fmt.Println("Failed to marshal pods", err) - return - } - fmt.Fprintln(r.dumpOutput, string(j)) -} - -func (r *KubernetesReporter) logNodes() { - fmt.Fprintf(r.dumpOutput, "Logging nodes") - - nodes, err := r.clients.CoreV1Interface.Nodes().List(context.Background(), metav1.ListOptions{}) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to fetch nodes: %v\n", err) - return - } - - j, err := json.MarshalIndent(nodes, "", " ") - if err != nil { - fmt.Println("Failed to marshal nodes") - return - } - fmt.Fprintln(r.dumpOutput, string(j)) -} - -func (r *KubernetesReporter) logLogs(filterPods func(*corev1.Pod) bool) { - fmt.Fprintf(r.dumpOutput, "Logging pods logs") - - pods, err := r.clients.Pods(corev1.NamespaceAll).List(context.Background(), metav1.ListOptions{}) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to fetch pods: %v\n", err) - return - } - - for _, pod := range pods.Items { - if filterPods(&pod) { - continue - } - for _, container := range pod.Spec.Containers { - logs, err := r.clients.Pods(pod.Namespace).GetLogs(pod.Name, &corev1.PodLogOptions{Container: container.Name}).DoRaw(context.Background()) - if err == nil { - fmt.Fprintf(r.dumpOutput, "Dumping logs for pod %s-%s-%s", pod.Namespace, pod.Name, container.Name) - fmt.Fprintln(r.dumpOutput, string(logs)) - } +func New(reportPath string) (*kniK8sReporter.KubernetesReporter, error) { + addToScheme := func(s *runtime.Scheme) error { + err := sriovv1.AddToScheme(s) + if err != nil { + return err } + return nil } -} - -func (r *KubernetesReporter) logNetworkPolicies() { - fmt.Fprintf(r.dumpOutput, "Logging network policies") - - policies := sriovv1.SriovNetworkNodePolicyList{} - err := r.clients.List(context.Background(), - &policies, - runtimeclient.InNamespace("openshift-sriov-network-operator")) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to fetch network policies: %v\n", err) - return + dumpNamespace := func(ns string) bool { + switch { + case ns == namespaces.Test: + return true + case ns == "openshift-sriov-network-operator": + return true + case strings.HasPrefix(ns, "sriov-"): + return true + } + return false } - j, err := json.MarshalIndent(policies, "", " ") - if err != nil { - fmt.Println("Failed to marshal policies") - return + crds := []kniK8sReporter.CRData{ + {Cr: &sriovv1.SriovNetworkNodeStateList{}}, + {Cr: &sriovv1.SriovNetworkNodePolicyList{}}, + {Cr: &sriovv1.SriovNetworkList{}}, + {Cr: &sriovv1.SriovOperatorConfigList{}}, } - fmt.Fprintln(r.dumpOutput, string(j)) -} -func (r *KubernetesReporter) logSriovNodeState() { - fmt.Fprintf(r.dumpOutput, "Logging node states") - - nodeStates, err := r.clients.SriovNetworkNodeStates("openshift-sriov-network-operator").List(context.Background(), metav1.ListOptions{}) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to fetch node states: %v\n", err) - return + err := os.Mkdir(reportPath, 0755) + if err != nil && !errors.Is(err, os.ErrExist) { + return nil, err } - j, err := json.MarshalIndent(nodeStates, "", " ") + reporter, err := kniK8sReporter.New("", addToScheme, dumpNamespace, reportPath, crds...) if err != nil { - fmt.Println("Failed to marshal node states") - return + return nil, err } - fmt.Fprintln(r.dumpOutput, string(j)) + return reporter, nil } diff --git a/test/validation/test_suite_test.go b/test/validation/test_suite_test.go index 861338ae6..008ebaf90 100644 --- a/test/validation/test_suite_test.go +++ b/test/validation/test_suite_test.go @@ -2,60 +2,55 @@ package conformance import ( "flag" - "fmt" - "os" + "log" + "path" "testing" + "time" . "github.com/onsi/ginkgo/v2" - "github.com/onsi/ginkgo/v2/types" . "github.com/onsi/gomega" - testclient "github.com/k8snetworkplumbingwg/sriov-network-operator/test/util/client" - - "github.com/k8snetworkplumbingwg/sriov-network-operator/test/util/k8sreporter" + kniK8sReporter "github.com/openshift-kni/k8sreporter" // Test files in this package must not end with `_test.go` suffix, as they are imported as go package _ "github.com/k8snetworkplumbingwg/sriov-network-operator/test/validation/tests" + + "github.com/k8snetworkplumbingwg/sriov-network-operator/test/util/k8sreporter" ) var ( - dumpOutput *bool - reporterFile string - customReporter *k8sreporter.KubernetesReporter + customReporter *kniK8sReporter.KubernetesReporter + err error + reportPath *string ) func init() { - dumpOutput = flag.Bool("dump", false, "dump informations for failed tests") + reportPath = flag.String("report", "", "the path of the report directory containing details for failed tests") } func TestTest(t *testing.T) { - RegisterFailHandler(Fail) - - reporterFile = os.Getenv("REPORTER_OUTPUT") - - clients := testclient.New("") - - if reporterFile != "" { - f, err := os.OpenFile(reporterFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + // We want to collect logs before any resource is deleted in AfterEach, so we register the global fail handler + // in a way such that the reporter's Dump is always called before the default Fail. + RegisterFailHandler( + func(message string, callerSkip ...int) { + if customReporter != nil { + customReporter.Dump(10*time.Minute, CurrentSpecReport().FullText()) + } + + // Ensure failing line location is not affected by this wrapper + for i := range callerSkip { + callerSkip[i]++ + } + Fail(message, callerSkip...) + }) + + if *reportPath != "" { + reportFile := path.Join(*reportPath, "sriov_failure_report.log") + customReporter, err = k8sreporter.New(reportFile) if err != nil { - fmt.Fprintf(os.Stderr, "failed to open the file: %v\n", err) - return + log.Fatalf("Failed to create the k8s reporter %s", err) } - defer f.Close() - customReporter = k8sreporter.New(clients, f) - } else if *dumpOutput { - customReporter = k8sreporter.New(clients, os.Stdout) } RunSpecs(t, "SRIOV Operator validation tests") } - -var _ = ReportAfterEach(func(sr types.SpecReport) { - if sr.Failed() == false { - return - } - - if reporterFile != "" || *dumpOutput { - customReporter.Report(sr) - } -}) diff --git a/vendor/github.com/Masterminds/semver/v3/.golangci.yml b/vendor/github.com/Masterminds/semver/v3/.golangci.yml index ac3772228..fdbdf1448 100644 --- a/vendor/github.com/Masterminds/semver/v3/.golangci.yml +++ b/vendor/github.com/Masterminds/semver/v3/.golangci.yml @@ -4,6 +4,7 @@ run: linters: disable-all: true enable: + - deadcode - dupl - errcheck - gofmt @@ -14,7 +15,9 @@ linters: - ineffassign - misspell - nakedret + - structcheck - unused + - varcheck linters-settings: gofmt: diff --git a/vendor/github.com/Mellanox/sriovnet/.golangci.yml b/vendor/github.com/Mellanox/sriovnet/.golangci.yml index c47d67705..13903829c 100644 --- a/vendor/github.com/Mellanox/sriovnet/.golangci.yml +++ b/vendor/github.com/Mellanox/sriovnet/.golangci.yml @@ -59,6 +59,7 @@ linters: disable-all: true enable: - bodyclose + - deadcode - depguard - dogsled - dupl @@ -81,13 +82,16 @@ linters: - misspell - nakedret - prealloc + - rowserrcheck - exportloopref - staticcheck + - structcheck - stylecheck - typecheck - unconvert - unparam - unused + - varcheck - whitespace issues: diff --git a/vendor/github.com/go-openapi/jsonreference/.golangci.yml b/vendor/github.com/go-openapi/jsonreference/.golangci.yml index 00708d8b5..013fc1943 100644 --- a/vendor/github.com/go-openapi/jsonreference/.golangci.yml +++ b/vendor/github.com/go-openapi/jsonreference/.golangci.yml @@ -39,9 +39,12 @@ linters: - nestif - godot - errorlint + - varcheck - interfacer + - deadcode - golint - ifshort + - structcheck - nosnakecase - varnamelen - exhaustruct diff --git a/vendor/github.com/openshift-kni/k8sreporter/LICENSE b/vendor/github.com/openshift-kni/k8sreporter/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/vendor/github.com/openshift-kni/k8sreporter/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/openshift-kni/k8sreporter/README.md b/vendor/github.com/openshift-kni/k8sreporter/README.md new file mode 100644 index 000000000..2142e63e6 --- /dev/null +++ b/vendor/github.com/openshift-kni/k8sreporter/README.md @@ -0,0 +1,62 @@ +# Kubernetes Reporter + +This is a small library to produce a readeable dump of the required information contained in a k8s cluster. + +The idea is to use it after an end to end test failure, to collect information useful for the failure. + +## Usage + +Init the reporter with the information about namespaces and the resources that requires to be dumped: + +```go + // When using custom crds, we need to add them to the scheme + addToScheme := func(s *runtime.Scheme) error { + err := sriovv1.AddToScheme(s) + if err != nil { + return err + } + err = metallbv1beta1.AddToScheme(s) + if err != nil { + return err + } + return nil + } + + // The namespaces we want to dump resources for (including pods and pod logs) + dumpNamespace := func(ns string) bool { + if strings.HasPrefix(ns, "test") { + return true + } + return false + } + + // The list of CRDs we want to dump + crds := []k8sreporter.CRData{ + {Cr: &sriovv1.SriovNetworkNodePolicyList{}}, + {Cr: &sriovv1.SriovNetworkList{}}, + {Cr: &sriovv1.SriovNetworkNodePolicyList{}}, + {Cr: &sriovv1.SriovOperatorConfigList{}}, + {Cr: &metallbv1beta1.MetalLBList{}}, + } +``` + +Create the reporter and invoke dump (note reportbase must exists): + +```go + reporter, err := k8sreporter.New(*kubeconfig, addToScheme, dumpNamespace, "/reportbase", crds...) + if err != nil { + log.Fatalf("Failed to initialize the reporter %s", err) + } + reporter.Dump(10*time.Minute, "nameofthetest") +``` + +The output will look like + +```bash +├── reportbase +│ └── test +│ ├── crs.log +│ ├── metallb-system-pods_logs.log +│ ├── metallb-system-pods_specs.log +│ └── nodes.log +``` diff --git a/vendor/github.com/openshift-kni/k8sreporter/client.go b/vendor/github.com/openshift-kni/k8sreporter/client.go new file mode 100644 index 000000000..e1aa547da --- /dev/null +++ b/vendor/github.com/openshift-kni/k8sreporter/client.go @@ -0,0 +1,51 @@ +package k8sreporter + +import ( + "fmt" + "os" + + "github.com/golang/glog" + "k8s.io/apimachinery/pkg/runtime" + appsv1client "k8s.io/client-go/kubernetes/typed/apps/v1" + corev1client "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "sigs.k8s.io/controller-runtime/pkg/client" + runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" +) + +type clientSet struct { + corev1client.CoreV1Interface + appsv1client.AppsV1Interface + runtimeclient.Client +} + +// New returns a *ClientBuilder with the given kubeconfig. +func newClient(kubeconfig string, crScheme *runtime.Scheme) (*clientSet, error) { + var config *rest.Config + var err error + + if kubeconfig == "" { + kubeconfig = os.Getenv("KUBECONFIG") + } + + if kubeconfig != "" { + glog.V(4).Infof("Loading kube client config from path %q", kubeconfig) + config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) + } else { + glog.V(4).Infof("Using in-cluster kube client config") + config, err = rest.InClusterConfig() + } + if err != nil { + return nil, fmt.Errorf("Failed to init client") + } + + clientSet := &clientSet{} + clientSet.CoreV1Interface = corev1client.NewForConfigOrDie(config) + clientSet.AppsV1Interface = appsv1client.NewForConfigOrDie(config) + + clientSet.Client, err = runtimeclient.New(config, client.Options{ + Scheme: crScheme, + }) + return clientSet, nil +} diff --git a/vendor/github.com/openshift-kni/k8sreporter/reporter.go b/vendor/github.com/openshift-kni/k8sreporter/reporter.go new file mode 100644 index 000000000..51257582b --- /dev/null +++ b/vendor/github.com/openshift-kni/k8sreporter/reporter.go @@ -0,0 +1,232 @@ +package k8sreporter + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "os" + "path" + "strings" + "sync" + "time" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" +) + +const fileSeparator = "-----------------------------------\n" + +// NamespaceFilter is a filter function to choose what namespaces to dump. +type NamespaceFilter func(string) bool + +// AddToScheme is a function for extend the reporter scheme and the CRs we are able to dump +type AddToScheme func(*runtime.Scheme) error + +// KubernetesReporter is a Ginkgo reporter that dumps info +// about configured kubernetes objects. +type KubernetesReporter struct { + sync.Mutex + clients *clientSet + reportPath string + namespaceToLog NamespaceFilter + crs []CRData +} + +// CRData represents a cr to dump +type CRData struct { + Cr runtimeclient.ObjectList + Namespace *string +} + +// New returns a new Kubernetes reporter from the given configuration. +func New(kubeconfig string, addToScheme AddToScheme, namespaceToLog NamespaceFilter, reportPath string, crs ...CRData) (*KubernetesReporter, error) { + crScheme := runtime.NewScheme() + clientgoscheme.AddToScheme(crScheme) + if err := addToScheme(crScheme); err != nil { + return nil, err + } + + clients, err := newClient(kubeconfig, crScheme) + + if err != nil { + return nil, err + } + + crsToDump := []CRData{} + if crs != nil { + crsToDump = crs[:] + } + + return &KubernetesReporter{ + clients: clients, + reportPath: reportPath, + namespaceToLog: namespaceToLog, + crs: crsToDump, + }, nil +} + +// Dump dumps the relevant crs + pod logs. +// duration represents how much in the past we need to go when fetching the pod +// logs. +// dumpSubpath is the subpath relative to reportPath where the reporter will +// dump the output. +func (r *KubernetesReporter) Dump(duration time.Duration, dumpSubpath string) { + since := time.Now().Add(-duration).Add(-5 * time.Second) + + dumpSubpath = cleanDirName(dumpSubpath) + err := os.Mkdir(path.Join(r.reportPath, dumpSubpath), 0755) + if err != nil && !errors.Is(err, os.ErrExist) { + fmt.Fprintf(os.Stderr, "failed to create test dir: %v\n", err) + return + } + r.logNodes(dumpSubpath) + r.logLogs(since, dumpSubpath) + r.logPods(dumpSubpath) + + for _, cr := range r.crs { + r.logCustomCR(cr.Cr, cr.Namespace, dumpSubpath) + } +} + +func (r *KubernetesReporter) logPods(dirName string) { + pods, err := r.clients.Pods(v1.NamespaceAll).List(context.Background(), metav1.ListOptions{}) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to fetch pods: %v\n", err) + return + } + for _, pod := range pods.Items { + if !r.namespaceToLog(pod.Namespace) { + continue + } + f, err := logFileFor(r.reportPath, dirName, pod.Namespace+"-pods_specs") + if err != nil { + fmt.Fprintf(os.Stderr, "failed to open pods_specs file: %v\n", dirName) + return + } + defer f.Close() + fmt.Fprintf(f, fileSeparator) + j, err := json.MarshalIndent(pod, "", " ") + if err != nil { + fmt.Println("Failed to marshal pods", err) + return + } + fmt.Fprintln(f, string(j)) + } +} + +func (r *KubernetesReporter) logNodes(dirName string) { + f, err := logFileFor(r.reportPath, dirName, "nodes") + if err != nil { + fmt.Fprintf(os.Stderr, "failed to open nodes file: %v\n", dirName) + return + } + defer f.Close() + fmt.Fprintf(f, fileSeparator) + + nodes, err := r.clients.Nodes().List(context.Background(), metav1.ListOptions{}) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to fetch nodes: %v\n", err) + return + } + + j, err := json.MarshalIndent(nodes, "", " ") + if err != nil { + fmt.Println("Failed to marshal nodes") + return + } + fmt.Fprintln(f, string(j)) +} + +func (r *KubernetesReporter) logLogs(since time.Time, dirName string) { + pods, err := r.clients.Pods(v1.NamespaceAll).List(context.Background(), metav1.ListOptions{}) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to fetch pods: %v\n", err) + return + } + for _, pod := range pods.Items { + if !r.namespaceToLog(pod.Namespace) { + continue + } + f, err := logFileFor(r.reportPath, dirName, pod.Namespace+"-pods_logs") + if err != nil { + fmt.Fprintf(os.Stderr, "failed to open pods_logs file: %v\n", dirName) + return + } + defer f.Close() + containersToLog := make([]v1.Container, 0) + containersToLog = append(containersToLog, pod.Spec.Containers...) + containersToLog = append(containersToLog, pod.Spec.InitContainers...) + for _, container := range containersToLog { + logStart := metav1.NewTime(since) + logs, err := r.clients.Pods(pod.Namespace).GetLogs(pod.Name, &v1.PodLogOptions{Container: container.Name, SinceTime: &logStart}).DoRaw(context.Background()) + if err == nil { + fmt.Fprintf(f, fileSeparator) + fmt.Fprintf(f, "Dumping logs for pod %s-%s-%s\n", pod.Namespace, pod.Name, container.Name) + fmt.Fprintln(f, string(logs)) + } + + logs, err = r.clients.Pods(pod.Namespace).GetLogs(pod.Name, &v1.PodLogOptions{Container: container.Name, SinceTime: &logStart, Previous: true}).DoRaw(context.Background()) + if err == nil { + fmt.Fprintf(f, fileSeparator) + fmt.Fprintf(f, "Dumping previous logs for pod %s-%s-%s\n", pod.Namespace, pod.Name, container.Name) + fmt.Fprintln(f, string(logs)) + } + } + + } +} + +func (r *KubernetesReporter) logCustomCR(cr runtimeclient.ObjectList, namespace *string, dirName string) { + f, err := logFileFor(r.reportPath, dirName, "crs") + if err != nil { + fmt.Fprintf(os.Stderr, "failed to open crs file: %v\n", dirName) + return + } + defer f.Close() + fmt.Fprintf(f, fileSeparator) + if namespace != nil { + fmt.Fprintf(f, "Dumping %T in namespace %s\n", cr, *namespace) + } else { + fmt.Fprintf(f, "Dumping %T\n", cr) + } + + options := []runtimeclient.ListOption{} + if namespace != nil { + options = append(options, runtimeclient.InNamespace(*namespace)) + } + err = r.clients.List(context.Background(), + cr, + options...) + + if err != nil { + // this can be expected if we are reporting a feature we did not install the operator for + fmt.Fprintf(f, "Failed to fetch %T: %v\n", cr, err) + return + } + + j, err := json.MarshalIndent(cr, "", " ") + if err != nil { + fmt.Fprintf(f, "Failed to marshal %T\n", cr) + return + } + fmt.Fprintln(f, string(j)) +} + +func logFileFor(dirName string, testName string, kind string) (*os.File, error) { + path := path.Join(dirName, testName, kind) + ".log" + f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return nil, err + } + return f, nil +} + +func cleanDirName(dirName string) string { + res := strings.ReplaceAll(dirName, "/", "-") + res = strings.ReplaceAll(res, " ", "_") + return res +} diff --git a/vendor/github.com/spf13/cobra/.golangci.yml b/vendor/github.com/spf13/cobra/.golangci.yml index b1cb10347..439d3e1de 100644 --- a/vendor/github.com/spf13/cobra/.golangci.yml +++ b/vendor/github.com/spf13/cobra/.golangci.yml @@ -19,6 +19,7 @@ linters: disable-all: true enable: #- bodyclose + - deadcode #- depguard #- dogsled #- dupl @@ -47,12 +48,15 @@ linters: #- nakedret #- noctx #- nolintlint + #- rowserrcheck #- scopelint #- staticcheck + - structcheck #- stylecheck #- typecheck - unconvert #- unparam #- unused + - varcheck #- whitespace fast: false diff --git a/vendor/modules.txt b/vendor/modules.txt index 60d8d6f47..5b905620c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -358,6 +358,9 @@ github.com/onsi/gomega/matchers/support/goraph/edge github.com/onsi/gomega/matchers/support/goraph/node github.com/onsi/gomega/matchers/support/goraph/util github.com/onsi/gomega/types +# github.com/openshift-kni/k8sreporter v1.0.4 +## explicit; go 1.20 +github.com/openshift-kni/k8sreporter # github.com/openshift/api v0.0.0-20221220162201-efeef9d83325 ## explicit; go 1.18 github.com/openshift/api/config/v1 @@ -1244,7 +1247,7 @@ k8s.io/kubectl/pkg/validation # k8s.io/kubelet v0.25.1 ## explicit; go 1.19 k8s.io/kubelet/pkg/apis/deviceplugin/v1beta1 -# k8s.io/utils v0.0.0-20230209194617-a36077c30491 +# k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 ## explicit; go 1.18 k8s.io/utils/buffer k8s.io/utils/clock