From 6b2e997d823a5ffbc744423591f7ade1e376691f Mon Sep 17 00:00:00 2001 From: Ming Yu Wang <90855268+mwangggg@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:39:47 -0500 Subject: [PATCH 01/16] ci(gh): add comment when /build_test is finished (#745) --- .github/workflows/test-ci-command.yml | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/.github/workflows/test-ci-command.yml b/.github/workflows/test-ci-command.yml index d8e45a85d..2e1ffce2b 100644 --- a/.github/workflows/test-ci-command.yml +++ b/.github/workflows/test-ci-command.yml @@ -95,3 +95,40 @@ jobs: ref: ${{ needs.checkout-branch.outputs.PR_head_ref }} tag: ${{ needs.get-test-image-tag.outputs.tag }} sha: ${{ needs.checkout-branch.outputs.PR_head_sha }} + + successful-test: + runs-on: ubuntu-latest + needs: [run-test-jobs] + permissions: + pull-requests: write + steps: + - name: Leave Actions Run Comment + uses: actions/github-script@v6 + with: + script: | + const runURL = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${{ github.run_id }}`; + const commentBody = `\`/build_test\` completed successfully ✅. \n[View Actions Run](${runURL}).`; + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: commentBody + }); + + cancelled-test: + if: (always() && contains(needs.*.result, 'failure')) + runs-on: ubuntu-latest + needs: [run-test-jobs] + steps: + - name: Leave Actions Run Comment + uses: actions/github-script@v6 + with: + script: | + const runURL = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${{ github.run_id }}`; + const commentBody = `\`/build_test\` : At least one test failed ❌. \n[View Actions Run](${runURL}).`; + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: commentBody + }); From 2af4362802fd7389c18e40c82ee155df61485c33 Mon Sep 17 00:00:00 2001 From: Ming Yu Wang <90855268+mwangggg@users.noreply.github.com> Date: Wed, 28 Feb 2024 14:38:40 -0500 Subject: [PATCH 02/16] add scorecard test/suite selection (#746) --- Makefile | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 897010c8a..05eb8396b 100644 --- a/Makefile +++ b/Makefile @@ -135,6 +135,14 @@ else KUSTOMIZE_DIR ?= config/default endif +# Specify which scorecard tests/suites to run +ifneq ($(SCORECARD_TEST_SELECTION),) +SCORECARD_TEST_SELECTOR := --selector='test in ($(SCORECARD_TEST_SELECTION))' +endif +ifneq ($(SCORECARD_TEST_SUITE),) +SCORECARD_TEST_SELECTOR := --selector=suite=$(SCORECARD_TEST_SUITE) +endif + ##@ General .PHONY: all @@ -159,9 +167,9 @@ endif test-scorecard: check_cert_manager kustomize operator-sdk ## Run scorecard tests. ifneq ($(SKIP_TESTS), true) $(call scorecard-setup) - $(call scorecard-cleanup); \ - trap cleanup EXIT; \ - $(OPERATOR_SDK) scorecard -n $(SCORECARD_NAMESPACE) -s cryostat-scorecard -w 20m $(BUNDLE_IMG) --pod-security=restricted + $(call scorecard-cleanup) ; \ + trap cleanup EXIT ; \ + $(OPERATOR_SDK) scorecard -n $(SCORECARD_NAMESPACE) -s cryostat-scorecard -w 20m $(BUNDLE_IMG) --pod-security=restricted $(SCORECARD_TEST_SELECTOR) endif .PHONY: clean-scorecard From cfcbfc74fab65b78bf5fb63a8c5abe658d65fe6a Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Tue, 5 Mar 2024 11:21:35 -0800 Subject: [PATCH 03/16] test(scorecard): scorecard tests for recording management (#698) * test(scorecard): scorecard tests for recording management Signed-off-by: Thuan Vo * fixup(scorecard): fix cr cleanup func * test(scorecard): registry recording test to suite * chore(scorecard): reorganize client def * chore(scorecard): clean up common setup func * chore(bundle): regenerate bundle with scorecard tag * chore(bundle): correct image tag in bundle * fix(bundle): add missing scorecard test config patch * feat(scorecard): scaffold cryostat API client * chore(scorecard): clean up API client * test(scorecard): implement recording scorecard test * fixup(scorecard): correctly add scorecard test via hack templates * fix(client): ignore unverified tls certs and base64 oauth token * chore(bundle): split cryostat tests to separate stage * fix(scorecard): extend default transport instead of overwriting * chore(scorecard): refactor client to support multi-part * fixup(client): fix request verb * fix(client): fix recording create form format * fix(scorecard): create stored credentials for target JVM * fix(scorecard): fix 502 status error * chore(scorecard): simplify client def * chore(scorecard): fetch recordings to ensure action is correctly performed * test(scorecard): test generating report for a recording * chore(scorecard): clean up * test(scorecard): list archives in tests * ci(scorecard): reconfigure ingress for kind * ci(k8s): correct cluster name * test(scorecard): use role instead of clusterrole for oauth rules * test(scorecard): parse health response for additional checks * chore(scorecard): add missing newline in logs * chore(scorecard): check status code before parsing body in health check * test(scorecard): add custom target discovery to recording scorecard test * add EOF wait and resp headers * add resp headers * chore(client): configure all clients to send safe requests * fix(clients): add missing content-type header * fix(scorecard): add missing test name in help message * chore(client): create new http requests when retrying * chore(bundle): update scorecard image tags --------- Signed-off-by: Thuan Vo Co-authored-by: Ming Yu Wang <90855268+mwangggg@users.noreply.github.com> Co-authored-by: Ming Wang --- .github/workflows/test-ci-reusable.yml | 26 +- ...yostat-operator.clusterserviceversion.yaml | 2 +- bundle/tests/scorecard/config.yaml | 15 +- config/rbac/oauth_client.yaml | 1 - config/scorecard/bases/config.yaml | 4 +- config/scorecard/patches/custom.config.yaml | 18 +- hack/custom.config.yaml.in | 14 +- .../images/custom-scorecard-tests/main.go | 4 + .../rbac/scorecard_role.yaml | 49 ++ internal/test/scorecard/clients.go | 512 ++++++++++++++++-- internal/test/scorecard/common_utils.go | 399 ++++++++++++++ internal/test/scorecard/openshift.go | 7 + internal/test/scorecard/tests.go | 367 ++++--------- internal/test/scorecard/types.go | 146 +++++ 14 files changed, 1258 insertions(+), 306 deletions(-) create mode 100644 internal/test/scorecard/common_utils.go create mode 100644 internal/test/scorecard/types.go diff --git a/.github/workflows/test-ci-reusable.yml b/.github/workflows/test-ci-reusable.yml index a1c9be76f..860971ac5 100644 --- a/.github/workflows/test-ci-reusable.yml +++ b/.github/workflows/test-ci-reusable.yml @@ -118,11 +118,29 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Kind cluster + uses: helm/kind-action@v1.8.0 + with: + config: .github/kind-config.yaml + cluster_name: ci-${{ github.run_id }} + wait: 1m + ignore_failed_clean: true + - name: Set up Ingress Controller run: | - kind create cluster --config=".github/kind-config.yaml" -n ci-${{ github.run_id }} - # Enabling Ingress + # Install nginx ingress controller kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml - kubectl rollout status -w deployment/ingress-nginx-controller -n ingress-nginx --timeout 5m + kubectl rollout status -w \ + deployment/ingress-nginx-controller \ + -n ingress-nginx --timeout 5m + + # Lower the number of worker processes + kubectl patch cm/ingress-nginx-controller \ + --type merge \ + -p '{"data":{"worker-processes":"1"}}' \ + -n ingress-nginx + + # Modify /etc/hosts to resolve hostnames + ip_address=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ci-${{ github.run_id }}-control-plane) + echo "$ip_address testing.cryostat" | sudo tee -a /etc/hosts - name: Install Operator Lifecycle Manager run: curl -sL https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.24.0/install.sh | bash -s v0.24.0 - name: Install Cert Manager @@ -140,8 +158,6 @@ jobs: SCORECARD_REGISTRY_PASSWORD="${{ secrets.GITHUB_TOKEN }}" \ BUNDLE_IMG="${{ steps.push-bundle-to-ghcr.outputs.registry-path }}" \ make test-scorecard - - name: Clean up Kind cluster - run: kind delete cluster -n ci-${{ github.run_id }} - name: Set latest commit status as ${{ job.status }} uses: myrotvorets/set-commit-status-action@master if: always() diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index e29650bae..824ee8edb 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -54,7 +54,7 @@ metadata: capabilities: Seamless Upgrades categories: Monitoring, Developer Tools containerImage: quay.io/cryostat/cryostat-operator:2.5.0-dev - createdAt: "2024-02-04T06:09:16Z" + createdAt: "2024-03-05T02:05:10Z" description: JVM monitoring and profiling tool operatorframework.io/initialization-resource: |- { diff --git a/bundle/tests/scorecard/config.yaml b/bundle/tests/scorecard/config.yaml index ba7284c76..12b393613 100644 --- a/bundle/tests/scorecard/config.yaml +++ b/bundle/tests/scorecard/config.yaml @@ -66,10 +66,11 @@ stages: storage: spec: mountPath: {} +- tests: - entrypoint: - cryostat-scorecard-tests - operator-install - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20231011144522 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240305020416 labels: suite: cryostat test: operator-install @@ -79,13 +80,23 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20231011144522 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240305020416 labels: suite: cryostat test: cryostat-cr storage: spec: mountPath: {} + - entrypoint: + - cryostat-scorecard-tests + - cryostat-recording + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240305020416 + labels: + suite: cryostat + test: cryostat-recording + storage: + spec: + mountPath: {} storage: spec: mountPath: {} diff --git a/config/rbac/oauth_client.yaml b/config/rbac/oauth_client.yaml index d8c6693c8..b1c50771e 100644 --- a/config/rbac/oauth_client.yaml +++ b/config/rbac/oauth_client.yaml @@ -3,7 +3,6 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - creationTimestamp: null name: oauth-client rules: - apiGroups: diff --git a/config/scorecard/bases/config.yaml b/config/scorecard/bases/config.yaml index c77047841..7924d1df6 100644 --- a/config/scorecard/bases/config.yaml +++ b/config/scorecard/bases/config.yaml @@ -3,5 +3,7 @@ kind: Configuration metadata: name: config stages: -- parallel: true +- parallel: true # Build-in Tests + tests: [] +- parallel: false # Cryostat Custom Tests tests: [] diff --git a/config/scorecard/patches/custom.config.yaml b/config/scorecard/patches/custom.config.yaml index 786cf388c..527eac9ad 100644 --- a/config/scorecard/patches/custom.config.yaml +++ b/config/scorecard/patches/custom.config.yaml @@ -3,22 +3,32 @@ path: /serviceaccount value: cryostat-scorecard - op: add - path: /stages/0/tests/- + path: /stages/1/tests/- value: entrypoint: - cryostat-scorecard-tests - operator-install - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20231011144522" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240305020416" labels: suite: cryostat test: operator-install - op: add - path: /stages/0/tests/- + path: /stages/1/tests/- value: entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20231011144522" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240305020416" labels: suite: cryostat test: cryostat-cr +- op: add + path: /stages/1/tests/- + value: + entrypoint: + - cryostat-scorecard-tests + - cryostat-recording + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240305020416" + labels: + suite: cryostat + test: cryostat-recording diff --git a/hack/custom.config.yaml.in b/hack/custom.config.yaml.in index d6bc3e714..487462006 100644 --- a/hack/custom.config.yaml.in +++ b/hack/custom.config.yaml.in @@ -2,7 +2,7 @@ path: /serviceaccount value: cryostat-scorecard - op: add - path: /stages/0/tests/- + path: /stages/1/tests/- value: entrypoint: - cryostat-scorecard-tests @@ -12,7 +12,7 @@ suite: cryostat test: operator-install - op: add - path: /stages/0/tests/- + path: /stages/1/tests/- value: entrypoint: - cryostat-scorecard-tests @@ -21,3 +21,13 @@ labels: suite: cryostat test: cryostat-cr +- op: add + path: /stages/1/tests/- + value: + entrypoint: + - cryostat-scorecard-tests + - cryostat-recording + image: "${CUSTOM_SCORECARD_IMG}" + labels: + suite: cryostat + test: cryostat-recording diff --git a/internal/images/custom-scorecard-tests/main.go b/internal/images/custom-scorecard-tests/main.go index 310ea1ef9..62808a82f 100644 --- a/internal/images/custom-scorecard-tests/main.go +++ b/internal/images/custom-scorecard-tests/main.go @@ -79,6 +79,7 @@ func printValidTests() []scapiv1alpha3.TestResult { str := fmt.Sprintf("valid tests for this image include: %s", strings.Join([]string{ tests.OperatorInstallTestName, tests.CryostatCRTestName, + tests.CryostatRecordingTestName, }, ",")) result.Errors = append(result.Errors, str) @@ -90,6 +91,7 @@ func validateTests(testNames []string) bool { switch testName { case tests.OperatorInstallTestName: case tests.CryostatCRTestName: + case tests.CryostatRecordingTestName: default: return false } @@ -108,6 +110,8 @@ func runTests(testNames []string, bundle *apimanifests.Bundle, namespace string, results = append(results, tests.OperatorInstallTest(bundle, namespace)) case tests.CryostatCRTestName: results = append(results, tests.CryostatCRTest(bundle, namespace, openShiftCertManager)) + case tests.CryostatRecordingTestName: + results = append(results, tests.CryostatRecordingTest(bundle, namespace, openShiftCertManager)) default: log.Fatalf("unknown test found: %s", testName) } diff --git a/internal/images/custom-scorecard-tests/rbac/scorecard_role.yaml b/internal/images/custom-scorecard-tests/rbac/scorecard_role.yaml index de76f86d7..d350e6464 100644 --- a/internal/images/custom-scorecard-tests/rbac/scorecard_role.yaml +++ b/internal/images/custom-scorecard-tests/rbac/scorecard_role.yaml @@ -53,6 +53,55 @@ rules: - cryostats/status verbs: - get +- apiGroups: + - "" + resources: + - secrets + verbs: + - get +# Permissions for default OAuth configurations +- apiGroups: + - operator.cryostat.io + resources: + - cryostats + verbs: + - create + - patch + - delete + - get +- apiGroups: + - "" + resources: + - pods + - pods/exec + - services + verbs: + - create + - patch + - delete + - get +- apiGroups: + - "" + resources: + - replicationcontrollers + - endpoints + verbs: + - get +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - get +- apiGroups: + - apps + resources: + - daemonsets + - replicasets + - statefulsets + verbs: + - get --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/internal/test/scorecard/clients.go b/internal/test/scorecard/clients.go index 2cf994c34..ffe3ad79e 100644 --- a/internal/test/scorecard/clients.go +++ b/internal/test/scorecard/clients.go @@ -16,16 +16,25 @@ package scorecard import ( "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/util/wait" + + "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" - ctrl "sigs.k8s.io/controller-runtime" ) // CryostatClientset is a Kubernetes Clientset that can also @@ -35,10 +44,15 @@ type CryostatClientset struct { operatorClient *OperatorCRDClient } +// OperatorCRDs returns a OperatorCRDClient +func (c *CryostatClientset) OperatorCRDs() *OperatorCRDClient { + return c.operatorClient +} + // NewClientset creates a CryostatClientset func NewClientset() (*CryostatClientset, error) { // Get in-cluster REST config from pod - config, err := ctrl.GetConfig() + config, err := rest.InClusterConfig() if err != nil { return nil, err } @@ -60,9 +74,19 @@ func NewClientset() (*CryostatClientset, error) { }, nil } -// OperatorCRDs returns a OperatorCRDClient -func (c *CryostatClientset) OperatorCRDs() *OperatorCRDClient { - return c.operatorClient +// OperatorCRDClient is a Kubernetes REST client for performing operations on +// Cryostat Operator custom resources +type OperatorCRDClient struct { + client *rest.RESTClient +} + +// Cryostats returns a CryostatClient configured to a specific namespace +func (c *OperatorCRDClient) Cryostats(namespace string) *CryostatClient { + return &CryostatClient{ + restClient: c.client, + namespace: namespace, + resource: "cryostats", + } } func newOperatorCRDClient(config *rest.Config) (*OperatorCRDClient, error) { @@ -75,19 +99,21 @@ func newOperatorCRDClient(config *rest.Config) (*OperatorCRDClient, error) { }, nil } -// OperatorCRDClient is a Kubernetes REST client for performing operations on -// Cryostat Operator custom resources -type OperatorCRDClient struct { - client *rest.RESTClient +func newCRDClient(config *rest.Config) (*rest.RESTClient, error) { + scheme := runtime.NewScheme() + if err := operatorv1beta1.AddToScheme(scheme); err != nil { + return nil, err + } + return newRESTClientForGV(config, scheme, &operatorv1beta1.GroupVersion) } -// Cryostats returns a CryostatClient configured to a specific namespace -func (c *OperatorCRDClient) Cryostats(namespace string) *CryostatClient { - return &CryostatClient{ - restClient: c.client, - namespace: namespace, - resource: "cryostats", - } +func newRESTClientForGV(config *rest.Config, scheme *runtime.Scheme, gv *schema.GroupVersion) (*rest.RESTClient, error) { + configCopy := *config + configCopy.GroupVersion = gv + configCopy.APIPath = "/apis" + configCopy.ContentType = runtime.ContentTypeJSON + configCopy.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: serializer.NewCodecFactory(scheme)} + return rest.RESTClientFor(&configCopy) } // CryostatClient contains methods to perform operations on @@ -118,23 +144,6 @@ func (c *CryostatClient) Delete(ctx context.Context, name string, options *metav return delete(ctx, c.restClient, c.resource, c.namespace, name, options) } -func newCRDClient(config *rest.Config) (*rest.RESTClient, error) { - scheme := runtime.NewScheme() - if err := operatorv1beta1.AddToScheme(scheme); err != nil { - return nil, err - } - return newRESTClientForGV(config, scheme, &operatorv1beta1.GroupVersion) -} - -func newRESTClientForGV(config *rest.Config, scheme *runtime.Scheme, gv *schema.GroupVersion) (*rest.RESTClient, error) { - configCopy := *config - configCopy.GroupVersion = gv - configCopy.APIPath = "/apis" - configCopy.ContentType = runtime.ContentTypeJSON - configCopy.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: serializer.NewCodecFactory(scheme)} - return rest.RESTClientFor(&configCopy) -} - func get[r runtime.Object](ctx context.Context, c rest.Interface, res string, ns string, name string, result r) (r, error) { err := c.Get(). Namespace(ns).Resource(res). @@ -162,3 +171,436 @@ func delete(ctx context.Context, c rest.Interface, res string, ns string, name s Name(name).Body(opts).Do(ctx). Error() } + +// CryostatRESTClientset contains methods to interact with +// the Cryostat API +type CryostatRESTClientset struct { + TargetClient *TargetClient + RecordingClient *RecordingClient + CredentialClient *CredentialClient +} + +func (c *CryostatRESTClientset) Targets() *TargetClient { + return c.TargetClient +} + +func (c *CryostatRESTClientset) Recordings() *RecordingClient { + return c.RecordingClient +} + +func (c *CryostatRESTClientset) Credential() *CredentialClient { + return c.CredentialClient +} + +func NewCryostatRESTClientset(base *url.URL) *CryostatRESTClientset { + commonClient := &commonCryostatRESTClient{ + Base: base, + Client: NewHttpClient(), + } + + return &CryostatRESTClientset{ + TargetClient: &TargetClient{ + commonCryostatRESTClient: commonClient, + }, + RecordingClient: &RecordingClient{ + commonCryostatRESTClient: commonClient, + }, + CredentialClient: &CredentialClient{ + commonCryostatRESTClient: commonClient, + }, + } +} + +type commonCryostatRESTClient struct { + Base *url.URL + *http.Client +} + +// Client for Cryostat Target resources +type TargetClient struct { + *commonCryostatRESTClient +} + +func (client *TargetClient) List(ctx context.Context) ([]Target, error) { + url := client.Base.JoinPath("/api/v1/targets") + header := make(http.Header) + header.Add("Accept", "*/*") + + resp, err := SendRequest(ctx, client.Client, http.MethodGet, url.String(), nil, header) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if !StatusOK(resp.StatusCode) { + return nil, fmt.Errorf("API request failed with status code: %d, response body: %s, and headers:\n%s", resp.StatusCode, ReadError(resp), ReadHeader(resp)) + } + + targets := make([]Target, 0) + err = ReadJSON(resp, &targets) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %s", err.Error()) + } + + return targets, nil +} + +func (client *TargetClient) Create(ctx context.Context, options *Target) (*Target, error) { + url := client.Base.JoinPath("/api/v2/targets") + header := make(http.Header) + header.Add("Content-Type", "application/x-www-form-urlencoded") + header.Add("Accept", "*/*") + body := options.ToFormData() + + resp, err := SendRequest(ctx, client.Client, http.MethodPost, url.String(), &body, header) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if !StatusOK(resp.StatusCode) { + return nil, fmt.Errorf("API request failed with status code: %d, response body: %s, and headers:\n%s", resp.StatusCode, ReadError(resp), ReadHeader(resp)) + } + + targetResp := &CustomTargetResponse{} + err = ReadJSON(resp, targetResp) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %s", err.Error()) + } + + return targetResp.Data.Result, nil +} + +// Client for Cryostat Recording resources +type RecordingClient struct { + *commonCryostatRESTClient +} + +func (client *RecordingClient) List(ctx context.Context, connectUrl string) ([]Recording, error) { + url := client.Base.JoinPath(fmt.Sprintf("/api/v1/targets/%s/recordings", url.PathEscape(connectUrl))) + header := make(http.Header) + header.Add("Accept", "*/*") + + resp, err := SendRequest(ctx, client.Client, http.MethodGet, url.String(), nil, header) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if !StatusOK(resp.StatusCode) { + return nil, fmt.Errorf("API request failed with status code: %d, response body: %s, and headers:\n%s", resp.StatusCode, ReadError(resp), ReadHeader(resp)) + } + + recordings := make([]Recording, 0) + err = ReadJSON(resp, &recordings) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %s", err.Error()) + } + + return recordings, nil +} + +func (client *RecordingClient) Get(ctx context.Context, connectUrl string, recordingName string) (*Recording, error) { + recordings, err := client.List(ctx, connectUrl) + if err != nil { + return nil, err + } + + for _, rec := range recordings { + if rec.Name == recordingName { + return &rec, nil + } + } + + return nil, fmt.Errorf("recording %s does not exist for target %s", recordingName, connectUrl) +} + +func (client *RecordingClient) Create(ctx context.Context, connectUrl string, options *RecordingCreateOptions) (*Recording, error) { + url := client.Base.JoinPath(fmt.Sprintf("/api/v1/targets/%s/recordings", url.PathEscape(connectUrl))) + body := options.ToFormData() + header := make(http.Header) + header.Add("Content-Type", "application/x-www-form-urlencoded") + header.Add("Accept", "*/*") + + resp, err := SendRequest(ctx, client.Client, http.MethodPost, url.String(), &body, header) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if !StatusOK(resp.StatusCode) { + return nil, fmt.Errorf("API request failed with status code: %d, response body: %s, and headers:\n%s", resp.StatusCode, ReadError(resp), ReadHeader(resp)) + } + + recording := &Recording{} + err = ReadJSON(resp, recording) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %s", err.Error()) + } + + return recording, err +} + +func (client *RecordingClient) Archive(ctx context.Context, connectUrl string, recordingName string) (string, error) { + url := client.Base.JoinPath(fmt.Sprintf("/api/v1/targets/%s/recordings/%s", url.PathEscape(connectUrl), url.PathEscape(recordingName))) + body := "SAVE" + header := make(http.Header) + header.Add("Content-Type", "text/plain") + header.Add("Accept", "*/*") + + resp, err := SendRequest(ctx, client.Client, http.MethodPatch, url.String(), &body, header) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if !StatusOK(resp.StatusCode) { + return "", fmt.Errorf("API request failed with status code: %d, response body: %s, and headers:\n%s", resp.StatusCode, ReadError(resp), ReadHeader(resp)) + } + + bodyAsString, err := ReadString(resp) + if err != nil { + return "", fmt.Errorf("failed to read response body: %s", err.Error()) + } + + return bodyAsString, nil +} + +func (client *RecordingClient) Stop(ctx context.Context, connectUrl string, recordingName string) error { + url := client.Base.JoinPath(fmt.Sprintf("/api/v1/targets/%s/recordings/%s", url.PathEscape(connectUrl), url.PathEscape(recordingName))) + body := "STOP" + header := make(http.Header) + header.Add("Content-Type", "text/plain") + header.Add("Accept", "*/*") + + resp, err := SendRequest(ctx, client.Client, http.MethodPatch, url.String(), &body, header) + if err != nil { + return err + } + defer resp.Body.Close() + + if !StatusOK(resp.StatusCode) { + return fmt.Errorf("API request failed with status code: %d, response body: %s, and headers:\n%s", resp.StatusCode, ReadError(resp), ReadHeader(resp)) + } + + return nil +} + +func (client *RecordingClient) Delete(ctx context.Context, connectUrl string, recordingName string) error { + url := client.Base.JoinPath(fmt.Sprintf("/api/v1/targets/%s/recordings/%s", url.PathEscape(connectUrl), url.PathEscape(recordingName))) + header := make(http.Header) + + resp, err := SendRequest(ctx, client.Client, http.MethodDelete, url.String(), nil, header) + if err != nil { + return err + } + defer resp.Body.Close() + + if !StatusOK(resp.StatusCode) { + return fmt.Errorf("API request failed with status code: %d, response body: %s, and headers:\n%s", resp.StatusCode, ReadError(resp), ReadHeader(resp)) + } + + return nil +} + +func (client *RecordingClient) GenerateReport(ctx context.Context, connectUrl string, recordingName *Recording) (map[string]interface{}, error) { + reportURL := recordingName.ReportURL + + if len(reportURL) < 1 { + return nil, fmt.Errorf("report URL is not available") + } + + header := make(http.Header) + header.Add("Accept", "application/json") + + resp, err := SendRequest(ctx, client.Client, http.MethodGet, reportURL, nil, header) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if !StatusOK(resp.StatusCode) { + return nil, fmt.Errorf("API request failed with status code: %d, response body: %s, and headers:\n%s", resp.StatusCode, ReadError(resp), ReadHeader(resp)) + } + + report := make(map[string]interface{}, 0) + err = ReadJSON(resp, &report) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %s", err.Error()) + } + + return report, nil +} + +func (client *RecordingClient) ListArchives(ctx context.Context, connectUrl string) ([]Archive, error) { + url := client.Base.JoinPath("/api/v2.2/graphql") + + query := &GraphQLQuery{ + Query: ` + query ArchivedRecordingsForTarget($connectUrl: String) { + archivedRecordings(filter: { sourceTarget: $connectUrl }) { + data { + name + downloadUrl + reportUrl + metadata { + labels + } + size + } + } + } + `, + Variables: map[string]string{ + connectUrl: connectUrl, + }, + } + queryJSON, err := query.ToJSON() + if err != nil { + return nil, fmt.Errorf("failed to construct graph query: %s", err.Error()) + } + body := string(queryJSON) + + header := make(http.Header) + header.Add("Content-Type", "application/json") + header.Add("Accept", "*/*") + + resp, err := SendRequest(ctx, client.Client, http.MethodPost, url.String(), &body, header) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if !StatusOK(resp.StatusCode) { + return nil, fmt.Errorf("API request failed with status code: %d, response body: %s, and headers:\n%s", resp.StatusCode, ReadError(resp), ReadHeader(resp)) + } + + graphQLResponse := &ArchiveGraphQLResponse{} + err = ReadJSON(resp, graphQLResponse) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %s", err.Error()) + } + + return graphQLResponse.Data.ArchivedRecordings.Data, nil +} + +type CredentialClient struct { + *commonCryostatRESTClient +} + +func (client *CredentialClient) Create(ctx context.Context, credential *Credential) error { + url := client.Base.JoinPath("/api/v2.2/credentials") + body := credential.ToFormData() + header := make(http.Header) + header.Add("Content-Type", "application/x-www-form-urlencoded") + + resp, err := SendRequest(ctx, client.Client, http.MethodPost, url.String(), &body, header) + if err != nil { + return err + } + defer resp.Body.Close() + + if !StatusOK(resp.StatusCode) { + return fmt.Errorf("API request failed with status code: %d, response body: %s, and headers:\n%s", resp.StatusCode, ReadError(resp), ReadHeader(resp)) + } + + return nil +} + +func ReadJSON(resp *http.Response, result interface{}) error { + body, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + err = json.Unmarshal(body, result) + if err != nil { + return err + } + return nil +} + +func ReadString(resp *http.Response) (string, error) { + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + return string(body), nil +} + +func ReadHeader(resp *http.Response) string { + header := "" + for name, value := range resp.Header { + for _, h := range value { + header += fmt.Sprintf("%s: %s\n", name, h) + } + } + return header +} + +func ReadError(resp *http.Response) string { + body, _ := ReadString(resp) + return body +} + +func NewHttpClient() *http.Client { + client := &http.Client{ + Timeout: testTimeout, + } + + transport := http.DefaultTransport.(*http.Transport).Clone() + // Ignore verifying certs + transport.TLSClientConfig.InsecureSkipVerify = true + + client.Transport = transport + return client +} + +func NewHttpRequest(ctx context.Context, method string, url string, body *string, header http.Header) (*http.Request, error) { + var reqBody io.Reader + if body != nil { + reqBody = strings.NewReader(*body) + } + req, err := http.NewRequestWithContext(ctx, method, url, reqBody) + if err != nil { + return nil, err + } + if header != nil { + req.Header = header + } + // Authentication is only enabled on OCP. Ignored on k8s. + config, err := rest.InClusterConfig() + if err != nil { + return nil, fmt.Errorf("failed to get in-cluster configurations: %s", err.Error()) + } + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", base64.StdEncoding.EncodeToString([]byte(config.BearerToken)))) + return req, nil +} + +func StatusOK(statusCode int) bool { + return statusCode >= 200 && statusCode < 300 +} + +func SendRequest(ctx context.Context, httpClient *http.Client, method string, url string, body *string, header http.Header) (*http.Response, error) { + var response *http.Response + err := wait.PollImmediateUntilWithContext(ctx, time.Second, func(ctx context.Context) (done bool, err error) { + // Create a new request + req, err := NewHttpRequest(ctx, method, url, body, header) + if err != nil { + return false, fmt.Errorf("failed to create a Cryostat REST request: %s", err.Error()) + } + + resp, err := httpClient.Do(req) + if err != nil { + // Retry when connection is closed. + if errors.Is(err, io.EOF) { + return false, nil + } + return false, err + } + response = resp + return true, nil + }) + + return response, err +} diff --git a/internal/test/scorecard/common_utils.go b/internal/test/scorecard/common_utils.go new file mode 100644 index 000000000..0b8daa582 --- /dev/null +++ b/internal/test/scorecard/common_utils.go @@ -0,0 +1,399 @@ +// Copyright The Cryostat Authors. +// +// 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. + +package scorecard + +import ( + "context" + "fmt" + "net/http" + "net/url" + "time" + + operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" + scapiv1alpha3 "github.com/operator-framework/api/pkg/apis/scorecard/v1alpha3" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + netv1 "k8s.io/api/networking/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/kubernetes/scheme" +) + +const ( + operatorDeploymentName string = "cryostat-operator-controller-manager" + testTimeout time.Duration = time.Minute * 10 +) + +type TestResources struct { + OpenShift bool + Client *CryostatClientset + *scapiv1alpha3.TestResult +} + +func waitForDeploymentAvailability(ctx context.Context, client *CryostatClientset, namespace string, + name string, r *scapiv1alpha3.TestResult) error { + err := wait.PollImmediateUntilWithContext(ctx, time.Second, func(ctx context.Context) (done bool, err error) { + deploy, err := client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + if kerrors.IsNotFound(err) { + r.Log += fmt.Sprintf("deployment %s is not yet found\n", name) + return false, nil // Retry + } + return false, fmt.Errorf("failed to get deployment: %s", err.Error()) + } + // Check for Available condition + for _, condition := range deploy.Status.Conditions { + if condition.Type == appsv1.DeploymentAvailable && + condition.Status == corev1.ConditionTrue { + r.Log += fmt.Sprintf("deployment %s is available\n", deploy.Name) + return true, nil + } + if condition.Type == appsv1.DeploymentReplicaFailure && + condition.Status == corev1.ConditionTrue { + r.Log += fmt.Sprintf("deployment %s is failing, %s: %s\n", deploy.Name, + condition.Reason, condition.Message) + } + } + r.Log += fmt.Sprintf("deployment %s is not yet available\n", deploy.Name) + return false, nil + }) + if err != nil { + logErr := logWorkloadEvents(r, client, namespace, name) + if logErr != nil { + r.Log += fmt.Sprintf("failed to look up deployment errors: %s\n", logErr.Error()) + } + } + return err +} + +func logError(r *scapiv1alpha3.TestResult, message string) { + r.State = scapiv1alpha3.FailState + r.Errors = append(r.Errors, message) +} + +func fail(r scapiv1alpha3.TestResult, message string) scapiv1alpha3.TestResult { + r.State = scapiv1alpha3.FailState + r.Errors = append(r.Errors, message) + return r +} + +func logWorkloadEvents(r *scapiv1alpha3.TestResult, client *CryostatClientset, namespace string, name string) error { + ctx := context.Background() + deploy, err := client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return err + } + // Log deployment conditions and events + r.Log += fmt.Sprintf("deployment %s conditions:\n", deploy.Name) + for _, condition := range deploy.Status.Conditions { + r.Log += fmt.Sprintf("\t%s == %s, %s: %s\n", condition.Type, + condition.Status, condition.Reason, condition.Message) + } + + r.Log += fmt.Sprintf("deployment %s warning events:\n", deploy.Name) + err = logEvents(r, client, namespace, scheme.Scheme, deploy) + if err != nil { + return err + } + + // Look up replica sets for deployment and log conditions and events + selector, err := metav1.LabelSelectorAsSelector(deploy.Spec.Selector) + if err != nil { + return err + } + replicaSets, err := client.AppsV1().ReplicaSets(namespace).List(ctx, metav1.ListOptions{ + LabelSelector: selector.String(), + }) + if err != nil { + return err + } + for _, rs := range replicaSets.Items { + r.Log += fmt.Sprintf("replica set %s conditions:\n", rs.Name) + for _, condition := range rs.Status.Conditions { + r.Log += fmt.Sprintf("\t%s == %s, %s: %s\n", condition.Type, condition.Status, + condition.Reason, condition.Message) + } + r.Log += fmt.Sprintf("replica set %s warning events:\n", rs.Name) + err = logEvents(r, client, namespace, scheme.Scheme, &rs) + if err != nil { + return err + } + } + + // Look up pods for deployment and log conditions and events + pods, err := client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{ + LabelSelector: selector.String(), + }) + if err != nil { + return err + } + for _, pod := range pods.Items { + r.Log += fmt.Sprintf("pod %s phase: %s\n", pod.Name, pod.Status.Phase) + r.Log += fmt.Sprintf("pod %s conditions:\n", pod.Name) + for _, condition := range pod.Status.Conditions { + r.Log += fmt.Sprintf("\t%s == %s, %s: %s\n", condition.Type, condition.Status, + condition.Reason, condition.Message) + } + r.Log += fmt.Sprintf("pod %s warning events:\n", pod.Name) + err = logEvents(r, client, namespace, scheme.Scheme, &pod) + if err != nil { + return err + } + } + return nil +} + +func logEvents(r *scapiv1alpha3.TestResult, client *CryostatClientset, namespace string, + scheme *runtime.Scheme, obj runtime.Object) error { + events, err := client.CoreV1().Events(namespace).Search(scheme, obj) + if err != nil { + return err + } + for _, event := range events.Items { + if event.Type == corev1.EventTypeWarning { + r.Log += fmt.Sprintf("\t%s: %s\n", event.Reason, event.Message) + } + } + return nil +} + +func newEmptyTestResult(testName string) *scapiv1alpha3.TestResult { + return &scapiv1alpha3.TestResult{ + Name: testName, + State: scapiv1alpha3.PassState, + Errors: make([]string, 0), + Suggestions: make([]string, 0), + } +} + +func newTestResources(testName string) *TestResources { + return &TestResources{ + TestResult: newEmptyTestResult(testName), + } +} + +func setupCRTestResources(tr *TestResources, openShiftCertManager bool) error { + r := tr.TestResult + + // Create a new Kubernetes REST client for this test + client, err := NewClientset() + if err != nil { + logError(r, fmt.Sprintf("failed to create client: %s", err.Error())) + return err + } + tr.Client = client + + openshift, err := isOpenShift(client) + if err != nil { + logError(r, fmt.Sprintf("could not determine whether platform is OpenShift: %s", err.Error())) + return err + } + tr.OpenShift = openshift + + if openshift && openShiftCertManager { + err := installOpenShiftCertManager(r) + if err != nil { + logError(r, fmt.Sprintf("failed to install cert-manager Operator for Red Hat OpenShift: %s", err.Error())) + return err + } + } + return nil +} + +func newCryostatCR(name string, namespace string, withIngress bool) *operatorv1beta1.Cryostat { + cr := &operatorv1beta1.Cryostat{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: operatorv1beta1.CryostatSpec{ + Minimal: false, + EnableCertManager: &[]bool{true}[0], + }, + } + + if withIngress { + pathType := netv1.PathTypePrefix + cr.Spec.NetworkOptions = &operatorv1beta1.NetworkConfigurationList{ + CoreConfig: &operatorv1beta1.NetworkConfiguration{ + Annotations: map[string]string{ + "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS", + }, + IngressSpec: &netv1.IngressSpec{ + TLS: []netv1.IngressTLS{{}}, + Rules: []netv1.IngressRule{ + { + Host: "testing.cryostat", + IngressRuleValue: netv1.IngressRuleValue{ + HTTP: &netv1.HTTPIngressRuleValue{ + Paths: []netv1.HTTPIngressPath{ + { + Path: "/", + PathType: &pathType, + Backend: netv1.IngressBackend{ + Service: &netv1.IngressServiceBackend{ + Name: name, + Port: netv1.ServiceBackendPort{ + Number: 8181, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + GrafanaConfig: &operatorv1beta1.NetworkConfiguration{ + Annotations: map[string]string{ + "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS", + }, + IngressSpec: &netv1.IngressSpec{ + TLS: []netv1.IngressTLS{{}}, + Rules: []netv1.IngressRule{ + { + Host: "testing.cryostat-grafana", + IngressRuleValue: netv1.IngressRuleValue{ + HTTP: &netv1.HTTPIngressRuleValue{ + Paths: []netv1.HTTPIngressPath{ + { + Path: "/", + PathType: &pathType, + Backend: netv1.IngressBackend{ + Service: &netv1.IngressServiceBackend{ + Name: fmt.Sprintf("%s-grafana", name), + Port: netv1.ServiceBackendPort{ + Number: 3000, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + } + return cr +} + +func createAndWaitTillCryostatAvailable(cr *operatorv1beta1.Cryostat, resources *TestResources) (*operatorv1beta1.Cryostat, error) { + client := resources.Client + r := resources.TestResult + + cr, err := client.OperatorCRDs().Cryostats(cr.Namespace).Create(context.Background(), cr) + if err != nil { + logError(r, fmt.Sprintf("failed to create Cryostat CR: %s", err.Error())) + return nil, err + } + + // Poll the deployment until it becomes available or we timeout + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + err = waitForDeploymentAvailability(ctx, client, cr.Namespace, cr.Name, r) + if err != nil { + logError(r, fmt.Sprintf("Cryostat main deployment did not become available: %s", err.Error())) + return nil, err + } + + err = wait.PollImmediateUntilWithContext(ctx, time.Second, func(ctx context.Context) (done bool, err error) { + cr, err = client.OperatorCRDs().Cryostats(cr.Namespace).Get(ctx, cr.Name) + if err != nil { + return false, fmt.Errorf("failed to get Cryostat CR: %s", err.Error()) + } + if len(cr.Status.ApplicationURL) > 0 { + return true, nil + } + r.Log += "application URL is not yet available\n" + return false, nil + }) + if err != nil { + logError(r, fmt.Sprintf("application URL not found in CR: %s", err.Error())) + return nil, err + } + r.Log += fmt.Sprintf("application is available at %s\n", cr.Status.ApplicationURL) + + return cr, nil +} + +func waitTillCryostatReady(base *url.URL, resources *TestResources) error { + client := NewHttpClient() + r := resources.TestResult + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + err := wait.PollImmediateUntilWithContext(ctx, time.Second, func(ctx context.Context) (done bool, err error) { + url := base.JoinPath("/health") + req, err := NewHttpRequest(ctx, http.MethodGet, url.String(), nil, make(http.Header)) + if err != nil { + return false, fmt.Errorf("failed to create a Cryostat REST request: %s", err.Error()) + } + req.Header.Add("Accept", "*/*") + + resp, err := client.Do(req) + if err != nil { + return false, err + } + defer resp.Body.Close() + + if !StatusOK(resp.StatusCode) { + if resp.StatusCode == http.StatusServiceUnavailable { + r.Log += fmt.Sprintf("application is not yet reachable at %s\n", base.String()) + return false, nil // Try again + } + return false, fmt.Errorf("API request failed with status code %d: %s", resp.StatusCode, ReadError(resp)) + } + + health := &HealthResponse{} + err = ReadJSON(resp, health) + if err != nil { + return false, fmt.Errorf("failed to read response body: %s", err.Error()) + } + + if err = health.Ready(); err != nil { + r.Log += fmt.Sprintf("application is not yet ready: %s\n", err.Error()) + return false, nil // Try again + } + + r.Log += fmt.Sprintf("application is ready at %s\n", base.String()) + return true, nil + }) + + return err +} + +func cleanupCryostat(r *scapiv1alpha3.TestResult, client *CryostatClientset, name string, namespace string) { + cr := &operatorv1beta1.Cryostat{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + } + ctx := context.Background() + err := client.OperatorCRDs().Cryostats(cr.Namespace).Delete(ctx, + cr.Name, &metav1.DeleteOptions{}) + if err != nil { + r.Log += fmt.Sprintf("failed to delete Cryostat: %s\n", err.Error()) + } +} diff --git a/internal/test/scorecard/openshift.go b/internal/test/scorecard/openshift.go index dee12c4d3..247068a48 100644 --- a/internal/test/scorecard/openshift.go +++ b/internal/test/scorecard/openshift.go @@ -27,12 +27,15 @@ import ( ctrl "sigs.k8s.io/controller-runtime" configv1 "github.com/openshift/api/config/v1" + routev1 "github.com/openshift/api/route/v1" corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/discovery" corev1client "k8s.io/client-go/kubernetes/typed/core/v1" ) @@ -202,3 +205,7 @@ func installOpenShiftCertManager(r *scapiv1alpha3.TestResult) error { return false, nil }) } + +func isOpenShift(client discovery.DiscoveryInterface) (bool, error) { + return discovery.IsResourceEnabled(client, routev1.GroupVersion.WithResource("routes")) +} diff --git a/internal/test/scorecard/tests.go b/internal/test/scorecard/tests.go index b2aab776a..2860aa65e 100644 --- a/internal/test/scorecard/tests.go +++ b/internal/test/scorecard/tests.go @@ -17,338 +17,195 @@ package scorecard import ( "context" "fmt" + "net/url" "time" - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" scapiv1alpha3 "github.com/operator-framework/api/pkg/apis/scorecard/v1alpha3" apimanifests "github.com/operator-framework/api/pkg/manifests" - - routev1 "github.com/openshift/api/route/v1" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - netv1 "k8s.io/api/networking/v1" - kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/discovery" - "k8s.io/client-go/kubernetes/scheme" ) const ( - OperatorInstallTestName string = "operator-install" - CryostatCRTestName string = "cryostat-cr" - operatorDeploymentName string = "cryostat-operator-controller-manager" - testTimeout time.Duration = time.Minute * 10 + OperatorInstallTestName string = "operator-install" + CryostatCRTestName string = "cryostat-cr" + CryostatRecordingTestName string = "cryostat-recording" ) // OperatorInstallTest checks that the operator installed correctly func OperatorInstallTest(bundle *apimanifests.Bundle, namespace string) scapiv1alpha3.TestResult { - r := scapiv1alpha3.TestResult{} - r.Name = OperatorInstallTestName - r.State = scapiv1alpha3.PassState - r.Errors = make([]string, 0) - r.Suggestions = make([]string, 0) + r := newEmptyTestResult(OperatorInstallTestName) // Create a new Kubernetes REST client for this test client, err := NewClientset() if err != nil { - return fail(r, fmt.Sprintf("failed to create client: %s", err.Error())) + return fail(*r, fmt.Sprintf("failed to create client: %s", err.Error())) } // Poll the deployment until it becomes available or we timeout ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - err = waitForDeploymentAvailability(ctx, client, namespace, operatorDeploymentName, &r) + err = waitForDeploymentAvailability(ctx, client, namespace, operatorDeploymentName, r) if err != nil { - return fail(r, fmt.Sprintf("operator deployment did not become available: %s", err.Error())) + return fail(*r, fmt.Sprintf("operator deployment did not become available: %s", err.Error())) } - return r + return *r } // CryostatCRTest checks that the operator installs Cryostat in response to a Cryostat CR func CryostatCRTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) scapiv1alpha3.TestResult { - r := scapiv1alpha3.TestResult{} - r.Name = CryostatCRTestName - r.State = scapiv1alpha3.PassState - r.Errors = make([]string, 0) - r.Suggestions = make([]string, 0) + tr := newTestResources(CryostatCRTestName) + r := tr.TestResult - // Create a new Kubernetes REST client for this test - client, err := NewClientset() + err := setupCRTestResources(tr, openShiftCertManager) if err != nil { - return fail(r, fmt.Sprintf("failed to create client: %s", err.Error())) + return fail(*r, fmt.Sprintf("failed to set up %s test: %s", CryostatCRTestName, err.Error())) } - defer cleanupCryostat(&r, client, namespace) - openshift, err := isOpenShift(client.DiscoveryClient) + // Create a default Cryostat CR + _, err = createAndWaitTillCryostatAvailable(newCryostatCR(CryostatCRTestName, namespace, !tr.OpenShift), tr) if err != nil { - return fail(r, fmt.Sprintf("could not determine whether platform is OpenShift: %s", err.Error())) + return fail(*r, fmt.Sprintf("%s test failed: %s", CryostatCRTestName, err.Error())) } + defer cleanupCryostat(r, tr.Client, CryostatCRTestName, namespace) - if openshift && openShiftCertManager { - err := installOpenShiftCertManager(&r) - if err != nil { - return fail(r, fmt.Sprintf("failed to install cert-manager Operator for Red Hat OpenShift: %s", err.Error())) - } - } + return *r +} - // Create a default Cryostat CR - cr := newCryostatCR(namespace, !openshift) +// TODO add a built in discovery test too +func CryostatRecordingTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) scapiv1alpha3.TestResult { + tr := newTestResources(CryostatRecordingTestName) + r := tr.TestResult - ctx := context.Background() - cr, err = client.OperatorCRDs().Cryostats(namespace).Create(ctx, cr) + err := setupCRTestResources(tr, openShiftCertManager) if err != nil { - return fail(r, fmt.Sprintf("failed to create Cryostat CR: %s", err.Error())) + return fail(*r, fmt.Sprintf("failed to set up %s test: %s", CryostatRecordingTestName, err.Error())) } - // Poll the deployment until it becomes available or we timeout - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - err = waitForDeploymentAvailability(ctx, client, cr.Namespace, cr.Name, &r) + // Create a default Cryostat CR + cr, err := createAndWaitTillCryostatAvailable(newCryostatCR(CryostatRecordingTestName, namespace, !tr.OpenShift), tr) if err != nil { - return fail(r, fmt.Sprintf("Cryostat main deployment did not become available: %s", err.Error())) + return fail(*r, fmt.Sprintf("failed to determine application URL: %s", err.Error())) } + defer cleanupCryostat(r, tr.Client, CryostatRecordingTestName, namespace) - err = wait.PollImmediateUntilWithContext(ctx, time.Second, func(ctx context.Context) (done bool, err error) { - cr, err = client.OperatorCRDs().Cryostats(namespace).Get(ctx, cr.Name) - if err != nil { - return false, fmt.Errorf("failed to get Cryostat CR: %s", err.Error()) - } - if len(cr.Status.ApplicationURL) > 0 { - return true, nil - } - r.Log += "Application URL is not yet available\n" - return false, nil - }) + base, err := url.Parse(cr.Status.ApplicationURL) if err != nil { - return fail(r, fmt.Sprintf("Application URL not found in CR: %s", err.Error())) + return fail(*r, fmt.Sprintf("application URL is invalid: %s", err.Error())) } - r.Log += fmt.Sprintf("Application is ready at %s\n", cr.Status.ApplicationURL) - - return r -} -func waitForDeploymentAvailability(ctx context.Context, client *CryostatClientset, namespace string, - name string, r *scapiv1alpha3.TestResult) error { - err := wait.PollImmediateUntilWithContext(ctx, time.Second, func(ctx context.Context) (done bool, err error) { - deploy, err := client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{}) - if err != nil { - if kerrors.IsNotFound(err) { - r.Log += fmt.Sprintf("deployment %s is not yet found\n", name) - return false, nil // Retry - } - return false, fmt.Errorf("failed to get deployment: %s", err.Error()) - } - // Check for Available condition - for _, condition := range deploy.Status.Conditions { - if condition.Type == appsv1.DeploymentAvailable && - condition.Status == corev1.ConditionTrue { - r.Log += fmt.Sprintf("deployment %s is available\n", deploy.Name) - return true, nil - } - if condition.Type == appsv1.DeploymentReplicaFailure && - condition.Status == corev1.ConditionTrue { - r.Log += fmt.Sprintf("deployment %s is failing, %s: %s\n", deploy.Name, - condition.Reason, condition.Message) - } - } - r.Log += fmt.Sprintf("deployment %s is not yet available\n", deploy.Name) - return false, nil - }) + err = waitTillCryostatReady(base, tr) if err != nil { - logErr := logErrors(r, client, namespace, name) - if logErr != nil { - r.Log += fmt.Sprintf("failed to look up deployment errors: %s\n", logErr.Error()) - } + return fail(*r, fmt.Sprintf("failed to reach the application: %s", err.Error())) } - return err -} -func fail(r scapiv1alpha3.TestResult, message string) scapiv1alpha3.TestResult { - r.State = scapiv1alpha3.FailState - r.Errors = append(r.Errors, message) - return r -} + apiClient := NewCryostatRESTClientset(base) -func cleanupCryostat(r *scapiv1alpha3.TestResult, client *CryostatClientset, namespace string) { - cr := &operatorv1beta1.Cryostat{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat-cr-test", - Namespace: namespace, - }, + // Create a custom target for test + targetOptions := &Target{ + ConnectUrl: "service:jmx:rmi:///jndi/rmi://localhost:0/jmxrmi", + Alias: "customTarget", } - ctx := context.Background() - err := client.OperatorCRDs().Cryostats(cr.Namespace).Delete(ctx, - cr.Name, &metav1.DeleteOptions{}) + target, err := apiClient.Targets().Create(context.Background(), targetOptions) if err != nil { - r.Log += fmt.Sprintf("failed to delete Cryostat: %s\n", err.Error()) + return fail(*r, fmt.Sprintf("failed to create a target: %s", err.Error())) } -} + r.Log += fmt.Sprintf("created a custom target: %+v\n", target) + connectUrl := target.ConnectUrl -func logErrors(r *scapiv1alpha3.TestResult, client *CryostatClientset, namespace string, name string) error { - ctx := context.Background() - deploy, err := client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{}) + jmxSecretName := CryostatRecordingTestName + "-jmx-auth" + secret, err := tr.Client.CoreV1().Secrets(namespace).Get(context.Background(), jmxSecretName, metav1.GetOptions{}) if err != nil { - return err + return fail(*r, fmt.Sprintf("failed to get jmx credentials: %s", err.Error())) } - // Log deployment conditions and events - r.Log += fmt.Sprintf("deployment %s conditions:\n", deploy.Name) - for _, condition := range deploy.Status.Conditions { - r.Log += fmt.Sprintf("\t%s == %s, %s: %s\n", condition.Type, - condition.Status, condition.Reason, condition.Message) + + credential := &Credential{ + UserName: string(secret.Data["CRYOSTAT_RJMX_USER"]), + Password: string(secret.Data["CRYOSTAT_RJMX_PASS"]), + MatchExpression: fmt.Sprintf("target.alias==\"%s\"", target.Alias), } - r.Log += fmt.Sprintf("deployment %s warning events:\n", deploy.Name) - err = logEvents(r, client, namespace, scheme.Scheme, deploy) + err = apiClient.CredentialClient.Create(context.Background(), credential) if err != nil { - return err + return fail(*r, fmt.Sprintf("failed to create stored credential: %s", err.Error())) } + r.Log += fmt.Sprintf("created stored credential with match expression: %s\n", credential.MatchExpression) + + // Wait for Cryostat to update the discovery tree + time.Sleep(2 * time.Second) - // Look up replica sets for deployment and log conditions and events - selector, err := metav1.LabelSelectorAsSelector(deploy.Spec.Selector) + // Create a recording + options := &RecordingCreateOptions{ + RecordingName: "scorecard_test_rec", + Events: "template=ALL", + Duration: 0, // Continuous + ToDisk: true, + MaxSize: 0, + MaxAge: 0, + } + rec, err := apiClient.Recordings().Create(context.Background(), connectUrl, options) if err != nil { - return err + return fail(*r, fmt.Sprintf("failed to create a recording: %s", err.Error())) } - replicaSets, err := client.AppsV1().ReplicaSets(namespace).List(ctx, metav1.ListOptions{ - LabelSelector: selector.String(), - }) + r.Log += fmt.Sprintf("created a recording: %+v\n", rec) + + // View the current recording list after creating one + recs, err := apiClient.Recordings().List(context.Background(), connectUrl) if err != nil { - return err + return fail(*r, fmt.Sprintf("failed to list recordings: %s", err.Error())) } - for _, rs := range replicaSets.Items { - r.Log += fmt.Sprintf("replica set %s conditions:\n", rs.Name) - for _, condition := range rs.Status.Conditions { - r.Log += fmt.Sprintf("\t%s == %s, %s: %s\n", condition.Type, condition.Status, - condition.Reason, condition.Message) - } - r.Log += fmt.Sprintf("replica set %s warning events:\n", rs.Name) - err = logEvents(r, client, namespace, scheme.Scheme, &rs) - if err != nil { - return err - } + r.Log += fmt.Sprintf("current list of recordings: %+v\n", recs) + + // Allow the recording to run for 10s + time.Sleep(30 * time.Second) + + // Archive the recording + archiveName, err := apiClient.Recordings().Archive(context.Background(), connectUrl, rec.Name) + if err != nil { + return fail(*r, fmt.Sprintf("failed to archive the recording: %s", err.Error())) } + r.Log += fmt.Sprintf("archived the recording %s at: %s\n", rec.Name, archiveName) - // Look up pods for deployment and log conditions and events - pods, err := client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{ - LabelSelector: selector.String(), - }) + archives, err := apiClient.Recordings().ListArchives(context.Background(), connectUrl) if err != nil { - return err + return fail(*r, fmt.Sprintf("failed to list archives: %s", err.Error())) } - for _, pod := range pods.Items { - r.Log += fmt.Sprintf("pod %s phase: %s\n", pod.Name, pod.Status.Phase) - r.Log += fmt.Sprintf("pod %s conditions:\n", pod.Name) - for _, condition := range pod.Status.Conditions { - r.Log += fmt.Sprintf("\t%s == %s, %s: %s\n", condition.Type, condition.Status, - condition.Reason, condition.Message) - } - r.Log += fmt.Sprintf("pod %s warning events:\n", pod.Name) - err = logEvents(r, client, namespace, scheme.Scheme, &pod) - if err != nil { - return err - } + r.Log += fmt.Sprintf("current list of archives: %+v\n", archives) + + report, err := apiClient.Recordings().GenerateReport(context.Background(), connectUrl, rec) + if err != nil { + return fail(*r, fmt.Sprintf("failed to generate report for the recording: %s", err.Error())) } - return nil -} + r.Log += fmt.Sprintf("generated report for the recording %s: %+v\n", rec.Name, report) -func logEvents(r *scapiv1alpha3.TestResult, client *CryostatClientset, namespace string, - scheme *runtime.Scheme, obj runtime.Object) error { - events, err := client.CoreV1().Events(namespace).Search(scheme, obj) + // Stop the recording + err = apiClient.Recordings().Stop(context.Background(), connectUrl, rec.Name) if err != nil { - return err + return fail(*r, fmt.Sprintf("failed to stop the recording %s: %s", rec.Name, err.Error())) } - for _, event := range events.Items { - if event.Type == corev1.EventTypeWarning { - r.Log += fmt.Sprintf("\t%s: %s\n", event.Reason, event.Message) - } + // Get the recording to verify its state + rec, err = apiClient.Recordings().Get(context.Background(), connectUrl, rec.Name) + if err != nil { + return fail(*r, fmt.Sprintf("failed to get the recordings: %s", err.Error())) } - return nil -} + if rec.State != "STOPPED" { + return fail(*r, fmt.Sprintf("recording %s failed to stop: %s", rec.Name, err.Error())) + } + r.Log += fmt.Sprintf("stopped the recording: %s\n", rec.Name) -func newCryostatCR(namespace string, withIngress bool) *operatorv1beta1.Cryostat { - cr := &operatorv1beta1.Cryostat{ - ObjectMeta: metav1.ObjectMeta{ - Name: "cryostat-cr-test", - Namespace: namespace, - }, - Spec: operatorv1beta1.CryostatSpec{ - Minimal: false, - EnableCertManager: &[]bool{true}[0], - }, + // Delete the recording + err = apiClient.Recordings().Delete(context.Background(), connectUrl, rec.Name) + if err != nil { + return fail(*r, fmt.Sprintf("failed to delete the recording %s: %s", rec.Name, err.Error())) } + r.Log += fmt.Sprintf("deleted the recording: %s\n", rec.Name) - if withIngress { - pathType := netv1.PathTypePrefix - cr.Spec.NetworkOptions = &operatorv1beta1.NetworkConfigurationList{ - CoreConfig: &operatorv1beta1.NetworkConfiguration{ - Annotations: map[string]string{ - "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS", - }, - IngressSpec: &netv1.IngressSpec{ - TLS: []netv1.IngressTLS{{}}, - Rules: []netv1.IngressRule{ - { - Host: "testing.cryostat", - IngressRuleValue: netv1.IngressRuleValue{ - HTTP: &netv1.HTTPIngressRuleValue{ - Paths: []netv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathType, - Backend: netv1.IngressBackend{ - Service: &netv1.IngressServiceBackend{ - Name: "cryostat-cr-test", - Port: netv1.ServiceBackendPort{ - Number: 8181, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - GrafanaConfig: &operatorv1beta1.NetworkConfiguration{ - Annotations: map[string]string{ - "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS", - }, - IngressSpec: &netv1.IngressSpec{ - TLS: []netv1.IngressTLS{{}}, - Rules: []netv1.IngressRule{ - { - Host: "testing.cryostat-grafana", - IngressRuleValue: netv1.IngressRuleValue{ - HTTP: &netv1.HTTPIngressRuleValue{ - Paths: []netv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathType, - Backend: netv1.IngressBackend{ - Service: &netv1.IngressServiceBackend{ - Name: "cryostat-cr-test-grafana", - Port: netv1.ServiceBackendPort{ - Number: 3000, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - } + // View the current recording list after deleting one + recs, err = apiClient.Recordings().List(context.Background(), connectUrl) + if err != nil { + return fail(*r, fmt.Sprintf("failed to list recordings: %s", err.Error())) } - return cr -} + r.Log += fmt.Sprintf("current list of recordings: %+v\n", recs) -func isOpenShift(client discovery.DiscoveryInterface) (bool, error) { - return discovery.IsResourceEnabled(client, routev1.GroupVersion.WithResource("routes")) + return *r } diff --git a/internal/test/scorecard/types.go b/internal/test/scorecard/types.go new file mode 100644 index 000000000..7e34ff4a0 --- /dev/null +++ b/internal/test/scorecard/types.go @@ -0,0 +1,146 @@ +// Copyright The Cryostat Authors. +// +// 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. + +package scorecard + +import ( + "encoding/json" + "errors" + "net/url" + "strconv" +) + +type HealthResponse struct { + CryostatVersion string `json:"cryostatVersion"` + DashboardAvailable bool `json:"dashboardAvailable"` + DashboardConfigured bool `json:"dashboardConfigured"` + DataSourceAvailable bool `json:"datasourceAvailable"` + DataSourceConfigured bool `json:"datasourceConfigured"` + ReportsAvailable bool `json:"reportsAvailable"` + ReportsConfigured bool `json:"reportsConfigured"` +} + +func (health *HealthResponse) Ready() error { + if !health.DashboardAvailable { + return errors.New("dashboard is not available") + } + + if !health.DataSourceAvailable { + return errors.New("datasource is not available") + } + + if !health.ReportsAvailable { + return errors.New("report is not available") + } + return nil +} + +type RecordingCreateOptions struct { + RecordingName string + Events string + Duration int32 + ToDisk bool + MaxSize int32 + MaxAge int32 +} + +func (opts *RecordingCreateOptions) ToFormData() string { + formData := &url.Values{} + + formData.Add("recordingName", opts.RecordingName) + formData.Add("events", opts.Events) + formData.Add("duration", strconv.Itoa(int(opts.Duration))) + formData.Add("toDisk", strconv.FormatBool(opts.ToDisk)) + formData.Add("maxSize", strconv.Itoa(int(opts.MaxSize))) + formData.Add("maxAge", strconv.Itoa(int(opts.MaxAge))) + + return formData.Encode() +} + +type Credential struct { + UserName string + Password string + MatchExpression string +} + +func (cred *Credential) ToFormData() string { + formData := &url.Values{} + + formData.Add("username", cred.UserName) + formData.Add("password", cred.Password) + formData.Add("matchExpression", cred.MatchExpression) + + return formData.Encode() +} + +type Recording struct { + DownloadURL string `json:"downloadUrl"` + ReportURL string `json:"reportUrl"` + Id uint32 `json:"id"` + Name string `json:"name"` + StartTime uint64 `json:"startTime"` + State string `json:"state"` + Duration int32 `json:"duration"` + Continuous bool `json:"continuous"` + ToDisk bool `json:"toDisk"` + MaxSize int32 `json:"maxSize"` + MaxAge int32 `json:"maxAge"` +} + +type Archive struct { + Name string + DownloadUrl string + ReportUrl string + Metadata struct { + Labels map[string]interface{} + } + Size int32 +} + +type CustomTargetResponse struct { + Data struct { + Result *Target `json:"result"` + } `json:"data"` +} + +type Target struct { + ConnectUrl string `json:"connectUrl"` + Alias string `json:"alias,omitempty"` +} + +func (target *Target) ToFormData() string { + formData := &url.Values{} + + formData.Add("connectUrl", target.ConnectUrl) + formData.Add("alias", target.Alias) + + return formData.Encode() +} + +type GraphQLQuery struct { + Query string `json:"query"` + Variables map[string]string `json:"variables,omitempty"` +} + +func (query *GraphQLQuery) ToJSON() ([]byte, error) { + return json.Marshal(query) +} + +type ArchiveGraphQLResponse struct { + Data struct { + ArchivedRecordings struct { + Data []Archive `json:"data"` + } `json:"archivedRecordings"` + } `json:"data"` +} From bf8df159a12fb448d9c371da3807562a64523952 Mon Sep 17 00:00:00 2001 From: Ming Yu Wang <90855268+mwangggg@users.noreply.github.com> Date: Fri, 8 Mar 2024 12:17:51 -0500 Subject: [PATCH 04/16] test(scorecard): scorecard test for Cryostat CR configuration changes (#739) * CR config scorecard * reformat * reviews * add kubectl license --- ...yostat-operator.clusterserviceversion.yaml | 2 +- bundle/tests/scorecard/config.yaml | 16 +++- config/scorecard/patches/custom.config.yaml | 16 +++- go-license.yml | 20 +++++ hack/custom.config.yaml.in | 10 +++ .../images/custom-scorecard-tests/main.go | 4 + internal/test/scorecard/clients.go | 6 +- internal/test/scorecard/common_utils.go | 67 +++++++++++++++++ internal/test/scorecard/tests.go | 75 ++++++++++++++++++- 9 files changed, 203 insertions(+), 13 deletions(-) diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index 824ee8edb..8f7a96226 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -54,7 +54,7 @@ metadata: capabilities: Seamless Upgrades categories: Monitoring, Developer Tools containerImage: quay.io/cryostat/cryostat-operator:2.5.0-dev - createdAt: "2024-03-05T02:05:10Z" + createdAt: "2024-03-07T15:43:22Z" description: JVM monitoring and profiling tool operatorframework.io/initialization-resource: |- { diff --git a/bundle/tests/scorecard/config.yaml b/bundle/tests/scorecard/config.yaml index 12b393613..548b2d46f 100644 --- a/bundle/tests/scorecard/config.yaml +++ b/bundle/tests/scorecard/config.yaml @@ -70,7 +70,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - operator-install - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240305020416 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240307153901 labels: suite: cryostat test: operator-install @@ -80,7 +80,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240305020416 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240307153901 labels: suite: cryostat test: cryostat-cr @@ -90,13 +90,23 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-recording - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240305020416 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240307153901 labels: suite: cryostat test: cryostat-recording storage: spec: mountPath: {} + - entrypoint: + - cryostat-scorecard-tests + - cryostat-config-change + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240307153901 + labels: + suite: cryostat + test: cryostat-config-change + storage: + spec: + mountPath: {} storage: spec: mountPath: {} diff --git a/config/scorecard/patches/custom.config.yaml b/config/scorecard/patches/custom.config.yaml index 527eac9ad..59b597ef0 100644 --- a/config/scorecard/patches/custom.config.yaml +++ b/config/scorecard/patches/custom.config.yaml @@ -8,7 +8,7 @@ entrypoint: - cryostat-scorecard-tests - operator-install - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240305020416" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240307154322" labels: suite: cryostat test: operator-install @@ -18,7 +18,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240305020416" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240307154322" labels: suite: cryostat test: cryostat-cr @@ -28,7 +28,17 @@ entrypoint: - cryostat-scorecard-tests - cryostat-recording - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240305020416" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240307154322" labels: suite: cryostat test: cryostat-recording +- op: add + path: /stages/1/tests/- + value: + entrypoint: + - cryostat-scorecard-tests + - cryostat-config-change + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240307154322" + labels: + suite: cryostat + test: cryostat-config-change diff --git a/go-license.yml b/go-license.yml index 2a9d2d95a..f5d6506c9 100644 --- a/go-license.yml +++ b/go-license.yml @@ -13,6 +13,26 @@ header: | // See the License for the specific language governing permissions and // limitations under the License. +custom-headers: + - name: kubectl + header: | + // Copyright The Cryostat Authors. + // Copyright 2016 The Kubernetes Authors. + // + // 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. + paths: + - internal/test/scorecard/common_utils.go + exclude: names: - '.*generated.*' diff --git a/hack/custom.config.yaml.in b/hack/custom.config.yaml.in index 487462006..b707766ac 100644 --- a/hack/custom.config.yaml.in +++ b/hack/custom.config.yaml.in @@ -31,3 +31,13 @@ labels: suite: cryostat test: cryostat-recording +- op: add + path: /stages/1/tests/- + value: + entrypoint: + - cryostat-scorecard-tests + - cryostat-config-change + image: "${CUSTOM_SCORECARD_IMG}" + labels: + suite: cryostat + test: cryostat-config-change diff --git a/internal/images/custom-scorecard-tests/main.go b/internal/images/custom-scorecard-tests/main.go index 62808a82f..6faed656e 100644 --- a/internal/images/custom-scorecard-tests/main.go +++ b/internal/images/custom-scorecard-tests/main.go @@ -80,6 +80,7 @@ func printValidTests() []scapiv1alpha3.TestResult { tests.OperatorInstallTestName, tests.CryostatCRTestName, tests.CryostatRecordingTestName, + tests.CryostatConfigChangeTestName, }, ",")) result.Errors = append(result.Errors, str) @@ -92,6 +93,7 @@ func validateTests(testNames []string) bool { case tests.OperatorInstallTestName: case tests.CryostatCRTestName: case tests.CryostatRecordingTestName: + case tests.CryostatConfigChangeTestName: default: return false } @@ -112,6 +114,8 @@ func runTests(testNames []string, bundle *apimanifests.Bundle, namespace string, results = append(results, tests.CryostatCRTest(bundle, namespace, openShiftCertManager)) case tests.CryostatRecordingTestName: results = append(results, tests.CryostatRecordingTest(bundle, namespace, openShiftCertManager)) + case tests.CryostatConfigChangeTestName: + results = append(results, tests.CryostatConfigChangeTest(bundle, namespace, openShiftCertManager)) default: log.Fatalf("unknown test found: %s", testName) } diff --git a/internal/test/scorecard/clients.go b/internal/test/scorecard/clients.go index ffe3ad79e..8f78d7a05 100644 --- a/internal/test/scorecard/clients.go +++ b/internal/test/scorecard/clients.go @@ -136,7 +136,7 @@ func (c *CryostatClient) Create(ctx context.Context, obj *operatorv1beta1.Cryost // Update updates the provided Cryostat CR func (c *CryostatClient) Update(ctx context.Context, obj *operatorv1beta1.Cryostat) (*operatorv1beta1.Cryostat, error) { - return update(ctx, c.restClient, c.resource, c.namespace, obj, &operatorv1beta1.Cryostat{}) + return update(ctx, c.restClient, c.resource, c.namespace, obj, &operatorv1beta1.Cryostat{}, obj.Name) } // Delete deletes the Cryostat CR with the given name @@ -158,9 +158,9 @@ func create[r runtime.Object](ctx context.Context, c rest.Interface, res string, return result, err } -func update[r runtime.Object](ctx context.Context, c rest.Interface, res string, ns string, obj r, result r) (r, error) { +func update[r runtime.Object](ctx context.Context, c rest.Interface, res string, ns string, obj r, result r, name string) (r, error) { err := c.Put(). - Namespace(ns).Resource(res). + Namespace(ns).Resource(res).Name(name). Body(obj).Do(ctx).Into(result) return result, err } diff --git a/internal/test/scorecard/common_utils.go b/internal/test/scorecard/common_utils.go index 0b8daa582..c36dc720a 100644 --- a/internal/test/scorecard/common_utils.go +++ b/internal/test/scorecard/common_utils.go @@ -1,4 +1,5 @@ // Copyright The Cryostat Authors. +// Copyright 2016 The Kubernetes Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -383,6 +384,72 @@ func waitTillCryostatReady(base *url.URL, resources *TestResources) error { return err } +func updateAndWaitTillCryostatAvailable(cr *operatorv1beta1.Cryostat, resources *TestResources) error { + client := resources.Client + r := resources.TestResult + + cr, err := client.OperatorCRDs().Cryostats(cr.Namespace).Update(context.Background(), cr) + if err != nil { + r.Log += fmt.Sprintf("failed to update Cryostat CR: %s", err.Error()) + return err + } + + // Poll the deployment until it becomes available or we timeout + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + err = wait.PollImmediateUntilWithContext(ctx, time.Second, func(ctx context.Context) (done bool, err error) { + deploy, err := client.AppsV1().Deployments(cr.Namespace).Get(ctx, cr.Name, metav1.GetOptions{}) + if err != nil { + if kerrors.IsNotFound(err) { + r.Log += fmt.Sprintf("deployment %s is not yet found\n", cr.Name) + return false, nil // Retry + } + return false, fmt.Errorf("failed to get deployment: %s", err.Error()) + } + + // Wait for deployment to update by verifying Cryostat has PVC configured + for _, volume := range deploy.Spec.Template.Spec.Volumes { + if volume.VolumeSource.EmptyDir != nil { + r.Log += fmt.Sprintf("Cryostat deployment is still updating. Storage: %s\n", volume.VolumeSource.EmptyDir) + return false, nil // Retry + } + if volume.VolumeSource.PersistentVolumeClaim != nil { + break + } + } + + // Derived from kubectl: https://github.com/kubernetes/kubectl/blob/24d21a0/pkg/polymorphichelpers/rollout_status.go#L75-L91 + // Check for deployment condition + if deploy.Generation <= deploy.Status.ObservedGeneration { + for _, condition := range deploy.Status.Conditions { + if condition.Type == appsv1.DeploymentProgressing && condition.Status == corev1.ConditionFalse && condition.Reason == "ProgressDeadlineExceeded" { + return false, fmt.Errorf("deployment %s exceeded its progress deadline", deploy.Name) // Don't Retry + } + } + if deploy.Spec.Replicas != nil && deploy.Status.UpdatedReplicas < *deploy.Spec.Replicas { + r.Log += fmt.Sprintf("Waiting for deployment %s rollout to finish: %d out of %d new replicas have been updated... \n", deploy.Name, deploy.Status.UpdatedReplicas, *deploy.Spec.Replicas) + return false, nil + } + if deploy.Status.Replicas > deploy.Status.UpdatedReplicas { + r.Log += fmt.Sprintf("Waiting for deployment %s rollout to finish: %d old replicas are pending termination... \n", deploy.Name, deploy.Status.Replicas-deploy.Status.UpdatedReplicas) + return false, nil + } + if deploy.Status.AvailableReplicas < deploy.Status.UpdatedReplicas { + r.Log += fmt.Sprintf("Waiting for deployment %s rollout to finish: %d out of %d updated replicas are available... \n", deploy.Name, deploy.Status.AvailableReplicas, deploy.Status.UpdatedReplicas) + return false, nil + } + r.Log += fmt.Sprintf("deployment %s successfully rolled out\n", deploy.Name) + return true, nil + } + r.Log += "Waiting for deployment spec update to be observed...\n" + return false, nil + }) + if err != nil { + return fmt.Errorf("failed to look up deployment errors: %s", err.Error()) + } + return err +} + func cleanupCryostat(r *scapiv1alpha3.TestResult, client *CryostatClientset, name string, namespace string) { cr := &operatorv1beta1.Cryostat{ ObjectMeta: metav1.ObjectMeta{ diff --git a/internal/test/scorecard/tests.go b/internal/test/scorecard/tests.go index 2860aa65e..72bb48f1a 100644 --- a/internal/test/scorecard/tests.go +++ b/internal/test/scorecard/tests.go @@ -20,15 +20,19 @@ import ( "net/url" "time" + operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" scapiv1alpha3 "github.com/operator-framework/api/pkg/apis/scorecard/v1alpha3" apimanifests "github.com/operator-framework/api/pkg/manifests" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ( - OperatorInstallTestName string = "operator-install" - CryostatCRTestName string = "cryostat-cr" - CryostatRecordingTestName string = "cryostat-recording" + OperatorInstallTestName string = "operator-install" + CryostatCRTestName string = "cryostat-cr" + CryostatRecordingTestName string = "cryostat-recording" + CryostatConfigChangeTestName string = "cryostat-config-change" ) // OperatorInstallTest checks that the operator installed correctly @@ -72,6 +76,71 @@ func CryostatCRTest(bundle *apimanifests.Bundle, namespace string, openShiftCert return *r } +func CryostatConfigChangeTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) scapiv1alpha3.TestResult { + tr := newTestResources(CryostatConfigChangeTestName) + r := tr.TestResult + + err := setupCRTestResources(tr, openShiftCertManager) + if err != nil { + return fail(*r, fmt.Sprintf("failed to set up %s test: %s", CryostatConfigChangeTestName, err.Error())) + } + + // Create a default Cryostat CR with default empty dir + cr := newCryostatCR(CryostatConfigChangeTestName, namespace, !tr.OpenShift) + cr.Spec.StorageOptions = &operatorv1beta1.StorageConfiguration{ + EmptyDir: &operatorv1beta1.EmptyDirConfig{ + Enabled: true, + }, + } + + _, err = createAndWaitTillCryostatAvailable(cr, tr) + if err != nil { + return fail(*r, fmt.Sprintf("failed to determine application URL: %s", err.Error())) + } + defer cleanupCryostat(r, tr.Client, CryostatRecordingTestName, namespace) + + // Switch Cryostat CR to PVC for redeployment + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + client := tr.Client + + cr, err = client.OperatorCRDs().Cryostats(namespace).Get(ctx, CryostatConfigChangeTestName) + if err != nil { + return fail(*r, fmt.Sprintf("failed to get Cryostat CR: %s", err.Error())) + } + cr.Spec.StorageOptions = &operatorv1beta1.StorageConfiguration{ + PVC: &operatorv1beta1.PersistentVolumeClaimConfig{ + Spec: &corev1.PersistentVolumeClaimSpec{ + StorageClassName: nil, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: resource.MustParse("1Gi"), + }, + }, + }, + }, + } + + // Wait for redeployment of Cryostat CR + err = updateAndWaitTillCryostatAvailable(cr, tr) + if err != nil { + return fail(*r, fmt.Sprintf("Cryostat redeployment did not become available: %s", err.Error())) + } + r.Log += "Cryostat deployment has successfully updated with new spec template" + + base, err := url.Parse(cr.Status.ApplicationURL) + if err != nil { + return fail(*r, fmt.Sprintf("application URL is invalid: %s", err.Error())) + } + + err = waitTillCryostatReady(base, tr) + if err != nil { + return fail(*r, fmt.Sprintf("failed to reach the application: %s", err.Error())) + } + + return *r +} + // TODO add a built in discovery test too func CryostatRecordingTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) scapiv1alpha3.TestResult { tr := newTestResources(CryostatRecordingTestName) From 96ea4cb7ae2c219eb37cf5583a2607c31d7889a7 Mon Sep 17 00:00:00 2001 From: Ming Yu Wang <90855268+mwangggg@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:29:40 -0400 Subject: [PATCH 05/16] test(scorecard): scorecard test for report generator (#753) * deploy reports sidecar * report scorecard test * update * rebase fix * query health --- ...yostat-operator.clusterserviceversion.yaml | 2 +- bundle/tests/scorecard/config.yaml | 18 +++-- config/scorecard/patches/custom.config.yaml | 18 +++-- hack/custom.config.yaml.in | 10 +++ .../images/custom-scorecard-tests/main.go | 4 ++ internal/test/scorecard/clients.go | 2 +- internal/test/scorecard/common_utils.go | 65 ++++++++++++++----- internal/test/scorecard/tests.go | 41 +++++++++++- 8 files changed, 132 insertions(+), 28 deletions(-) diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index 8f7a96226..3198a60f0 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -54,7 +54,7 @@ metadata: capabilities: Seamless Upgrades categories: Monitoring, Developer Tools containerImage: quay.io/cryostat/cryostat-operator:2.5.0-dev - createdAt: "2024-03-07T15:43:22Z" + createdAt: "2024-03-13T15:52:10Z" description: JVM monitoring and profiling tool operatorframework.io/initialization-resource: |- { diff --git a/bundle/tests/scorecard/config.yaml b/bundle/tests/scorecard/config.yaml index 548b2d46f..25b90398c 100644 --- a/bundle/tests/scorecard/config.yaml +++ b/bundle/tests/scorecard/config.yaml @@ -70,7 +70,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - operator-install - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240307153901 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313145612 labels: suite: cryostat test: operator-install @@ -80,7 +80,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240307153901 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313145612 labels: suite: cryostat test: cryostat-cr @@ -90,7 +90,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-recording - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240307153901 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313145612 labels: suite: cryostat test: cryostat-recording @@ -100,13 +100,23 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-config-change - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240307153901 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313145612 labels: suite: cryostat test: cryostat-config-change storage: spec: mountPath: {} + - entrypoint: + - cryostat-scorecard-tests + - cryostat-report + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313145612 + labels: + suite: cryostat + test: cryostat-report + storage: + spec: + mountPath: {} storage: spec: mountPath: {} diff --git a/config/scorecard/patches/custom.config.yaml b/config/scorecard/patches/custom.config.yaml index 59b597ef0..f6e806ece 100644 --- a/config/scorecard/patches/custom.config.yaml +++ b/config/scorecard/patches/custom.config.yaml @@ -8,7 +8,7 @@ entrypoint: - cryostat-scorecard-tests - operator-install - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240307154322" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313155212" labels: suite: cryostat test: operator-install @@ -18,7 +18,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240307154322" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313155212" labels: suite: cryostat test: cryostat-cr @@ -28,7 +28,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-recording - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240307154322" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313155212" labels: suite: cryostat test: cryostat-recording @@ -38,7 +38,17 @@ entrypoint: - cryostat-scorecard-tests - cryostat-config-change - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240307154322" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313155212" labels: suite: cryostat test: cryostat-config-change +- op: add + path: /stages/1/tests/- + value: + entrypoint: + - cryostat-scorecard-tests + - cryostat-report + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313155212" + labels: + suite: cryostat + test: cryostat-report diff --git a/hack/custom.config.yaml.in b/hack/custom.config.yaml.in index b707766ac..4336abbe4 100644 --- a/hack/custom.config.yaml.in +++ b/hack/custom.config.yaml.in @@ -41,3 +41,13 @@ labels: suite: cryostat test: cryostat-config-change +- op: add + path: /stages/1/tests/- + value: + entrypoint: + - cryostat-scorecard-tests + - cryostat-report + image: "${CUSTOM_SCORECARD_IMG}" + labels: + suite: cryostat + test: cryostat-report diff --git a/internal/images/custom-scorecard-tests/main.go b/internal/images/custom-scorecard-tests/main.go index 6faed656e..b3041281c 100644 --- a/internal/images/custom-scorecard-tests/main.go +++ b/internal/images/custom-scorecard-tests/main.go @@ -81,6 +81,7 @@ func printValidTests() []scapiv1alpha3.TestResult { tests.CryostatCRTestName, tests.CryostatRecordingTestName, tests.CryostatConfigChangeTestName, + tests.CryostatReportTestName, }, ",")) result.Errors = append(result.Errors, str) @@ -94,6 +95,7 @@ func validateTests(testNames []string) bool { case tests.CryostatCRTestName: case tests.CryostatRecordingTestName: case tests.CryostatConfigChangeTestName: + case tests.CryostatReportTestName: default: return false } @@ -116,6 +118,8 @@ func runTests(testNames []string, bundle *apimanifests.Bundle, namespace string, results = append(results, tests.CryostatRecordingTest(bundle, namespace, openShiftCertManager)) case tests.CryostatConfigChangeTestName: results = append(results, tests.CryostatConfigChangeTest(bundle, namespace, openShiftCertManager)) + case tests.CryostatReportTestName: + results = append(results, tests.CryostatReportTest(bundle, namespace, openShiftCertManager)) default: log.Fatalf("unknown test found: %s", testName) } diff --git a/internal/test/scorecard/clients.go b/internal/test/scorecard/clients.go index 8f78d7a05..2c831e72f 100644 --- a/internal/test/scorecard/clients.go +++ b/internal/test/scorecard/clients.go @@ -587,7 +587,7 @@ func SendRequest(ctx context.Context, httpClient *http.Client, method string, ur // Create a new request req, err := NewHttpRequest(ctx, method, url, body, header) if err != nil { - return false, fmt.Errorf("failed to create a Cryostat REST request: %s", err.Error()) + return false, fmt.Errorf("failed to create an http request: %s", err.Error()) } resp, err := httpClient.Do(req) diff --git a/internal/test/scorecard/common_utils.go b/internal/test/scorecard/common_utils.go index c36dc720a..00933fdf6 100644 --- a/internal/test/scorecard/common_utils.go +++ b/internal/test/scorecard/common_utils.go @@ -17,7 +17,9 @@ package scorecard import ( "context" + "errors" "fmt" + "io" "net/http" "net/url" "time" @@ -338,6 +340,48 @@ func createAndWaitTillCryostatAvailable(cr *operatorv1beta1.Cryostat, resources } func waitTillCryostatReady(base *url.URL, resources *TestResources) error { + return sendHealthRequest(base, resources, func(resp *http.Response, r *scapiv1alpha3.TestResult) (done bool, err error) { + health := &HealthResponse{} + err = ReadJSON(resp, health) + if err != nil { + return false, fmt.Errorf("failed to read response body: %s", err.Error()) + } + + if err = health.Ready(); err != nil { + r.Log += fmt.Sprintf("application is not yet ready: %s\n", err.Error()) + return false, nil // Try again + } + + r.Log += fmt.Sprintf("application is ready at %s\n", base.String()) + return true, nil + }) +} + +func waitTillReportReady(name string, namespace string, port int32, resources *TestResources) error { + client := resources.Client + r := resources.TestResult + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + err := waitForDeploymentAvailability(ctx, client, namespace, name, r) + if err != nil { + return fmt.Errorf("report sidecar deployment did not become available: %s", err.Error()) + } + + reportsUrl := fmt.Sprintf("https://%s.%s.svc.cluster.local:%d", name, namespace, port) + base, err := url.Parse(reportsUrl) + if err != nil { + return fmt.Errorf("application URL is invalid: %s", err.Error()) + } + + return sendHealthRequest(base, resources, func(resp *http.Response, r *scapiv1alpha3.TestResult) (done bool, err error) { + r.Log += fmt.Sprintf("reports sidecar is ready at %s\n", base.String()) + return true, nil + }) +} + +func sendHealthRequest(base *url.URL, resources *TestResources, healthCheck func(resp *http.Response, r *scapiv1alpha3.TestResult) (done bool, err error)) error { client := NewHttpClient() r := resources.TestResult @@ -348,12 +392,15 @@ func waitTillCryostatReady(base *url.URL, resources *TestResources) error { url := base.JoinPath("/health") req, err := NewHttpRequest(ctx, http.MethodGet, url.String(), nil, make(http.Header)) if err != nil { - return false, fmt.Errorf("failed to create a Cryostat REST request: %s", err.Error()) + return false, fmt.Errorf("failed to create a an http request: %s", err.Error()) } req.Header.Add("Accept", "*/*") resp, err := client.Do(req) if err != nil { + if errors.Is(err, io.EOF) { + return false, nil // Retry + } return false, err } defer resp.Body.Close() @@ -365,22 +412,8 @@ func waitTillCryostatReady(base *url.URL, resources *TestResources) error { } return false, fmt.Errorf("API request failed with status code %d: %s", resp.StatusCode, ReadError(resp)) } - - health := &HealthResponse{} - err = ReadJSON(resp, health) - if err != nil { - return false, fmt.Errorf("failed to read response body: %s", err.Error()) - } - - if err = health.Ready(); err != nil { - r.Log += fmt.Sprintf("application is not yet ready: %s\n", err.Error()) - return false, nil // Try again - } - - r.Log += fmt.Sprintf("application is ready at %s\n", base.String()) - return true, nil + return healthCheck(resp, r) }) - return err } diff --git a/internal/test/scorecard/tests.go b/internal/test/scorecard/tests.go index 72bb48f1a..fed488d72 100644 --- a/internal/test/scorecard/tests.go +++ b/internal/test/scorecard/tests.go @@ -33,6 +33,7 @@ const ( CryostatCRTestName string = "cryostat-cr" CryostatRecordingTestName string = "cryostat-recording" CryostatConfigChangeTestName string = "cryostat-config-change" + CryostatReportTestName string = "cryostat-report" ) // OperatorInstallTest checks that the operator installed correctly @@ -97,7 +98,7 @@ func CryostatConfigChangeTest(bundle *apimanifests.Bundle, namespace string, ope if err != nil { return fail(*r, fmt.Sprintf("failed to determine application URL: %s", err.Error())) } - defer cleanupCryostat(r, tr.Client, CryostatRecordingTestName, namespace) + defer cleanupCryostat(r, tr.Client, CryostatConfigChangeTestName, namespace) // Switch Cryostat CR to PVC for redeployment ctx, cancel := context.WithTimeout(context.Background(), testTimeout) @@ -126,7 +127,7 @@ func CryostatConfigChangeTest(bundle *apimanifests.Bundle, namespace string, ope if err != nil { return fail(*r, fmt.Sprintf("Cryostat redeployment did not become available: %s", err.Error())) } - r.Log += "Cryostat deployment has successfully updated with new spec template" + r.Log += "Cryostat deployment has successfully updated with new spec template\n" base, err := url.Parse(cr.Status.ApplicationURL) if err != nil { @@ -278,3 +279,39 @@ func CryostatRecordingTest(bundle *apimanifests.Bundle, namespace string, openSh return *r } + +func CryostatReportTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) scapiv1alpha3.TestResult { + tr := newTestResources(CryostatReportTestName) + r := tr.TestResult + + err := setupCRTestResources(tr, openShiftCertManager) + if err != nil { + return fail(*r, fmt.Sprintf("failed to set up %s test: %s", CryostatReportTestName, err.Error())) + } + + port := int32(10000) + cr := newCryostatCR(CryostatReportTestName, namespace, !tr.OpenShift) + cr.Spec.ReportOptions = &operatorv1beta1.ReportConfiguration{ + Replicas: 1, + } + cr.Spec.ServiceOptions = &operatorv1beta1.ServiceConfigList{ + ReportsConfig: &operatorv1beta1.ReportsServiceConfig{ + HTTPPort: &port, + }, + } + + // Create a default Cryostat CR + cr, err = createAndWaitTillCryostatAvailable(cr, tr) + if err != nil { + return fail(*r, fmt.Sprintf("%s test failed: %s", CryostatReportTestName, err.Error())) + } + defer cleanupCryostat(r, tr.Client, CryostatReportTestName, namespace) + + // Query health of report sidecar + err = waitTillReportReady(cr.Name+"-reports", cr.Namespace, port, tr) + if err != nil { + return fail(*r, fmt.Sprintf("failed to reach the application: %s", err.Error())) + } + + return *r +} From 2201704822677f0e1ad94fee0d64250a7af2e67c Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Fri, 15 Mar 2024 17:47:05 -0400 Subject: [PATCH 06/16] fix(build-ci): fix scorecard image tag returned as null (#760) Signed-off-by: Thuan Vo Co-authored-by: Elliott Baron --- .github/workflows/build-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-ci.yml b/.github/workflows/build-ci.yml index 41b2959ef..4fcb43481 100644 --- a/.github/workflows/build-ci.yml +++ b/.github/workflows/build-ci.yml @@ -97,7 +97,7 @@ jobs: - name: Get scorecard image tag id: get-image-tag run: | - SCORECARD_TAG=$(yq '[.stages[0].tests[].image | capture("cryostat-operator-scorecard:(?P[\w.\-_]+)$")][0].tag' bundle/tests/scorecard/config.yaml) + SCORECARD_TAG=$(yq '[.stages[1].tests[].image | capture("cryostat-operator-scorecard:(?P[\w.\-_]+)$")][0].tag' bundle/tests/scorecard/config.yaml) echo "tag=$SCORECARD_TAG" >> $GITHUB_OUTPUT - name: Check if scorecard image tag already exists id: check-tag-exists From d01e0d283d958b6540c9319c964d50f56964a330 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Tue, 19 Mar 2024 16:51:17 -0400 Subject: [PATCH 07/16] test(scorecard): add container logs to scorecard results (#758) * test(scorecard): add container logs to scorecard results * build(bundle): regenerate bundle with new scorecard tags * chore(scorecard): refactor to remove duplicate codes --- ...yostat-operator.clusterserviceversion.yaml | 2 +- bundle/tests/scorecard/config.yaml | 10 +- config/scorecard/patches/custom.config.yaml | 10 +- .../rbac/scorecard_role.yaml | 7 ++ internal/test/scorecard/common_utils.go | 28 +++++ internal/test/scorecard/logger.go | 106 ++++++++++++++++++ internal/test/scorecard/tests.go | 8 +- 7 files changed, 159 insertions(+), 12 deletions(-) create mode 100644 internal/test/scorecard/logger.go diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index 3198a60f0..a867c9d96 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -54,7 +54,7 @@ metadata: capabilities: Seamless Upgrades categories: Monitoring, Developer Tools containerImage: quay.io/cryostat/cryostat-operator:2.5.0-dev - createdAt: "2024-03-13T15:52:10Z" + createdAt: "2024-03-18T06:48:08Z" description: JVM monitoring and profiling tool operatorframework.io/initialization-resource: |- { diff --git a/bundle/tests/scorecard/config.yaml b/bundle/tests/scorecard/config.yaml index 25b90398c..885b38c1f 100644 --- a/bundle/tests/scorecard/config.yaml +++ b/bundle/tests/scorecard/config.yaml @@ -70,7 +70,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - operator-install - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313145612 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240318064753 labels: suite: cryostat test: operator-install @@ -80,7 +80,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313145612 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240318064753 labels: suite: cryostat test: cryostat-cr @@ -90,7 +90,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-recording - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313145612 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240318064753 labels: suite: cryostat test: cryostat-recording @@ -100,7 +100,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-config-change - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313145612 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240318064753 labels: suite: cryostat test: cryostat-config-change @@ -110,7 +110,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-report - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313145612 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240318064753 labels: suite: cryostat test: cryostat-report diff --git a/config/scorecard/patches/custom.config.yaml b/config/scorecard/patches/custom.config.yaml index f6e806ece..d0ee315cb 100644 --- a/config/scorecard/patches/custom.config.yaml +++ b/config/scorecard/patches/custom.config.yaml @@ -8,7 +8,7 @@ entrypoint: - cryostat-scorecard-tests - operator-install - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313155212" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240318064753" labels: suite: cryostat test: operator-install @@ -18,7 +18,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313155212" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240318064753" labels: suite: cryostat test: cryostat-cr @@ -28,7 +28,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-recording - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313155212" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240318064753" labels: suite: cryostat test: cryostat-recording @@ -38,7 +38,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-config-change - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313155212" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240318064753" labels: suite: cryostat test: cryostat-config-change @@ -48,7 +48,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-report - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313155212" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240318064753" labels: suite: cryostat test: cryostat-report diff --git a/internal/images/custom-scorecard-tests/rbac/scorecard_role.yaml b/internal/images/custom-scorecard-tests/rbac/scorecard_role.yaml index d350e6464..7eaedd854 100644 --- a/internal/images/custom-scorecard-tests/rbac/scorecard_role.yaml +++ b/internal/images/custom-scorecard-tests/rbac/scorecard_role.yaml @@ -102,6 +102,13 @@ rules: - statefulsets verbs: - get +# Permissions to retrieve container logs +- apiGroups: + - "" + resources: + - pods/log + verbs: + - get --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/internal/test/scorecard/common_utils.go b/internal/test/scorecard/common_utils.go index 00933fdf6..a35a2f916 100644 --- a/internal/test/scorecard/common_utils.go +++ b/internal/test/scorecard/common_utils.go @@ -32,8 +32,10 @@ import ( netv1 "k8s.io/api/networking/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" ) @@ -497,3 +499,29 @@ func cleanupCryostat(r *scapiv1alpha3.TestResult, client *CryostatClientset, nam r.Log += fmt.Sprintf("failed to delete Cryostat: %s\n", err.Error()) } } + +func getCryostatPodNameForCR(clientset *kubernetes.Clientset, cr *operatorv1beta1.Cryostat) (string, error) { + selector := metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": cr.Name, + "component": "cryostat", + }, + } + opts := metav1.ListOptions{ + LabelSelector: labels.Set(selector.MatchLabels).String(), + } + + ctx, cancel := context.WithTimeout(context.TODO(), testTimeout) + defer cancel() + + pods, err := clientset.CoreV1().Pods(cr.Namespace).List(ctx, opts) + if err != nil { + return "", err + } + + if len(pods.Items) == 0 { + return "", fmt.Errorf("no matching cryostat pods for cr: %s", cr.Name) + } + + return pods.Items[0].ObjectMeta.Name, nil +} diff --git a/internal/test/scorecard/logger.go b/internal/test/scorecard/logger.go new file mode 100644 index 000000000..4cfd8a51f --- /dev/null +++ b/internal/test/scorecard/logger.go @@ -0,0 +1,106 @@ +// Copyright The Cryostat Authors. +// +// 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. + +package scorecard + +import ( + "context" + "fmt" + "io" + "strings" + + operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" + scapiv1alpha3 "github.com/operator-framework/api/pkg/apis/scorecard/v1alpha3" + v1 "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes" +) + +type ContainerLog struct { + Container string + Log string +} + +func LogContainer(clientset *kubernetes.Clientset, namespace, podName, containerName string, ch chan *ContainerLog) { + containerLog := &ContainerLog{ + Container: containerName, + } + buf := &strings.Builder{} + + err := GetContainerLogs(clientset, namespace, podName, containerName, buf) + if err != nil { + buf.WriteString(fmt.Sprintf("%s\n", err.Error())) + } + + containerLog.Log = buf.String() + ch <- containerLog +} + +func GetContainerLogs(clientset *kubernetes.Clientset, namespace, podName, containerName string, dest io.Writer) error { + ctx, cancel := context.WithTimeout(context.TODO(), testTimeout) + defer cancel() + + logOptions := &v1.PodLogOptions{ + Follow: true, + Container: containerName, + } + stream, err := clientset.CoreV1().Pods(namespace).GetLogs(podName, logOptions).Stream(ctx) + if err != nil { + return fmt.Errorf("failed to get logs for container %s in pod %s: %s", containerName, podName, err.Error()) + } + defer stream.Close() + + _, err = io.Copy(dest, stream) + if err != nil { + return fmt.Errorf("failed to store logs for container %s in pod %s: %s", containerName, podName, err.Error()) + } + return nil +} + +func CollectLogs(ch chan *ContainerLog) []*ContainerLog { + logs := make([]*ContainerLog, 0) + for i := 0; i < cap(ch); i++ { + logs = append(logs, <-ch) + } + return logs +} + +func CollectContainersLogsToResult(result *scapiv1alpha3.TestResult, ch chan *ContainerLog) { + logs := CollectLogs(ch) + for _, log := range logs { + if log != nil { + result.Log += fmt.Sprintf("%s CONTAINER LOG:\n\n\t%s\n", strings.ToUpper(log.Container), log.Log) + } + } +} + +func StartLogs(clientset *kubernetes.Clientset, cr *operatorv1beta1.Cryostat) (chan *ContainerLog, error) { + podName, err := getCryostatPodNameForCR(clientset, cr) + if err != nil { + return nil, fmt.Errorf("failed to get pod name for CR: %s", err.Error()) + } + + containerNames := []string{ + cr.Name, + cr.Name + "-grafana", + cr.Name + "-jfr-datasource", + } + + ch := make(chan *ContainerLog, len(containerNames)) + + for _, containerName := range containerNames { + go LogContainer(clientset, cr.Namespace, podName, containerName, ch) + } + + return ch, nil +} diff --git a/internal/test/scorecard/tests.go b/internal/test/scorecard/tests.go index fed488d72..88c5cb217 100644 --- a/internal/test/scorecard/tests.go +++ b/internal/test/scorecard/tests.go @@ -143,7 +143,7 @@ func CryostatConfigChangeTest(bundle *apimanifests.Bundle, namespace string, ope } // TODO add a built in discovery test too -func CryostatRecordingTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) scapiv1alpha3.TestResult { +func CryostatRecordingTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) (result scapiv1alpha3.TestResult) { tr := newTestResources(CryostatRecordingTestName) r := tr.TestResult @@ -157,6 +157,12 @@ func CryostatRecordingTest(bundle *apimanifests.Bundle, namespace string, openSh if err != nil { return fail(*r, fmt.Sprintf("failed to determine application URL: %s", err.Error())) } + ch, err := StartLogs(tr.Client.Clientset, cr) + if err != nil { + return fail(*r, fmt.Sprintf("failed to retrieve logs for the application: %s", err.Error())) + } + defer CollectContainersLogsToResult(&result, ch) + defer cleanupCryostat(r, tr.Client, CryostatRecordingTestName, namespace) base, err := url.Parse(cr.Status.ApplicationURL) From 6498b0f92236ceb6374cfeeedfc230c73ac0e67f Mon Sep 17 00:00:00 2001 From: Ming Yu Wang <90855268+mwangggg@users.noreply.github.com> Date: Tue, 19 Mar 2024 18:02:06 -0400 Subject: [PATCH 08/16] add permission to publish comment when ci fails (#769) Co-authored-by: Elliott Baron --- .github/workflows/test-ci-command.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test-ci-command.yml b/.github/workflows/test-ci-command.yml index 2e1ffce2b..559991df1 100644 --- a/.github/workflows/test-ci-command.yml +++ b/.github/workflows/test-ci-command.yml @@ -119,6 +119,8 @@ jobs: if: (always() && contains(needs.*.result, 'failure')) runs-on: ubuntu-latest needs: [run-test-jobs] + permissions: + pull-requests: write steps: - name: Leave Actions Run Comment uses: actions/github-script@v6 From 903e93fa58f34b6c2182944ee0c334d705997904 Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Wed, 27 Mar 2024 12:50:40 -0400 Subject: [PATCH 09/16] build(go): update Golang to 1.21 (#777) --- .github/workflows/test-ci-reusable.yml | 2 +- Dockerfile | 2 +- README.md | 2 +- go.mod | 2 +- go.sum | 2 ++ internal/images/custom-scorecard-tests/Dockerfile | 2 +- 6 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-ci-reusable.yml b/.github/workflows/test-ci-reusable.yml index 860971ac5..0eefabd1b 100644 --- a/.github/workflows/test-ci-reusable.yml +++ b/.github/workflows/test-ci-reusable.yml @@ -48,7 +48,7 @@ jobs: ref: ${{ inputs.ref }} - uses: actions/setup-go@v4 with: - go-version: '1.20.*' + go-version: '1.21.*' - name: Run controller tests run: make test-envtest - name: Set latest commit status as ${{ job.status }} diff --git a/Dockerfile b/Dockerfile index a50eb94d6..6cb9d7973 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM docker.io/library/golang:1.20 as builder +FROM docker.io/library/golang:1.21 as builder ARG TARGETOS ARG TARGETARCH diff --git a/README.md b/README.md index 69e03e0a9..acd009cf7 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ kubectl get secret ${CRYOSTAT_NAME}-jmx-auth -o jsonpath='{$.data.CRYOSTAT_RJMX_ # Building ## Requirements -- `go` v1.20 +- `go` v1.21 - [`operator-sdk`](https://github.com/operator-framework/operator-sdk) v1.31.0 - [`cert-manager`](https://github.com/cert-manager/cert-manager) v1.11.5+ (Recommended) - `podman` or `docker` diff --git a/go.mod b/go.mod index b9e111672..db9c8957d 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/cryostatio/cryostat-operator -go 1.20 +go 1.21 require ( github.com/blang/semver/v4 v4.0.0 diff --git a/go.sum b/go.sum index d6c3e38d1..0460050f6 100644 --- a/go.sum +++ b/go.sum @@ -287,6 +287,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= @@ -305,6 +306,7 @@ go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= diff --git a/internal/images/custom-scorecard-tests/Dockerfile b/internal/images/custom-scorecard-tests/Dockerfile index 101f5efdf..2dc8ec4dd 100644 --- a/internal/images/custom-scorecard-tests/Dockerfile +++ b/internal/images/custom-scorecard-tests/Dockerfile @@ -13,7 +13,7 @@ # limitations under the License. # Build the manager binary -FROM docker.io/library/golang:1.20 as builder +FROM docker.io/library/golang:1.21 as builder ARG TARGETOS ARG TARGETARCH From baebe17122b18cfef30d6b7259979a5c5ffec519 Mon Sep 17 00:00:00 2001 From: Ming Yu Wang <90855268+mwangggg@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:29:53 -0400 Subject: [PATCH 10/16] test(scorecard): logWorkloadEvent for cryostat-recording errors (#759) * logWorkLoadEvent for cryostat-recording errors * reviews * tr.LogChannel --------- Co-authored-by: Elliott Baron --- ...yostat-operator.clusterserviceversion.yaml | 2 +- bundle/tests/scorecard/config.yaml | 10 ++--- config/scorecard/patches/custom.config.yaml | 10 ++--- internal/test/scorecard/common_utils.go | 37 ++++++++++++++++--- internal/test/scorecard/logger.go | 2 +- internal/test/scorecard/tests.go | 23 +++++------- 6 files changed, 53 insertions(+), 31 deletions(-) diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index a867c9d96..10b9cdde5 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -54,7 +54,7 @@ metadata: capabilities: Seamless Upgrades categories: Monitoring, Developer Tools containerImage: quay.io/cryostat/cryostat-operator:2.5.0-dev - createdAt: "2024-03-18T06:48:08Z" + createdAt: "2024-03-27T15:40:00Z" description: JVM monitoring and profiling tool operatorframework.io/initialization-resource: |- { diff --git a/bundle/tests/scorecard/config.yaml b/bundle/tests/scorecard/config.yaml index 885b38c1f..d9f8eb421 100644 --- a/bundle/tests/scorecard/config.yaml +++ b/bundle/tests/scorecard/config.yaml @@ -70,7 +70,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - operator-install - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240318064753 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327153853 labels: suite: cryostat test: operator-install @@ -80,7 +80,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240318064753 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327153853 labels: suite: cryostat test: cryostat-cr @@ -90,7 +90,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-recording - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240318064753 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327153853 labels: suite: cryostat test: cryostat-recording @@ -100,7 +100,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-config-change - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240318064753 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327153853 labels: suite: cryostat test: cryostat-config-change @@ -110,7 +110,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-report - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240318064753 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327153853 labels: suite: cryostat test: cryostat-report diff --git a/config/scorecard/patches/custom.config.yaml b/config/scorecard/patches/custom.config.yaml index d0ee315cb..a1609840e 100644 --- a/config/scorecard/patches/custom.config.yaml +++ b/config/scorecard/patches/custom.config.yaml @@ -8,7 +8,7 @@ entrypoint: - cryostat-scorecard-tests - operator-install - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240318064753" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327153853" labels: suite: cryostat test: operator-install @@ -18,7 +18,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240318064753" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327153853" labels: suite: cryostat test: cryostat-cr @@ -28,7 +28,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-recording - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240318064753" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327153853" labels: suite: cryostat test: cryostat-recording @@ -38,7 +38,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-config-change - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240318064753" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327153853" labels: suite: cryostat test: cryostat-config-change @@ -48,7 +48,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-report - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240318064753" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327153853" labels: suite: cryostat test: cryostat-report diff --git a/internal/test/scorecard/common_utils.go b/internal/test/scorecard/common_utils.go index a35a2f916..ea134a2ac 100644 --- a/internal/test/scorecard/common_utils.go +++ b/internal/test/scorecard/common_utils.go @@ -45,8 +45,9 @@ const ( ) type TestResources struct { - OpenShift bool - Client *CryostatClientset + OpenShift bool + Client *CryostatClientset + LogChannel chan *ContainerLog *scapiv1alpha3.TestResult } @@ -101,6 +102,9 @@ func logWorkloadEvents(r *scapiv1alpha3.TestResult, client *CryostatClientset, n ctx := context.Background() deploy, err := client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { + if kerrors.IsNotFound(err) { + return nil + } return err } // Log deployment conditions and events @@ -177,6 +181,18 @@ func logEvents(r *scapiv1alpha3.TestResult, client *CryostatClientset, namespace return nil } +func LogWorkloadEventsOnError(r *scapiv1alpha3.TestResult, client *CryostatClientset, namespace string, name string) { + if len(r.Errors) > 0 { + r.Log += "\nWORKLOAD EVENTS:\n" + for _, deployName := range []string{name, name + "-reports"} { + logErr := logWorkloadEvents(r, client, namespace, deployName) + if logErr != nil { + r.Log += fmt.Sprintf("failed to get workload logs: %s", logErr) + } + } + } +} + func newEmptyTestResult(testName string) *scapiv1alpha3.TestResult { return &scapiv1alpha3.TestResult{ Name: testName, @@ -485,7 +501,11 @@ func updateAndWaitTillCryostatAvailable(cr *operatorv1beta1.Cryostat, resources return err } -func cleanupCryostat(r *scapiv1alpha3.TestResult, client *CryostatClientset, name string, namespace string) { +func cleanupAndLogs(r *scapiv1alpha3.TestResult, tr *TestResources, name string, namespace string) { + client := tr.Client + + LogWorkloadEventsOnError(r, client, namespace, name) + cr := &operatorv1beta1.Cryostat{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -493,10 +513,15 @@ func cleanupCryostat(r *scapiv1alpha3.TestResult, client *CryostatClientset, nam }, } ctx := context.Background() - err := client.OperatorCRDs().Cryostats(cr.Namespace).Delete(ctx, - cr.Name, &metav1.DeleteOptions{}) + err := client.OperatorCRDs().Cryostats(cr.Namespace).Delete(ctx, cr.Name, &metav1.DeleteOptions{}) if err != nil { - r.Log += fmt.Sprintf("failed to delete Cryostat: %s\n", err.Error()) + if !kerrors.IsNotFound(err) { + r.Log += fmt.Sprintf("failed to delete Cryostat: %s\n", err.Error()) + } + } + + if tr.LogChannel != nil { + CollectContainersLogsToResult(r, tr.LogChannel) } } diff --git a/internal/test/scorecard/logger.go b/internal/test/scorecard/logger.go index 4cfd8a51f..6b02761e0 100644 --- a/internal/test/scorecard/logger.go +++ b/internal/test/scorecard/logger.go @@ -79,7 +79,7 @@ func CollectContainersLogsToResult(result *scapiv1alpha3.TestResult, ch chan *Co logs := CollectLogs(ch) for _, log := range logs { if log != nil { - result.Log += fmt.Sprintf("%s CONTAINER LOG:\n\n\t%s\n", strings.ToUpper(log.Container), log.Log) + result.Log += fmt.Sprintf("\n%s CONTAINER LOG:\n\n\t%s\n", strings.ToUpper(log.Container), log.Log) } } } diff --git a/internal/test/scorecard/tests.go b/internal/test/scorecard/tests.go index 88c5cb217..12a8bf2a3 100644 --- a/internal/test/scorecard/tests.go +++ b/internal/test/scorecard/tests.go @@ -37,7 +37,7 @@ const ( ) // OperatorInstallTest checks that the operator installed correctly -func OperatorInstallTest(bundle *apimanifests.Bundle, namespace string) scapiv1alpha3.TestResult { +func OperatorInstallTest(bundle *apimanifests.Bundle, namespace string) (result scapiv1alpha3.TestResult) { r := newEmptyTestResult(OperatorInstallTestName) // Create a new Kubernetes REST client for this test @@ -58,7 +58,7 @@ func OperatorInstallTest(bundle *apimanifests.Bundle, namespace string) scapiv1a } // CryostatCRTest checks that the operator installs Cryostat in response to a Cryostat CR -func CryostatCRTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) scapiv1alpha3.TestResult { +func CryostatCRTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) (result scapiv1alpha3.TestResult) { tr := newTestResources(CryostatCRTestName) r := tr.TestResult @@ -66,18 +66,17 @@ func CryostatCRTest(bundle *apimanifests.Bundle, namespace string, openShiftCert if err != nil { return fail(*r, fmt.Sprintf("failed to set up %s test: %s", CryostatCRTestName, err.Error())) } + defer cleanupAndLogs(&result, tr, CryostatCRTestName, namespace) // Create a default Cryostat CR _, err = createAndWaitTillCryostatAvailable(newCryostatCR(CryostatCRTestName, namespace, !tr.OpenShift), tr) if err != nil { return fail(*r, fmt.Sprintf("%s test failed: %s", CryostatCRTestName, err.Error())) } - defer cleanupCryostat(r, tr.Client, CryostatCRTestName, namespace) - return *r } -func CryostatConfigChangeTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) scapiv1alpha3.TestResult { +func CryostatConfigChangeTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) (result scapiv1alpha3.TestResult) { tr := newTestResources(CryostatConfigChangeTestName) r := tr.TestResult @@ -85,6 +84,7 @@ func CryostatConfigChangeTest(bundle *apimanifests.Bundle, namespace string, ope if err != nil { return fail(*r, fmt.Sprintf("failed to set up %s test: %s", CryostatConfigChangeTestName, err.Error())) } + defer cleanupAndLogs(&result, tr, CryostatConfigChangeTestName, namespace) // Create a default Cryostat CR with default empty dir cr := newCryostatCR(CryostatConfigChangeTestName, namespace, !tr.OpenShift) @@ -98,7 +98,6 @@ func CryostatConfigChangeTest(bundle *apimanifests.Bundle, namespace string, ope if err != nil { return fail(*r, fmt.Sprintf("failed to determine application URL: %s", err.Error())) } - defer cleanupCryostat(r, tr.Client, CryostatConfigChangeTestName, namespace) // Switch Cryostat CR to PVC for redeployment ctx, cancel := context.WithTimeout(context.Background(), testTimeout) @@ -151,19 +150,17 @@ func CryostatRecordingTest(bundle *apimanifests.Bundle, namespace string, openSh if err != nil { return fail(*r, fmt.Sprintf("failed to set up %s test: %s", CryostatRecordingTestName, err.Error())) } + defer cleanupAndLogs(&result, tr, CryostatRecordingTestName, namespace) // Create a default Cryostat CR cr, err := createAndWaitTillCryostatAvailable(newCryostatCR(CryostatRecordingTestName, namespace, !tr.OpenShift), tr) if err != nil { return fail(*r, fmt.Sprintf("failed to determine application URL: %s", err.Error())) } - ch, err := StartLogs(tr.Client.Clientset, cr) + tr.LogChannel, err = StartLogs(tr.Client.Clientset, cr) if err != nil { - return fail(*r, fmt.Sprintf("failed to retrieve logs for the application: %s", err.Error())) + r.Log += fmt.Sprintf("failed to retrieve logs for the application: %s", err.Error()) } - defer CollectContainersLogsToResult(&result, ch) - - defer cleanupCryostat(r, tr.Client, CryostatRecordingTestName, namespace) base, err := url.Parse(cr.Status.ApplicationURL) if err != nil { @@ -286,7 +283,7 @@ func CryostatRecordingTest(bundle *apimanifests.Bundle, namespace string, openSh return *r } -func CryostatReportTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) scapiv1alpha3.TestResult { +func CryostatReportTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) (result scapiv1alpha3.TestResult) { tr := newTestResources(CryostatReportTestName) r := tr.TestResult @@ -294,6 +291,7 @@ func CryostatReportTest(bundle *apimanifests.Bundle, namespace string, openShift if err != nil { return fail(*r, fmt.Sprintf("failed to set up %s test: %s", CryostatReportTestName, err.Error())) } + defer cleanupAndLogs(&result, tr, CryostatReportTestName, namespace) port := int32(10000) cr := newCryostatCR(CryostatReportTestName, namespace, !tr.OpenShift) @@ -311,7 +309,6 @@ func CryostatReportTest(bundle *apimanifests.Bundle, namespace string, openShift if err != nil { return fail(*r, fmt.Sprintf("%s test failed: %s", CryostatReportTestName, err.Error())) } - defer cleanupCryostat(r, tr.Client, CryostatReportTestName, namespace) // Query health of report sidecar err = waitTillReportReady(cr.Name+"-reports", cr.Namespace, port, tr) From b3970953d2bfcc2cf2add227a8a0687f6a65b52c Mon Sep 17 00:00:00 2001 From: Ming Yu Wang <90855268+mwangggg@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:18:16 -0400 Subject: [PATCH 11/16] test(scorecard): fix rebasing skipped commit (#780) * Merge pull request #8 from ebaron/scorecard-methods test(scorecard): use methods for more easily passing data * update bundle image --- ...yostat-operator.clusterserviceversion.yaml | 2 +- bundle/tests/scorecard/config.yaml | 10 +- config/scorecard/patches/custom.config.yaml | 10 +- .../images/custom-scorecard-tests/main.go | 10 +- internal/test/scorecard/common_utils.go | 113 ++++++-------- internal/test/scorecard/logger.go | 36 +++-- internal/test/scorecard/openshift.go | 3 +- internal/test/scorecard/tests.go | 142 +++++++++--------- 8 files changed, 151 insertions(+), 175 deletions(-) diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index 10b9cdde5..960e558f9 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -54,7 +54,7 @@ metadata: capabilities: Seamless Upgrades categories: Monitoring, Developer Tools containerImage: quay.io/cryostat/cryostat-operator:2.5.0-dev - createdAt: "2024-03-27T15:40:00Z" + createdAt: "2024-03-27T17:54:03Z" description: JVM monitoring and profiling tool operatorframework.io/initialization-resource: |- { diff --git a/bundle/tests/scorecard/config.yaml b/bundle/tests/scorecard/config.yaml index d9f8eb421..ae53496a9 100644 --- a/bundle/tests/scorecard/config.yaml +++ b/bundle/tests/scorecard/config.yaml @@ -70,7 +70,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - operator-install - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327153853 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240326210241 labels: suite: cryostat test: operator-install @@ -80,7 +80,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327153853 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240326210241 labels: suite: cryostat test: cryostat-cr @@ -90,7 +90,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-recording - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327153853 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240326210241 labels: suite: cryostat test: cryostat-recording @@ -100,7 +100,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-config-change - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327153853 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240326210241 labels: suite: cryostat test: cryostat-config-change @@ -110,7 +110,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-report - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327153853 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240326210241 labels: suite: cryostat test: cryostat-report diff --git a/config/scorecard/patches/custom.config.yaml b/config/scorecard/patches/custom.config.yaml index a1609840e..4bc52ec07 100644 --- a/config/scorecard/patches/custom.config.yaml +++ b/config/scorecard/patches/custom.config.yaml @@ -8,7 +8,7 @@ entrypoint: - cryostat-scorecard-tests - operator-install - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327153853" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327175405" labels: suite: cryostat test: operator-install @@ -18,7 +18,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327153853" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327175405" labels: suite: cryostat test: cryostat-cr @@ -28,7 +28,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-recording - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327153853" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327175405" labels: suite: cryostat test: cryostat-recording @@ -38,7 +38,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-config-change - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327153853" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327175405" labels: suite: cryostat test: cryostat-config-change @@ -48,7 +48,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-report - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327153853" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327175405" labels: suite: cryostat test: cryostat-report diff --git a/internal/images/custom-scorecard-tests/main.go b/internal/images/custom-scorecard-tests/main.go index b3041281c..cce16f5a6 100644 --- a/internal/images/custom-scorecard-tests/main.go +++ b/internal/images/custom-scorecard-tests/main.go @@ -111,15 +111,15 @@ func runTests(testNames []string, bundle *apimanifests.Bundle, namespace string, for _, testName := range testNames { switch testName { case tests.OperatorInstallTestName: - results = append(results, tests.OperatorInstallTest(bundle, namespace)) + results = append(results, *tests.OperatorInstallTest(bundle, namespace, openShiftCertManager)) case tests.CryostatCRTestName: - results = append(results, tests.CryostatCRTest(bundle, namespace, openShiftCertManager)) + results = append(results, *tests.CryostatCRTest(bundle, namespace, openShiftCertManager)) case tests.CryostatRecordingTestName: - results = append(results, tests.CryostatRecordingTest(bundle, namespace, openShiftCertManager)) + results = append(results, *tests.CryostatRecordingTest(bundle, namespace, openShiftCertManager)) case tests.CryostatConfigChangeTestName: - results = append(results, tests.CryostatConfigChangeTest(bundle, namespace, openShiftCertManager)) + results = append(results, *tests.CryostatConfigChangeTest(bundle, namespace, openShiftCertManager)) case tests.CryostatReportTestName: - results = append(results, tests.CryostatReportTest(bundle, namespace, openShiftCertManager)) + results = append(results, *tests.CryostatReportTest(bundle, namespace, openShiftCertManager)) default: log.Fatalf("unknown test found: %s", testName) } diff --git a/internal/test/scorecard/common_utils.go b/internal/test/scorecard/common_utils.go index ea134a2ac..99c639ee0 100644 --- a/internal/test/scorecard/common_utils.go +++ b/internal/test/scorecard/common_utils.go @@ -35,7 +35,6 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" ) @@ -51,10 +50,9 @@ type TestResources struct { *scapiv1alpha3.TestResult } -func waitForDeploymentAvailability(ctx context.Context, client *CryostatClientset, namespace string, - name string, r *scapiv1alpha3.TestResult) error { +func (r *TestResources) waitForDeploymentAvailability(ctx context.Context, namespace string, name string) error { err := wait.PollImmediateUntilWithContext(ctx, time.Second, func(ctx context.Context) (done bool, err error) { - deploy, err := client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{}) + deploy, err := r.Client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { if kerrors.IsNotFound(err) { r.Log += fmt.Sprintf("deployment %s is not yet found\n", name) @@ -79,7 +77,7 @@ func waitForDeploymentAvailability(ctx context.Context, client *CryostatClientse return false, nil }) if err != nil { - logErr := logWorkloadEvents(r, client, namespace, name) + logErr := r.logWorkloadEvents(namespace, name) if logErr != nil { r.Log += fmt.Sprintf("failed to look up deployment errors: %s\n", logErr.Error()) } @@ -87,20 +85,20 @@ func waitForDeploymentAvailability(ctx context.Context, client *CryostatClientse return err } -func logError(r *scapiv1alpha3.TestResult, message string) { +func (r *TestResources) logError(message string) { r.State = scapiv1alpha3.FailState r.Errors = append(r.Errors, message) } -func fail(r scapiv1alpha3.TestResult, message string) scapiv1alpha3.TestResult { +func (r *TestResources) fail(message string) *scapiv1alpha3.TestResult { r.State = scapiv1alpha3.FailState r.Errors = append(r.Errors, message) - return r + return r.TestResult } -func logWorkloadEvents(r *scapiv1alpha3.TestResult, client *CryostatClientset, namespace string, name string) error { +func (r *TestResources) logWorkloadEvents(namespace string, name string) error { ctx := context.Background() - deploy, err := client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{}) + deploy, err := r.Client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { if kerrors.IsNotFound(err) { return nil @@ -115,7 +113,7 @@ func logWorkloadEvents(r *scapiv1alpha3.TestResult, client *CryostatClientset, n } r.Log += fmt.Sprintf("deployment %s warning events:\n", deploy.Name) - err = logEvents(r, client, namespace, scheme.Scheme, deploy) + err = r.logEvents(namespace, scheme.Scheme, deploy) if err != nil { return err } @@ -125,7 +123,7 @@ func logWorkloadEvents(r *scapiv1alpha3.TestResult, client *CryostatClientset, n if err != nil { return err } - replicaSets, err := client.AppsV1().ReplicaSets(namespace).List(ctx, metav1.ListOptions{ + replicaSets, err := r.Client.AppsV1().ReplicaSets(namespace).List(ctx, metav1.ListOptions{ LabelSelector: selector.String(), }) if err != nil { @@ -138,14 +136,14 @@ func logWorkloadEvents(r *scapiv1alpha3.TestResult, client *CryostatClientset, n condition.Reason, condition.Message) } r.Log += fmt.Sprintf("replica set %s warning events:\n", rs.Name) - err = logEvents(r, client, namespace, scheme.Scheme, &rs) + err = r.logEvents(namespace, scheme.Scheme, &rs) if err != nil { return err } } // Look up pods for deployment and log conditions and events - pods, err := client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{ + pods, err := r.Client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{ LabelSelector: selector.String(), }) if err != nil { @@ -159,7 +157,7 @@ func logWorkloadEvents(r *scapiv1alpha3.TestResult, client *CryostatClientset, n condition.Reason, condition.Message) } r.Log += fmt.Sprintf("pod %s warning events:\n", pod.Name) - err = logEvents(r, client, namespace, scheme.Scheme, &pod) + err = r.logEvents(namespace, scheme.Scheme, &pod) if err != nil { return err } @@ -167,9 +165,8 @@ func logWorkloadEvents(r *scapiv1alpha3.TestResult, client *CryostatClientset, n return nil } -func logEvents(r *scapiv1alpha3.TestResult, client *CryostatClientset, namespace string, - scheme *runtime.Scheme, obj runtime.Object) error { - events, err := client.CoreV1().Events(namespace).Search(scheme, obj) +func (r *TestResources) logEvents(namespace string, scheme *runtime.Scheme, obj runtime.Object) error { + events, err := r.Client.CoreV1().Events(namespace).Search(scheme, obj) if err != nil { return err } @@ -181,11 +178,11 @@ func logEvents(r *scapiv1alpha3.TestResult, client *CryostatClientset, namespace return nil } -func LogWorkloadEventsOnError(r *scapiv1alpha3.TestResult, client *CryostatClientset, namespace string, name string) { +func (r *TestResources) LogWorkloadEventsOnError(namespace string, name string) { if len(r.Errors) > 0 { r.Log += "\nWORKLOAD EVENTS:\n" for _, deployName := range []string{name, name + "-reports"} { - logErr := logWorkloadEvents(r, client, namespace, deployName) + logErr := r.logWorkloadEvents(namespace, deployName) if logErr != nil { r.Log += fmt.Sprintf("failed to get workload logs: %s", logErr) } @@ -208,28 +205,26 @@ func newTestResources(testName string) *TestResources { } } -func setupCRTestResources(tr *TestResources, openShiftCertManager bool) error { - r := tr.TestResult - +func (r *TestResources) setupCRTestResources(openShiftCertManager bool) error { // Create a new Kubernetes REST client for this test client, err := NewClientset() if err != nil { - logError(r, fmt.Sprintf("failed to create client: %s", err.Error())) + r.logError(fmt.Sprintf("failed to create client: %s", err.Error())) return err } - tr.Client = client + r.Client = client openshift, err := isOpenShift(client) if err != nil { - logError(r, fmt.Sprintf("could not determine whether platform is OpenShift: %s", err.Error())) + r.logError(fmt.Sprintf("could not determine whether platform is OpenShift: %s", err.Error())) return err } - tr.OpenShift = openshift + r.OpenShift = openshift if openshift && openShiftCertManager { - err := installOpenShiftCertManager(r) + err := r.installOpenShiftCertManager() if err != nil { - logError(r, fmt.Sprintf("failed to install cert-manager Operator for Red Hat OpenShift: %s", err.Error())) + r.logError(fmt.Sprintf("failed to install cert-manager Operator for Red Hat OpenShift: %s", err.Error())) return err } } @@ -318,27 +313,24 @@ func newCryostatCR(name string, namespace string, withIngress bool) *operatorv1b return cr } -func createAndWaitTillCryostatAvailable(cr *operatorv1beta1.Cryostat, resources *TestResources) (*operatorv1beta1.Cryostat, error) { - client := resources.Client - r := resources.TestResult - - cr, err := client.OperatorCRDs().Cryostats(cr.Namespace).Create(context.Background(), cr) +func (r *TestResources) createAndWaitTillCryostatAvailable(cr *operatorv1beta1.Cryostat) (*operatorv1beta1.Cryostat, error) { + cr, err := r.Client.OperatorCRDs().Cryostats(cr.Namespace).Create(context.Background(), cr) if err != nil { - logError(r, fmt.Sprintf("failed to create Cryostat CR: %s", err.Error())) + r.logError(fmt.Sprintf("failed to create Cryostat CR: %s", err.Error())) return nil, err } // Poll the deployment until it becomes available or we timeout ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - err = waitForDeploymentAvailability(ctx, client, cr.Namespace, cr.Name, r) + err = r.waitForDeploymentAvailability(ctx, cr.Namespace, cr.Name) if err != nil { - logError(r, fmt.Sprintf("Cryostat main deployment did not become available: %s", err.Error())) + r.logError(fmt.Sprintf("Cryostat main deployment did not become available: %s", err.Error())) return nil, err } err = wait.PollImmediateUntilWithContext(ctx, time.Second, func(ctx context.Context) (done bool, err error) { - cr, err = client.OperatorCRDs().Cryostats(cr.Namespace).Get(ctx, cr.Name) + cr, err = r.Client.OperatorCRDs().Cryostats(cr.Namespace).Get(ctx, cr.Name) if err != nil { return false, fmt.Errorf("failed to get Cryostat CR: %s", err.Error()) } @@ -349,7 +341,7 @@ func createAndWaitTillCryostatAvailable(cr *operatorv1beta1.Cryostat, resources return false, nil }) if err != nil { - logError(r, fmt.Sprintf("application URL not found in CR: %s", err.Error())) + r.logError(fmt.Sprintf("application URL not found in CR: %s", err.Error())) return nil, err } r.Log += fmt.Sprintf("application is available at %s\n", cr.Status.ApplicationURL) @@ -357,8 +349,8 @@ func createAndWaitTillCryostatAvailable(cr *operatorv1beta1.Cryostat, resources return cr, nil } -func waitTillCryostatReady(base *url.URL, resources *TestResources) error { - return sendHealthRequest(base, resources, func(resp *http.Response, r *scapiv1alpha3.TestResult) (done bool, err error) { +func (r *TestResources) waitTillCryostatReady(base *url.URL) error { + return r.sendHealthRequest(base, func(resp *http.Response, r *scapiv1alpha3.TestResult) (done bool, err error) { health := &HealthResponse{} err = ReadJSON(resp, health) if err != nil { @@ -375,14 +367,11 @@ func waitTillCryostatReady(base *url.URL, resources *TestResources) error { }) } -func waitTillReportReady(name string, namespace string, port int32, resources *TestResources) error { - client := resources.Client - r := resources.TestResult - +func (r *TestResources) waitTillReportReady(name string, namespace string, port int32) error { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - err := waitForDeploymentAvailability(ctx, client, namespace, name, r) + err := r.waitForDeploymentAvailability(ctx, namespace, name) if err != nil { return fmt.Errorf("report sidecar deployment did not become available: %s", err.Error()) } @@ -393,15 +382,14 @@ func waitTillReportReady(name string, namespace string, port int32, resources *T return fmt.Errorf("application URL is invalid: %s", err.Error()) } - return sendHealthRequest(base, resources, func(resp *http.Response, r *scapiv1alpha3.TestResult) (done bool, err error) { + return r.sendHealthRequest(base, func(resp *http.Response, r *scapiv1alpha3.TestResult) (done bool, err error) { r.Log += fmt.Sprintf("reports sidecar is ready at %s\n", base.String()) return true, nil }) } -func sendHealthRequest(base *url.URL, resources *TestResources, healthCheck func(resp *http.Response, r *scapiv1alpha3.TestResult) (done bool, err error)) error { +func (r *TestResources) sendHealthRequest(base *url.URL, healthCheck func(resp *http.Response, r *scapiv1alpha3.TestResult) (done bool, err error)) error { client := NewHttpClient() - r := resources.TestResult ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() @@ -430,16 +418,13 @@ func sendHealthRequest(base *url.URL, resources *TestResources, healthCheck func } return false, fmt.Errorf("API request failed with status code %d: %s", resp.StatusCode, ReadError(resp)) } - return healthCheck(resp, r) + return healthCheck(resp, r.TestResult) }) return err } -func updateAndWaitTillCryostatAvailable(cr *operatorv1beta1.Cryostat, resources *TestResources) error { - client := resources.Client - r := resources.TestResult - - cr, err := client.OperatorCRDs().Cryostats(cr.Namespace).Update(context.Background(), cr) +func (r *TestResources) updateAndWaitTillCryostatAvailable(cr *operatorv1beta1.Cryostat) error { + cr, err := r.Client.OperatorCRDs().Cryostats(cr.Namespace).Update(context.Background(), cr) if err != nil { r.Log += fmt.Sprintf("failed to update Cryostat CR: %s", err.Error()) return err @@ -449,7 +434,7 @@ func updateAndWaitTillCryostatAvailable(cr *operatorv1beta1.Cryostat, resources ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() err = wait.PollImmediateUntilWithContext(ctx, time.Second, func(ctx context.Context) (done bool, err error) { - deploy, err := client.AppsV1().Deployments(cr.Namespace).Get(ctx, cr.Name, metav1.GetOptions{}) + deploy, err := r.Client.AppsV1().Deployments(cr.Namespace).Get(ctx, cr.Name, metav1.GetOptions{}) if err != nil { if kerrors.IsNotFound(err) { r.Log += fmt.Sprintf("deployment %s is not yet found\n", cr.Name) @@ -501,10 +486,8 @@ func updateAndWaitTillCryostatAvailable(cr *operatorv1beta1.Cryostat, resources return err } -func cleanupAndLogs(r *scapiv1alpha3.TestResult, tr *TestResources, name string, namespace string) { - client := tr.Client - - LogWorkloadEventsOnError(r, client, namespace, name) +func (r *TestResources) cleanupAndLogs(name string, namespace string) { + r.LogWorkloadEventsOnError(namespace, name) cr := &operatorv1beta1.Cryostat{ ObjectMeta: metav1.ObjectMeta{ @@ -513,19 +496,19 @@ func cleanupAndLogs(r *scapiv1alpha3.TestResult, tr *TestResources, name string, }, } ctx := context.Background() - err := client.OperatorCRDs().Cryostats(cr.Namespace).Delete(ctx, cr.Name, &metav1.DeleteOptions{}) + err := r.Client.OperatorCRDs().Cryostats(cr.Namespace).Delete(ctx, cr.Name, &metav1.DeleteOptions{}) if err != nil { if !kerrors.IsNotFound(err) { r.Log += fmt.Sprintf("failed to delete Cryostat: %s\n", err.Error()) } } - if tr.LogChannel != nil { - CollectContainersLogsToResult(r, tr.LogChannel) + if r.LogChannel != nil { + r.CollectContainersLogsToResult() } } -func getCryostatPodNameForCR(clientset *kubernetes.Clientset, cr *operatorv1beta1.Cryostat) (string, error) { +func (r *TestResources) getCryostatPodNameForCR(cr *operatorv1beta1.Cryostat) (string, error) { selector := metav1.LabelSelector{ MatchLabels: map[string]string{ "app": cr.Name, @@ -539,7 +522,7 @@ func getCryostatPodNameForCR(clientset *kubernetes.Clientset, cr *operatorv1beta ctx, cancel := context.WithTimeout(context.TODO(), testTimeout) defer cancel() - pods, err := clientset.CoreV1().Pods(cr.Namespace).List(ctx, opts) + pods, err := r.Client.CoreV1().Pods(cr.Namespace).List(ctx, opts) if err != nil { return "", err } diff --git a/internal/test/scorecard/logger.go b/internal/test/scorecard/logger.go index 6b02761e0..dd38d79b7 100644 --- a/internal/test/scorecard/logger.go +++ b/internal/test/scorecard/logger.go @@ -21,9 +21,7 @@ import ( "strings" operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" - scapiv1alpha3 "github.com/operator-framework/api/pkg/apis/scorecard/v1alpha3" v1 "k8s.io/api/core/v1" - "k8s.io/client-go/kubernetes" ) type ContainerLog struct { @@ -31,22 +29,22 @@ type ContainerLog struct { Log string } -func LogContainer(clientset *kubernetes.Clientset, namespace, podName, containerName string, ch chan *ContainerLog) { +func (r *TestResources) logContainer(namespace, podName, containerName string) { containerLog := &ContainerLog{ Container: containerName, } buf := &strings.Builder{} - err := GetContainerLogs(clientset, namespace, podName, containerName, buf) + err := r.GetContainerLogs(namespace, podName, containerName, buf) if err != nil { buf.WriteString(fmt.Sprintf("%s\n", err.Error())) } containerLog.Log = buf.String() - ch <- containerLog + r.LogChannel <- containerLog } -func GetContainerLogs(clientset *kubernetes.Clientset, namespace, podName, containerName string, dest io.Writer) error { +func (r *TestResources) GetContainerLogs(namespace, podName, containerName string, dest io.Writer) error { ctx, cancel := context.WithTimeout(context.TODO(), testTimeout) defer cancel() @@ -54,7 +52,7 @@ func GetContainerLogs(clientset *kubernetes.Clientset, namespace, podName, conta Follow: true, Container: containerName, } - stream, err := clientset.CoreV1().Pods(namespace).GetLogs(podName, logOptions).Stream(ctx) + stream, err := r.Client.CoreV1().Pods(namespace).GetLogs(podName, logOptions).Stream(ctx) if err != nil { return fmt.Errorf("failed to get logs for container %s in pod %s: %s", containerName, podName, err.Error()) } @@ -67,27 +65,27 @@ func GetContainerLogs(clientset *kubernetes.Clientset, namespace, podName, conta return nil } -func CollectLogs(ch chan *ContainerLog) []*ContainerLog { +func (r *TestResources) CollectLogs() []*ContainerLog { logs := make([]*ContainerLog, 0) - for i := 0; i < cap(ch); i++ { - logs = append(logs, <-ch) + for i := 0; i < cap(r.LogChannel); i++ { + logs = append(logs, <-r.LogChannel) } return logs } -func CollectContainersLogsToResult(result *scapiv1alpha3.TestResult, ch chan *ContainerLog) { - logs := CollectLogs(ch) +func (r *TestResources) CollectContainersLogsToResult() { + logs := r.CollectLogs() for _, log := range logs { if log != nil { - result.Log += fmt.Sprintf("\n%s CONTAINER LOG:\n\n\t%s\n", strings.ToUpper(log.Container), log.Log) + r.Log += fmt.Sprintf("\n%s CONTAINER LOG:\n\n\t%s\n", strings.ToUpper(log.Container), log.Log) } } } -func StartLogs(clientset *kubernetes.Clientset, cr *operatorv1beta1.Cryostat) (chan *ContainerLog, error) { - podName, err := getCryostatPodNameForCR(clientset, cr) +func (r *TestResources) StartLogs(cr *operatorv1beta1.Cryostat) error { + podName, err := r.getCryostatPodNameForCR(cr) if err != nil { - return nil, fmt.Errorf("failed to get pod name for CR: %s", err.Error()) + return fmt.Errorf("failed to get pod name for CR: %s", err.Error()) } containerNames := []string{ @@ -96,11 +94,11 @@ func StartLogs(clientset *kubernetes.Clientset, cr *operatorv1beta1.Cryostat) (c cr.Name + "-jfr-datasource", } - ch := make(chan *ContainerLog, len(containerNames)) + r.LogChannel = make(chan *ContainerLog, len(containerNames)) for _, containerName := range containerNames { - go LogContainer(clientset, cr.Namespace, podName, containerName, ch) + go r.logContainer(cr.Namespace, podName, containerName) } - return ch, nil + return nil } diff --git a/internal/test/scorecard/openshift.go b/internal/test/scorecard/openshift.go index 247068a48..7880221ce 100644 --- a/internal/test/scorecard/openshift.go +++ b/internal/test/scorecard/openshift.go @@ -21,7 +21,6 @@ import ( "time" "github.com/blang/semver/v4" - scapiv1alpha3 "github.com/operator-framework/api/pkg/apis/scorecard/v1alpha3" operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" ctrl "sigs.k8s.io/controller-runtime" @@ -39,7 +38,7 @@ import ( corev1client "k8s.io/client-go/kubernetes/typed/core/v1" ) -func installOpenShiftCertManager(r *scapiv1alpha3.TestResult) error { +func (r *TestResources) installOpenShiftCertManager() error { ctx := context.Background() // Get in-cluster REST config from pod diff --git a/internal/test/scorecard/tests.go b/internal/test/scorecard/tests.go index 12a8bf2a3..9d3d251a6 100644 --- a/internal/test/scorecard/tests.go +++ b/internal/test/scorecard/tests.go @@ -37,76 +37,74 @@ const ( ) // OperatorInstallTest checks that the operator installed correctly -func OperatorInstallTest(bundle *apimanifests.Bundle, namespace string) (result scapiv1alpha3.TestResult) { - r := newEmptyTestResult(OperatorInstallTestName) +func OperatorInstallTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) *scapiv1alpha3.TestResult { + r := newTestResources(OperatorInstallTestName) // Create a new Kubernetes REST client for this test - client, err := NewClientset() + err := r.setupCRTestResources(openShiftCertManager) if err != nil { - return fail(*r, fmt.Sprintf("failed to create client: %s", err.Error())) + return r.fail(fmt.Sprintf("failed to set up %s test: %s", OperatorInstallTestName, err.Error())) } + defer r.cleanupAndLogs(OperatorInstallTestName, namespace) // Poll the deployment until it becomes available or we timeout ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - err = waitForDeploymentAvailability(ctx, client, namespace, operatorDeploymentName, r) + err = r.waitForDeploymentAvailability(ctx, namespace, operatorDeploymentName) if err != nil { - return fail(*r, fmt.Sprintf("operator deployment did not become available: %s", err.Error())) + return r.fail(fmt.Sprintf("operator deployment did not become available: %s", err.Error())) } - return *r + return r.TestResult } // CryostatCRTest checks that the operator installs Cryostat in response to a Cryostat CR -func CryostatCRTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) (result scapiv1alpha3.TestResult) { - tr := newTestResources(CryostatCRTestName) - r := tr.TestResult +func CryostatCRTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) *scapiv1alpha3.TestResult { + r := newTestResources(CryostatCRTestName) - err := setupCRTestResources(tr, openShiftCertManager) + err := r.setupCRTestResources(openShiftCertManager) if err != nil { - return fail(*r, fmt.Sprintf("failed to set up %s test: %s", CryostatCRTestName, err.Error())) + return r.fail(fmt.Sprintf("failed to set up %s test: %s", CryostatCRTestName, err.Error())) } - defer cleanupAndLogs(&result, tr, CryostatCRTestName, namespace) + defer r.cleanupAndLogs(CryostatCRTestName, namespace) // Create a default Cryostat CR - _, err = createAndWaitTillCryostatAvailable(newCryostatCR(CryostatCRTestName, namespace, !tr.OpenShift), tr) + _, err = r.createAndWaitTillCryostatAvailable(newCryostatCR(CryostatCRTestName, namespace, !r.OpenShift)) if err != nil { - return fail(*r, fmt.Sprintf("%s test failed: %s", CryostatCRTestName, err.Error())) + return r.fail(fmt.Sprintf("%s test failed: %s", CryostatCRTestName, err.Error())) } - return *r + return r.TestResult } -func CryostatConfigChangeTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) (result scapiv1alpha3.TestResult) { - tr := newTestResources(CryostatConfigChangeTestName) - r := tr.TestResult +func CryostatConfigChangeTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) *scapiv1alpha3.TestResult { + r := newTestResources(CryostatConfigChangeTestName) - err := setupCRTestResources(tr, openShiftCertManager) + err := r.setupCRTestResources(openShiftCertManager) if err != nil { - return fail(*r, fmt.Sprintf("failed to set up %s test: %s", CryostatConfigChangeTestName, err.Error())) + return r.fail(fmt.Sprintf("failed to set up %s test: %s", CryostatConfigChangeTestName, err.Error())) } - defer cleanupAndLogs(&result, tr, CryostatConfigChangeTestName, namespace) + defer r.cleanupAndLogs(CryostatConfigChangeTestName, namespace) // Create a default Cryostat CR with default empty dir - cr := newCryostatCR(CryostatConfigChangeTestName, namespace, !tr.OpenShift) + cr := newCryostatCR(CryostatConfigChangeTestName, namespace, !r.OpenShift) cr.Spec.StorageOptions = &operatorv1beta1.StorageConfiguration{ EmptyDir: &operatorv1beta1.EmptyDirConfig{ Enabled: true, }, } - _, err = createAndWaitTillCryostatAvailable(cr, tr) + _, err = r.createAndWaitTillCryostatAvailable(cr) if err != nil { - return fail(*r, fmt.Sprintf("failed to determine application URL: %s", err.Error())) + return r.fail(fmt.Sprintf("failed to determine application URL: %s", err.Error())) } // Switch Cryostat CR to PVC for redeployment ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - client := tr.Client - cr, err = client.OperatorCRDs().Cryostats(namespace).Get(ctx, CryostatConfigChangeTestName) + cr, err = r.Client.OperatorCRDs().Cryostats(namespace).Get(ctx, CryostatConfigChangeTestName) if err != nil { - return fail(*r, fmt.Sprintf("failed to get Cryostat CR: %s", err.Error())) + return r.fail(fmt.Sprintf("failed to get Cryostat CR: %s", err.Error())) } cr.Spec.StorageOptions = &operatorv1beta1.StorageConfiguration{ PVC: &operatorv1beta1.PersistentVolumeClaimConfig{ @@ -122,54 +120,53 @@ func CryostatConfigChangeTest(bundle *apimanifests.Bundle, namespace string, ope } // Wait for redeployment of Cryostat CR - err = updateAndWaitTillCryostatAvailable(cr, tr) + err = r.updateAndWaitTillCryostatAvailable(cr) if err != nil { - return fail(*r, fmt.Sprintf("Cryostat redeployment did not become available: %s", err.Error())) + return r.fail(fmt.Sprintf("Cryostat redeployment did not become available: %s", err.Error())) } r.Log += "Cryostat deployment has successfully updated with new spec template\n" base, err := url.Parse(cr.Status.ApplicationURL) if err != nil { - return fail(*r, fmt.Sprintf("application URL is invalid: %s", err.Error())) + return r.fail(fmt.Sprintf("application URL is invalid: %s", err.Error())) } - err = waitTillCryostatReady(base, tr) + err = r.waitTillCryostatReady(base) if err != nil { - return fail(*r, fmt.Sprintf("failed to reach the application: %s", err.Error())) + return r.fail(fmt.Sprintf("failed to reach the application: %s", err.Error())) } - return *r + return r.TestResult } // TODO add a built in discovery test too -func CryostatRecordingTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) (result scapiv1alpha3.TestResult) { - tr := newTestResources(CryostatRecordingTestName) - r := tr.TestResult +func CryostatRecordingTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) *scapiv1alpha3.TestResult { + r := newTestResources(CryostatRecordingTestName) - err := setupCRTestResources(tr, openShiftCertManager) + err := r.setupCRTestResources(openShiftCertManager) if err != nil { - return fail(*r, fmt.Sprintf("failed to set up %s test: %s", CryostatRecordingTestName, err.Error())) + return r.fail(fmt.Sprintf("failed to set up %s test: %s", CryostatRecordingTestName, err.Error())) } - defer cleanupAndLogs(&result, tr, CryostatRecordingTestName, namespace) + defer r.cleanupAndLogs(CryostatRecordingTestName, namespace) // Create a default Cryostat CR - cr, err := createAndWaitTillCryostatAvailable(newCryostatCR(CryostatRecordingTestName, namespace, !tr.OpenShift), tr) + cr, err := r.createAndWaitTillCryostatAvailable(newCryostatCR(CryostatRecordingTestName, namespace, !r.OpenShift)) if err != nil { - return fail(*r, fmt.Sprintf("failed to determine application URL: %s", err.Error())) + return r.fail(fmt.Sprintf("failed to determine application URL: %s", err.Error())) } - tr.LogChannel, err = StartLogs(tr.Client.Clientset, cr) + err = r.StartLogs(cr) if err != nil { r.Log += fmt.Sprintf("failed to retrieve logs for the application: %s", err.Error()) } base, err := url.Parse(cr.Status.ApplicationURL) if err != nil { - return fail(*r, fmt.Sprintf("application URL is invalid: %s", err.Error())) + return r.fail(fmt.Sprintf("application URL is invalid: %s", err.Error())) } - err = waitTillCryostatReady(base, tr) + err = r.waitTillCryostatReady(base) if err != nil { - return fail(*r, fmt.Sprintf("failed to reach the application: %s", err.Error())) + return r.fail(fmt.Sprintf("failed to reach the application: %s", err.Error())) } apiClient := NewCryostatRESTClientset(base) @@ -181,15 +178,15 @@ func CryostatRecordingTest(bundle *apimanifests.Bundle, namespace string, openSh } target, err := apiClient.Targets().Create(context.Background(), targetOptions) if err != nil { - return fail(*r, fmt.Sprintf("failed to create a target: %s", err.Error())) + return r.fail(fmt.Sprintf("failed to create a target: %s", err.Error())) } r.Log += fmt.Sprintf("created a custom target: %+v\n", target) connectUrl := target.ConnectUrl jmxSecretName := CryostatRecordingTestName + "-jmx-auth" - secret, err := tr.Client.CoreV1().Secrets(namespace).Get(context.Background(), jmxSecretName, metav1.GetOptions{}) + secret, err := r.Client.CoreV1().Secrets(namespace).Get(context.Background(), jmxSecretName, metav1.GetOptions{}) if err != nil { - return fail(*r, fmt.Sprintf("failed to get jmx credentials: %s", err.Error())) + return r.fail(fmt.Sprintf("failed to get jmx credentials: %s", err.Error())) } credential := &Credential{ @@ -200,7 +197,7 @@ func CryostatRecordingTest(bundle *apimanifests.Bundle, namespace string, openSh err = apiClient.CredentialClient.Create(context.Background(), credential) if err != nil { - return fail(*r, fmt.Sprintf("failed to create stored credential: %s", err.Error())) + return r.fail(fmt.Sprintf("failed to create stored credential: %s", err.Error())) } r.Log += fmt.Sprintf("created stored credential with match expression: %s\n", credential.MatchExpression) @@ -218,14 +215,14 @@ func CryostatRecordingTest(bundle *apimanifests.Bundle, namespace string, openSh } rec, err := apiClient.Recordings().Create(context.Background(), connectUrl, options) if err != nil { - return fail(*r, fmt.Sprintf("failed to create a recording: %s", err.Error())) + return r.fail(fmt.Sprintf("failed to create a recording: %s", err.Error())) } r.Log += fmt.Sprintf("created a recording: %+v\n", rec) // View the current recording list after creating one recs, err := apiClient.Recordings().List(context.Background(), connectUrl) if err != nil { - return fail(*r, fmt.Sprintf("failed to list recordings: %s", err.Error())) + return r.fail(fmt.Sprintf("failed to list recordings: %s", err.Error())) } r.Log += fmt.Sprintf("current list of recordings: %+v\n", recs) @@ -235,66 +232,65 @@ func CryostatRecordingTest(bundle *apimanifests.Bundle, namespace string, openSh // Archive the recording archiveName, err := apiClient.Recordings().Archive(context.Background(), connectUrl, rec.Name) if err != nil { - return fail(*r, fmt.Sprintf("failed to archive the recording: %s", err.Error())) + return r.fail(fmt.Sprintf("failed to archive the recording: %s", err.Error())) } r.Log += fmt.Sprintf("archived the recording %s at: %s\n", rec.Name, archiveName) archives, err := apiClient.Recordings().ListArchives(context.Background(), connectUrl) if err != nil { - return fail(*r, fmt.Sprintf("failed to list archives: %s", err.Error())) + return r.fail(fmt.Sprintf("failed to list archives: %s", err.Error())) } r.Log += fmt.Sprintf("current list of archives: %+v\n", archives) report, err := apiClient.Recordings().GenerateReport(context.Background(), connectUrl, rec) if err != nil { - return fail(*r, fmt.Sprintf("failed to generate report for the recording: %s", err.Error())) + return r.fail(fmt.Sprintf("failed to generate report for the recording: %s", err.Error())) } r.Log += fmt.Sprintf("generated report for the recording %s: %+v\n", rec.Name, report) // Stop the recording err = apiClient.Recordings().Stop(context.Background(), connectUrl, rec.Name) if err != nil { - return fail(*r, fmt.Sprintf("failed to stop the recording %s: %s", rec.Name, err.Error())) + return r.fail(fmt.Sprintf("failed to stop the recording %s: %s", rec.Name, err.Error())) } // Get the recording to verify its state rec, err = apiClient.Recordings().Get(context.Background(), connectUrl, rec.Name) if err != nil { - return fail(*r, fmt.Sprintf("failed to get the recordings: %s", err.Error())) + return r.fail(fmt.Sprintf("failed to get the recordings: %s", err.Error())) } if rec.State != "STOPPED" { - return fail(*r, fmt.Sprintf("recording %s failed to stop: %s", rec.Name, err.Error())) + return r.fail(fmt.Sprintf("recording %s failed to stop: %s", rec.Name, err.Error())) } r.Log += fmt.Sprintf("stopped the recording: %s\n", rec.Name) // Delete the recording err = apiClient.Recordings().Delete(context.Background(), connectUrl, rec.Name) if err != nil { - return fail(*r, fmt.Sprintf("failed to delete the recording %s: %s", rec.Name, err.Error())) + return r.fail(fmt.Sprintf("failed to delete the recording %s: %s", rec.Name, err.Error())) } r.Log += fmt.Sprintf("deleted the recording: %s\n", rec.Name) // View the current recording list after deleting one recs, err = apiClient.Recordings().List(context.Background(), connectUrl) if err != nil { - return fail(*r, fmt.Sprintf("failed to list recordings: %s", err.Error())) + return r.fail(fmt.Sprintf("failed to list recordings: %s", err.Error())) } r.Log += fmt.Sprintf("current list of recordings: %+v\n", recs) - return *r + return r.TestResult } -func CryostatReportTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) (result scapiv1alpha3.TestResult) { - tr := newTestResources(CryostatReportTestName) - r := tr.TestResult +func CryostatReportTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) *scapiv1alpha3.TestResult { + r := newTestResources(CryostatReportTestName) - err := setupCRTestResources(tr, openShiftCertManager) + err := r.setupCRTestResources(openShiftCertManager) if err != nil { - return fail(*r, fmt.Sprintf("failed to set up %s test: %s", CryostatReportTestName, err.Error())) + return r.fail(fmt.Sprintf("failed to set up %s test: %s", CryostatReportTestName, err.Error())) } - defer cleanupAndLogs(&result, tr, CryostatReportTestName, namespace) + defer r.cleanupAndLogs(CryostatReportTestName, namespace) port := int32(10000) - cr := newCryostatCR(CryostatReportTestName, namespace, !tr.OpenShift) + cr := newCryostatCR(CryostatReportTestName, namespace, !r.OpenShift) cr.Spec.ReportOptions = &operatorv1beta1.ReportConfiguration{ Replicas: 1, } @@ -305,16 +301,16 @@ func CryostatReportTest(bundle *apimanifests.Bundle, namespace string, openShift } // Create a default Cryostat CR - cr, err = createAndWaitTillCryostatAvailable(cr, tr) + cr, err = r.createAndWaitTillCryostatAvailable(cr) if err != nil { - return fail(*r, fmt.Sprintf("%s test failed: %s", CryostatReportTestName, err.Error())) + return r.fail(fmt.Sprintf("%s test failed: %s", CryostatReportTestName, err.Error())) } // Query health of report sidecar - err = waitTillReportReady(cr.Name+"-reports", cr.Namespace, port, tr) + err = r.waitTillReportReady(cr.Name+"-reports", cr.Namespace, port) if err != nil { - return fail(*r, fmt.Sprintf("failed to reach the application: %s", err.Error())) + return r.fail(fmt.Sprintf("failed to reach the application: %s", err.Error())) } - return *r + return r.TestResult } From 8aa43a8e381442b37b96469a11bf20f4b3583a20 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 5 Apr 2024 12:26:45 -0400 Subject: [PATCH 12/16] ci(branch): run push workflows on cryostat3 branch (#786) (#787) (cherry picked from commit 3fcbfab356fb38594e9957c548738615a5ccdf03) Co-authored-by: Elliott Baron --- .github/workflows/build-ci.yml | 2 ++ .github/workflows/test-ci-push.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/build-ci.yml b/.github/workflows/build-ci.yml index 4fcb43481..b6dfc8f46 100644 --- a/.github/workflows/build-ci.yml +++ b/.github/workflows/build-ci.yml @@ -11,6 +11,8 @@ on: - v[0-9]+ - v[0-9]+.[0-9]+ - cryostat-v[0-9]+.[0-9]+ + # TODO remove once merged into main + - cryostat3 env: CI_USER: cryostat+bot diff --git a/.github/workflows/test-ci-push.yml b/.github/workflows/test-ci-push.yml index 0975e4149..df523c501 100644 --- a/.github/workflows/test-ci-push.yml +++ b/.github/workflows/test-ci-push.yml @@ -11,6 +11,8 @@ on: - v[0-9]+ - v[0-9]+.[0-9]+ - cryostat-v[0-9]+.[0-9]+ + # TODO remove once merged into main + - cryostat3 jobs: check-before-test: From 6b63e3f67a6ac6dda5ac307dc5d0a1adea444ec8 Mon Sep 17 00:00:00 2001 From: Ming Yu Wang <90855268+mwangggg@users.noreply.github.com> Date: Fri, 5 Apr 2024 14:26:59 -0400 Subject: [PATCH 13/16] test(scorecard): multi-namespace scorecard test (#763) * multi-namespace scorecard test * kubernetes client makes and removes namespaces * reviews --------- Co-authored-by: Elliott Baron --- ...yostat-operator.clusterserviceversion.yaml | 2 +- bundle/tests/scorecard/config.yaml | 20 +- config/scorecard/patches/custom.config.yaml | 20 +- hack/custom.config.yaml.in | 10 + .../images/custom-scorecard-tests/main.go | 4 + .../rbac/scorecard_role.yaml | 15 ++ internal/test/scorecard/clients.go | 56 ++++- internal/test/scorecard/common_utils.go | 223 ++++++++++++++---- internal/test/scorecard/tests.go | 40 +++- 9 files changed, 312 insertions(+), 78 deletions(-) diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index 960e558f9..1f978f085 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -54,7 +54,7 @@ metadata: capabilities: Seamless Upgrades categories: Monitoring, Developer Tools containerImage: quay.io/cryostat/cryostat-operator:2.5.0-dev - createdAt: "2024-03-27T17:54:03Z" + createdAt: "2024-04-04T17:17:25Z" description: JVM monitoring and profiling tool operatorframework.io/initialization-resource: |- { diff --git a/bundle/tests/scorecard/config.yaml b/bundle/tests/scorecard/config.yaml index ae53496a9..f536a45d3 100644 --- a/bundle/tests/scorecard/config.yaml +++ b/bundle/tests/scorecard/config.yaml @@ -70,7 +70,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - operator-install - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240326210241 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171629 labels: suite: cryostat test: operator-install @@ -80,17 +80,27 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240326210241 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171629 labels: suite: cryostat test: cryostat-cr storage: spec: mountPath: {} + - entrypoint: + - cryostat-scorecard-tests + - cryostat-multi-namespace + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171629 + labels: + suite: cryostat + test: cryostat-multi-namespace + storage: + spec: + mountPath: {} - entrypoint: - cryostat-scorecard-tests - cryostat-recording - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240326210241 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171629 labels: suite: cryostat test: cryostat-recording @@ -100,7 +110,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-config-change - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240326210241 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171629 labels: suite: cryostat test: cryostat-config-change @@ -110,7 +120,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-report - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240326210241 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171629 labels: suite: cryostat test: cryostat-report diff --git a/config/scorecard/patches/custom.config.yaml b/config/scorecard/patches/custom.config.yaml index 4bc52ec07..333688c77 100644 --- a/config/scorecard/patches/custom.config.yaml +++ b/config/scorecard/patches/custom.config.yaml @@ -8,7 +8,7 @@ entrypoint: - cryostat-scorecard-tests - operator-install - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327175405" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171725" labels: suite: cryostat test: operator-install @@ -18,17 +18,27 @@ entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327175405" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171725" labels: suite: cryostat test: cryostat-cr +- op: add + path: /stages/1/tests/- + value: + entrypoint: + - cryostat-scorecard-tests + - cryostat-multi-namespace + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171725" + labels: + suite: cryostat + test: cryostat-multi-namespace - op: add path: /stages/1/tests/- value: entrypoint: - cryostat-scorecard-tests - cryostat-recording - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327175405" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171725" labels: suite: cryostat test: cryostat-recording @@ -38,7 +48,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-config-change - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327175405" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171725" labels: suite: cryostat test: cryostat-config-change @@ -48,7 +58,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-report - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327175405" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171725" labels: suite: cryostat test: cryostat-report diff --git a/hack/custom.config.yaml.in b/hack/custom.config.yaml.in index 4336abbe4..75306a910 100644 --- a/hack/custom.config.yaml.in +++ b/hack/custom.config.yaml.in @@ -21,6 +21,16 @@ labels: suite: cryostat test: cryostat-cr +- op: add + path: /stages/1/tests/- + value: + entrypoint: + - cryostat-scorecard-tests + - cryostat-multi-namespace + image: "${CUSTOM_SCORECARD_IMG}" + labels: + suite: cryostat + test: cryostat-multi-namespace - op: add path: /stages/1/tests/- value: diff --git a/internal/images/custom-scorecard-tests/main.go b/internal/images/custom-scorecard-tests/main.go index cce16f5a6..5592e766f 100644 --- a/internal/images/custom-scorecard-tests/main.go +++ b/internal/images/custom-scorecard-tests/main.go @@ -79,6 +79,7 @@ func printValidTests() []scapiv1alpha3.TestResult { str := fmt.Sprintf("valid tests for this image include: %s", strings.Join([]string{ tests.OperatorInstallTestName, tests.CryostatCRTestName, + tests.CryostatMultiNamespaceTestName, tests.CryostatRecordingTestName, tests.CryostatConfigChangeTestName, tests.CryostatReportTestName, @@ -93,6 +94,7 @@ func validateTests(testNames []string) bool { switch testName { case tests.OperatorInstallTestName: case tests.CryostatCRTestName: + case tests.CryostatMultiNamespaceTestName: case tests.CryostatRecordingTestName: case tests.CryostatConfigChangeTestName: case tests.CryostatReportTestName: @@ -114,6 +116,8 @@ func runTests(testNames []string, bundle *apimanifests.Bundle, namespace string, results = append(results, *tests.OperatorInstallTest(bundle, namespace, openShiftCertManager)) case tests.CryostatCRTestName: results = append(results, *tests.CryostatCRTest(bundle, namespace, openShiftCertManager)) + case tests.CryostatMultiNamespaceTestName: + results = append(results, *tests.CryostatMultiNamespaceTest(bundle, namespace, openShiftCertManager)) case tests.CryostatRecordingTestName: results = append(results, *tests.CryostatRecordingTest(bundle, namespace, openShiftCertManager)) case tests.CryostatConfigChangeTestName: diff --git a/internal/images/custom-scorecard-tests/rbac/scorecard_role.yaml b/internal/images/custom-scorecard-tests/rbac/scorecard_role.yaml index 7eaedd854..61b87d7de 100644 --- a/internal/images/custom-scorecard-tests/rbac/scorecard_role.yaml +++ b/internal/images/custom-scorecard-tests/rbac/scorecard_role.yaml @@ -150,3 +150,18 @@ rules: - namespaces verbs: - create + - delete +- apiGroups: + - operator.cryostat.io + resources: + - clustercryostats + verbs: + - create + - get + - delete +- apiGroups: + - operator.cryostat.io + resources: + - clustercryostats/status + verbs: + - get diff --git a/internal/test/scorecard/clients.go b/internal/test/scorecard/clients.go index 2c831e72f..54ef1fc40 100644 --- a/internal/test/scorecard/clients.go +++ b/internal/test/scorecard/clients.go @@ -89,6 +89,14 @@ func (c *OperatorCRDClient) Cryostats(namespace string) *CryostatClient { } } +// ClusterCryostats returns a ClusterCryostatClient +func (c *OperatorCRDClient) ClusterCryostats() *CryostatClient { + return &CryostatClient{ + restClient: c.client, + resource: "clustercryostats", + } +} + func newOperatorCRDClient(config *rest.Config) (*OperatorCRDClient, error) { client, err := newCRDClient(config) if err != nil { @@ -124,6 +132,21 @@ type CryostatClient struct { resource string } +// Get returns a Cryostat CR for the given name +func (c *CryostatClient) GetNonNamespaced(ctx context.Context, name string) (*operatorv1beta1.ClusterCryostat, error) { + return get(ctx, c.restClient, c.resource, c.namespace, name, &operatorv1beta1.ClusterCryostat{}) +} + +// Create creates the provided ClusterCryostat CR +func (c *CryostatClient) CreateNonNamespaced(ctx context.Context, obj *operatorv1beta1.ClusterCryostat) (*operatorv1beta1.ClusterCryostat, error) { + return create(ctx, c.restClient, c.resource, c.namespace, obj, &operatorv1beta1.ClusterCryostat{}) +} + +// Delete deletes the Cryostat CR with the given name +func (c *CryostatClient) DeleteNonNamespaced(ctx context.Context, name string, options *metav1.DeleteOptions) error { + return delete(ctx, c.restClient, c.resource, c.namespace, name, options) +} + // Get returns a Cryostat CR for the given name func (c *CryostatClient) Get(ctx context.Context, name string) (*operatorv1beta1.Cryostat, error) { return get(ctx, c.restClient, c.resource, c.namespace, name, &operatorv1beta1.Cryostat{}) @@ -145,31 +168,38 @@ func (c *CryostatClient) Delete(ctx context.Context, name string, options *metav } func get[r runtime.Object](ctx context.Context, c rest.Interface, res string, ns string, name string, result r) (r, error) { - err := c.Get(). - Namespace(ns).Resource(res). - Name(name).Do(ctx).Into(result) + rq := c.Get().Resource(res).Name(name) + if len(ns) > 0 { + rq = rq.Namespace(ns) + } + err := rq.Do(ctx).Into(result) return result, err } func create[r runtime.Object](ctx context.Context, c rest.Interface, res string, ns string, obj r, result r) (r, error) { - err := c.Post(). - Namespace(ns).Resource(res). - Body(obj).Do(ctx).Into(result) + rq := c.Post().Resource(res).Body(obj) + if len(ns) > 0 { + rq = rq.Namespace(ns) + } + err := rq.Do(ctx).Into(result) return result, err } func update[r runtime.Object](ctx context.Context, c rest.Interface, res string, ns string, obj r, result r, name string) (r, error) { - err := c.Put(). - Namespace(ns).Resource(res).Name(name). - Body(obj).Do(ctx).Into(result) + rq := c.Put().Resource(res).Name(name).Body(obj) + if len(ns) > 0 { + rq = rq.Namespace(ns) + } + err := rq.Do(ctx).Into(result) return result, err } func delete(ctx context.Context, c rest.Interface, res string, ns string, name string, opts *metav1.DeleteOptions) error { - return c.Delete(). - Namespace(ns).Resource(res). - Name(name).Body(opts).Do(ctx). - Error() + rq := c.Delete().Resource(res).Name(name).Body(opts) + if len(ns) > 0 { + rq = rq.Namespace(ns) + } + return rq.Do(ctx).Error() } // CryostatRESTClientset contains methods to interact with diff --git a/internal/test/scorecard/common_utils.go b/internal/test/scorecard/common_utils.go index 99c639ee0..6bc7e6afa 100644 --- a/internal/test/scorecard/common_utils.go +++ b/internal/test/scorecard/common_utils.go @@ -231,42 +231,85 @@ func (r *TestResources) setupCRTestResources(openShiftCertManager bool) error { return nil } +func (r *TestResources) setupTargetNamespace(name string) error { + ctx := context.Background() + + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } + ns, err := r.Client.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("failed to create namespace %s: %s", name, err.Error()) + } + r.Log += fmt.Sprintf("created namespace: %s\n", ns.Name) + return nil +} + func newCryostatCR(name string, namespace string, withIngress bool) *operatorv1beta1.Cryostat { cr := &operatorv1beta1.Cryostat{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, }, - Spec: operatorv1beta1.CryostatSpec{ - Minimal: false, - EnableCertManager: &[]bool{true}[0], + Spec: newCryostatSpec(), + } + + if withIngress { + configureIngress(name, &cr.Spec) + } + return cr +} + +func newClusterCryostatCR(name string, namespace string, namespaces []string, withIngress bool) *operatorv1beta1.ClusterCryostat { + cr := &operatorv1beta1.ClusterCryostat{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: operatorv1beta1.ClusterCryostatSpec{ + InstallNamespace: namespace, + TargetNamespaces: namespaces, + CryostatSpec: newCryostatSpec(), }, } if withIngress { - pathType := netv1.PathTypePrefix - cr.Spec.NetworkOptions = &operatorv1beta1.NetworkConfigurationList{ - CoreConfig: &operatorv1beta1.NetworkConfiguration{ - Annotations: map[string]string{ - "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS", - }, - IngressSpec: &netv1.IngressSpec{ - TLS: []netv1.IngressTLS{{}}, - Rules: []netv1.IngressRule{ - { - Host: "testing.cryostat", - IngressRuleValue: netv1.IngressRuleValue{ - HTTP: &netv1.HTTPIngressRuleValue{ - Paths: []netv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathType, - Backend: netv1.IngressBackend{ - Service: &netv1.IngressServiceBackend{ - Name: name, - Port: netv1.ServiceBackendPort{ - Number: 8181, - }, + configureIngress(name, &cr.Spec.CryostatSpec) + } + return cr +} + +func newCryostatSpec() operatorv1beta1.CryostatSpec { + return operatorv1beta1.CryostatSpec{ + Minimal: false, + EnableCertManager: &[]bool{true}[0], + } +} + +func configureIngress(name string, cryostatSpec *operatorv1beta1.CryostatSpec) { + pathType := netv1.PathTypePrefix + cryostatSpec.NetworkOptions = &operatorv1beta1.NetworkConfigurationList{ + CoreConfig: &operatorv1beta1.NetworkConfiguration{ + Annotations: map[string]string{ + "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS", + }, + IngressSpec: &netv1.IngressSpec{ + TLS: []netv1.IngressTLS{{}}, + Rules: []netv1.IngressRule{ + { + Host: "testing.cryostat", + IngressRuleValue: netv1.IngressRuleValue{ + HTTP: &netv1.HTTPIngressRuleValue{ + Paths: []netv1.HTTPIngressPath{ + { + Path: "/", + PathType: &pathType, + Backend: netv1.IngressBackend{ + Service: &netv1.IngressServiceBackend{ + Name: name, + Port: netv1.ServiceBackendPort{ + Number: 8181, }, }, }, @@ -277,27 +320,27 @@ func newCryostatCR(name string, namespace string, withIngress bool) *operatorv1b }, }, }, - GrafanaConfig: &operatorv1beta1.NetworkConfiguration{ - Annotations: map[string]string{ - "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS", - }, - IngressSpec: &netv1.IngressSpec{ - TLS: []netv1.IngressTLS{{}}, - Rules: []netv1.IngressRule{ - { - Host: "testing.cryostat-grafana", - IngressRuleValue: netv1.IngressRuleValue{ - HTTP: &netv1.HTTPIngressRuleValue{ - Paths: []netv1.HTTPIngressPath{ - { - Path: "/", - PathType: &pathType, - Backend: netv1.IngressBackend{ - Service: &netv1.IngressServiceBackend{ - Name: fmt.Sprintf("%s-grafana", name), - Port: netv1.ServiceBackendPort{ - Number: 3000, - }, + }, + GrafanaConfig: &operatorv1beta1.NetworkConfiguration{ + Annotations: map[string]string{ + "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS", + }, + IngressSpec: &netv1.IngressSpec{ + TLS: []netv1.IngressTLS{{}}, + Rules: []netv1.IngressRule{ + { + Host: "testing.cryostat-grafana", + IngressRuleValue: netv1.IngressRuleValue{ + HTTP: &netv1.HTTPIngressRuleValue{ + Paths: []netv1.HTTPIngressPath{ + { + Path: "/", + PathType: &pathType, + Backend: netv1.IngressBackend{ + Service: &netv1.IngressServiceBackend{ + Name: fmt.Sprintf("%s-grafana", name), + Port: netv1.ServiceBackendPort{ + Number: 3000, }, }, }, @@ -308,9 +351,8 @@ func newCryostatCR(name string, namespace string, withIngress bool) *operatorv1b }, }, }, - } + }, } - return cr } func (r *TestResources) createAndWaitTillCryostatAvailable(cr *operatorv1beta1.Cryostat) (*operatorv1beta1.Cryostat, error) { @@ -349,6 +391,52 @@ func (r *TestResources) createAndWaitTillCryostatAvailable(cr *operatorv1beta1.C return cr, nil } +func (r *TestResources) createAndWaitTillClusterCryostatAvailable(cr *operatorv1beta1.ClusterCryostat) (*operatorv1beta1.ClusterCryostat, error) { + cr, err := r.Client.OperatorCRDs().ClusterCryostats().CreateNonNamespaced(context.Background(), cr) + if err != nil { + r.logError(fmt.Sprintf("failed to create ClusterCryostat CR: %s", err.Error())) + return nil, err + } + + // Poll the deployment until it becomes available or we timeout + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + err = r.waitForDeploymentAvailability(ctx, cr.Spec.InstallNamespace, cr.Name) + if err != nil { + r.logError(fmt.Sprintf("ClusterCryostat main deployment did not become available: %s", err.Error())) + return nil, err + } + + err = wait.PollImmediateUntilWithContext(ctx, time.Second, func(ctx context.Context) (done bool, err error) { + cr, err = r.Client.OperatorCRDs().ClusterCryostats().GetNonNamespaced(ctx, cr.Name) + if err != nil { + return false, fmt.Errorf("failed to get ClusterCryostat CR: %s", err.Error()) + } + if len(cr.Status.TargetNamespaces) != len(cr.Spec.TargetNamespaces) { + r.Log += "application's target namespaces are not yet available" + return false, nil // Retry + } + for i := range cr.Status.TargetNamespaces { + if cr.Status.TargetNamespaces[i] != cr.Spec.TargetNamespaces[i] { + return false, fmt.Errorf("application's target namespaces do not correctly match CR's") + } + } + if len(cr.Status.CryostatStatus.ApplicationURL) == 0 { + r.Log += "application URL is not yet available\n" + return false, nil // Retry + } + return true, nil + }) + if err != nil { + r.logError(fmt.Sprintf("application URL not found in CR: %s", err.Error())) + return nil, err + } + r.Log += fmt.Sprintf("application has access to the following namespaces: %s\n", cr.Status.TargetNamespaces) + r.Log += fmt.Sprintf("application is available at %s\n", cr.Status.CryostatStatus.ApplicationURL) + + return cr, nil +} + func (r *TestResources) waitTillCryostatReady(base *url.URL) error { return r.sendHealthRequest(base, func(resp *http.Response, r *scapiv1alpha3.TestResult) (done bool, err error) { health := &HealthResponse{} @@ -533,3 +621,40 @@ func (r *TestResources) getCryostatPodNameForCR(cr *operatorv1beta1.Cryostat) (s return pods.Items[0].ObjectMeta.Name, nil } + +func (r *TestResources) cleanupClusterCryostat(name string, namespace string, targetNamespaces []string) { + r.LogWorkloadEventsOnError(namespace, name) + + cr := &operatorv1beta1.ClusterCryostat{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: operatorv1beta1.ClusterCryostatSpec{ + InstallNamespace: namespace, + }, + } + + ctx := context.Background() + err := r.Client.OperatorCRDs().ClusterCryostats().DeleteNonNamespaced(ctx, + cr.Name, &metav1.DeleteOptions{}) + if err != nil { + if !kerrors.IsNotFound(err) { + r.Log += fmt.Sprintf("failed to delete ClusterCryostat: %s\n", err.Error()) + } + } + + for _, ns := range targetNamespaces { + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: ns, + }, + } + + err := r.Client.CoreV1().Namespaces().Delete(ctx, ns.Name, metav1.DeleteOptions{}) + if err != nil { + if !kerrors.IsNotFound(err) { + r.Log += fmt.Sprintf("failed to delete namespace %s: %s\n", ns, err.Error()) + } + } + } +} diff --git a/internal/test/scorecard/tests.go b/internal/test/scorecard/tests.go index 9d3d251a6..c5d7e5b86 100644 --- a/internal/test/scorecard/tests.go +++ b/internal/test/scorecard/tests.go @@ -29,11 +29,12 @@ import ( ) const ( - OperatorInstallTestName string = "operator-install" - CryostatCRTestName string = "cryostat-cr" - CryostatRecordingTestName string = "cryostat-recording" - CryostatConfigChangeTestName string = "cryostat-config-change" - CryostatReportTestName string = "cryostat-report" + OperatorInstallTestName string = "operator-install" + CryostatCRTestName string = "cryostat-cr" + CryostatMultiNamespaceTestName string = "cryostat-multi-namespace" + CryostatConfigChangeTestName string = "cryostat-config-change" + CryostatRecordingTestName string = "cryostat-recording" + CryostatReportTestName string = "cryostat-report" ) // OperatorInstallTest checks that the operator installed correctly @@ -76,6 +77,34 @@ func CryostatCRTest(bundle *apimanifests.Bundle, namespace string, openShiftCert return r.TestResult } +// CryostatMultiNamespaceTest checks that the operator installs ClusterCryostat in response to a ClusterCryostat CR +func CryostatMultiNamespaceTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) *scapiv1alpha3.TestResult { + r := newTestResources(CryostatMultiNamespaceTestName) + namespaces := []string{namespace + "-other"} + + err := r.setupCRTestResources(openShiftCertManager) + if err != nil { + return r.fail(fmt.Sprintf("failed to set up %s test: %s", CryostatMultiNamespaceTestName, err.Error())) + } + defer r.cleanupClusterCryostat(CryostatMultiNamespaceTestName, namespace, namespaces) + + for _, ns := range namespaces { + err = r.setupTargetNamespace(ns) + if err != nil { + return r.fail(fmt.Sprintf("failed to create an additional namespace %s for %s test: %s", ns, CryostatMultiNamespaceTestName, err.Error())) + } + } + + // Create a default ClusterCryostat CR + _, err = r.createAndWaitTillClusterCryostatAvailable(newClusterCryostatCR(CryostatMultiNamespaceTestName, namespace, namespaces, !r.OpenShift)) + if err != nil { + return r.fail(fmt.Sprintf("%s test failed: %s", CryostatMultiNamespaceTestName, err.Error())) + } + + return r.TestResult +} + +// CryostatConfigChangeTest checks that the operator redeploys Cryostat in response to a change to Cryostat CR func CryostatConfigChangeTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) *scapiv1alpha3.TestResult { r := newTestResources(CryostatConfigChangeTestName) @@ -280,6 +309,7 @@ func CryostatRecordingTest(bundle *apimanifests.Bundle, namespace string, openSh return r.TestResult } +// CryostatReportTest checks that the operator deploys a report sidecar in response to a Cryostat CR func CryostatReportTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) *scapiv1alpha3.TestResult { r := newTestResources(CryostatReportTestName) From 4b8ad764340a9bd034e92c83f836fad6f4d05d85 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Tue, 16 Apr 2024 15:05:39 -0400 Subject: [PATCH 14/16] test(scorecard): add container logs for report sidecard test (#772) * test(scorecard): add container logs for report sidecard test odified: internal/test/scorecard/common_utils.go * test(scorecard): clean up --- ...yostat-operator.clusterserviceversion.yaml | 2 +- bundle/tests/scorecard/config.yaml | 12 +++--- config/scorecard/patches/custom.config.yaml | 12 +++--- internal/test/scorecard/common_utils.go | 42 +++++++++++++++---- internal/test/scorecard/logger.go | 41 ++++++++++++++---- internal/test/scorecard/tests.go | 5 +++ 6 files changed, 84 insertions(+), 30 deletions(-) diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index 1f978f085..02e4261ff 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -54,7 +54,7 @@ metadata: capabilities: Seamless Upgrades categories: Monitoring, Developer Tools containerImage: quay.io/cryostat/cryostat-operator:2.5.0-dev - createdAt: "2024-04-04T17:17:25Z" + createdAt: "2024-04-05T22:43:31Z" description: JVM monitoring and profiling tool operatorframework.io/initialization-resource: |- { diff --git a/bundle/tests/scorecard/config.yaml b/bundle/tests/scorecard/config.yaml index f536a45d3..3bdd1b03a 100644 --- a/bundle/tests/scorecard/config.yaml +++ b/bundle/tests/scorecard/config.yaml @@ -70,7 +70,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - operator-install - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171629 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154 labels: suite: cryostat test: operator-install @@ -80,7 +80,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171629 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154 labels: suite: cryostat test: cryostat-cr @@ -90,7 +90,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-multi-namespace - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171629 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154 labels: suite: cryostat test: cryostat-multi-namespace @@ -100,7 +100,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-recording - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171629 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154 labels: suite: cryostat test: cryostat-recording @@ -110,7 +110,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-config-change - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171629 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154 labels: suite: cryostat test: cryostat-config-change @@ -120,7 +120,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-report - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171629 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154 labels: suite: cryostat test: cryostat-report diff --git a/config/scorecard/patches/custom.config.yaml b/config/scorecard/patches/custom.config.yaml index 333688c77..fb8b6b2ce 100644 --- a/config/scorecard/patches/custom.config.yaml +++ b/config/scorecard/patches/custom.config.yaml @@ -8,7 +8,7 @@ entrypoint: - cryostat-scorecard-tests - operator-install - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171725" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154" labels: suite: cryostat test: operator-install @@ -18,7 +18,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171725" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154" labels: suite: cryostat test: cryostat-cr @@ -28,7 +28,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-multi-namespace - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171725" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154" labels: suite: cryostat test: cryostat-multi-namespace @@ -38,7 +38,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-recording - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171725" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154" labels: suite: cryostat test: cryostat-recording @@ -48,7 +48,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-config-change - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171725" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154" labels: suite: cryostat test: cryostat-config-change @@ -58,7 +58,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-report - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240404171725" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154" labels: suite: cryostat test: cryostat-report diff --git a/internal/test/scorecard/common_utils.go b/internal/test/scorecard/common_utils.go index 6bc7e6afa..981259bd4 100644 --- a/internal/test/scorecard/common_utils.go +++ b/internal/test/scorecard/common_utils.go @@ -600,26 +600,52 @@ func (r *TestResources) getCryostatPodNameForCR(cr *operatorv1beta1.Cryostat) (s selector := metav1.LabelSelector{ MatchLabels: map[string]string{ "app": cr.Name, + "kind": "cryostat", "component": "cryostat", }, } - opts := metav1.ListOptions{ - LabelSelector: labels.Set(selector.MatchLabels).String(), + + names, err := r.getPodnamesForSelector(cr.Namespace, selector) + if err != nil { + return "", err } - ctx, cancel := context.WithTimeout(context.TODO(), testTimeout) - defer cancel() + if len(names) == 0 { + return "", fmt.Errorf("no matching cryostat pods for cr: %s", cr.Name) + } + return names[0].ObjectMeta.Name, nil +} + +func (r *TestResources) getReportPodNameForCR(cr *operatorv1beta1.Cryostat) (string, error) { + selector := metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": cr.Name, + "kind": "cryostat", + "component": "reports", + }, + } - pods, err := r.Client.CoreV1().Pods(cr.Namespace).List(ctx, opts) + names, err := r.getPodnamesForSelector(cr.Namespace, selector) if err != nil { return "", err } - if len(pods.Items) == 0 { - return "", fmt.Errorf("no matching cryostat pods for cr: %s", cr.Name) + if len(names) == 0 { + return "", fmt.Errorf("no matching report sidecar pods for cr: %s", cr.Name) } + return names[0].ObjectMeta.Name, nil +} + +func (r *TestResources) getPodnamesForSelector(namespace string, selector metav1.LabelSelector) ([]corev1.Pod, error) { + labelSelector := labels.Set(selector.MatchLabels).String() - return pods.Items[0].ObjectMeta.Name, nil + ctx, cancel := context.WithTimeout(context.TODO(), testTimeout) + defer cancel() + + pods, err := r.Client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{ + LabelSelector: labelSelector, + }) + return pods.Items, err } func (r *TestResources) cleanupClusterCryostat(name string, namespace string, targetNamespaces []string) { diff --git a/internal/test/scorecard/logger.go b/internal/test/scorecard/logger.go index dd38d79b7..414439639 100644 --- a/internal/test/scorecard/logger.go +++ b/internal/test/scorecard/logger.go @@ -25,12 +25,16 @@ import ( ) type ContainerLog struct { + Namespace string + Pod string Container string Log string } func (r *TestResources) logContainer(namespace, podName, containerName string) { containerLog := &ContainerLog{ + Namespace: namespace, + Pod: podName, Container: containerName, } buf := &strings.Builder{} @@ -77,27 +81,46 @@ func (r *TestResources) CollectContainersLogsToResult() { logs := r.CollectLogs() for _, log := range logs { if log != nil { - r.Log += fmt.Sprintf("\n%s CONTAINER LOG:\n\n\t%s\n", strings.ToUpper(log.Container), log.Log) + r.Log += fmt.Sprintf("\nNAMESPACE: %s\nPOD: %s\nCONTAINER: %s\nLOG:\n\t%s\n", + strings.ToUpper(log.Namespace), + strings.ToUpper(log.Pod), + strings.ToUpper(log.Container), + log.Log, + ) } } } func (r *TestResources) StartLogs(cr *operatorv1beta1.Cryostat) error { - podName, err := r.getCryostatPodNameForCR(cr) + cryostatPodName, err := r.getCryostatPodNameForCR(cr) if err != nil { return fmt.Errorf("failed to get pod name for CR: %s", err.Error()) } - containerNames := []string{ - cr.Name, - cr.Name + "-grafana", - cr.Name + "-jfr-datasource", + logSelections := map[string][]string{ + cryostatPodName: { + cr.Name, + cr.Name + "-grafana", + cr.Name + "-jfr-datasource", + }, } + bufferSize := 3 - r.LogChannel = make(chan *ContainerLog, len(containerNames)) + if cr.Spec.ReportOptions != nil && cr.Spec.ReportOptions.Replicas > 0 { + reportPodName, err := r.getReportPodNameForCR(cr) + if err != nil { + return fmt.Errorf("failed to get pod name for report sidecar: %s", err.Error()) + } + logSelections[reportPodName] = []string{cr.Name + "-reports"} + bufferSize++ + } - for _, containerName := range containerNames { - go r.logContainer(cr.Namespace, podName, containerName) + r.LogChannel = make(chan *ContainerLog, bufferSize) + + for pod, containers := range logSelections { + for _, container := range containers { + go r.logContainer(cr.Namespace, pod, container) + } } return nil diff --git a/internal/test/scorecard/tests.go b/internal/test/scorecard/tests.go index c5d7e5b86..751105a28 100644 --- a/internal/test/scorecard/tests.go +++ b/internal/test/scorecard/tests.go @@ -342,5 +342,10 @@ func CryostatReportTest(bundle *apimanifests.Bundle, namespace string, openShift return r.fail(fmt.Sprintf("failed to reach the application: %s", err.Error())) } + err = r.StartLogs(cr) + if err != nil { + r.Log += fmt.Sprintf("failed to retrieve logs for the application: %s", err.Error()) + } + return r.TestResult } From d7b0379122b9129a76b0c25ffa29923fe4ca433a Mon Sep 17 00:00:00 2001 From: Ming Yu Wang <90855268+mwangggg@users.noreply.github.com> Date: Tue, 16 Apr 2024 17:02:10 -0400 Subject: [PATCH 15/16] test(scorecard): retry on cryostat update conflict (#774) * retry on cryostat update conflict * review * review --- ...yostat-operator.clusterserviceversion.yaml | 2 +- bundle/tests/scorecard/config.yaml | 12 +++--- config/scorecard/patches/custom.config.yaml | 12 +++--- internal/test/scorecard/common_utils.go | 37 ++++++++++++++++--- internal/test/scorecard/tests.go | 26 +------------ 5 files changed, 46 insertions(+), 43 deletions(-) diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index 02e4261ff..846bd4a61 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -54,7 +54,7 @@ metadata: capabilities: Seamless Upgrades categories: Monitoring, Developer Tools containerImage: quay.io/cryostat/cryostat-operator:2.5.0-dev - createdAt: "2024-04-05T22:43:31Z" + createdAt: "2024-04-16T20:46:24Z" description: JVM monitoring and profiling tool operatorframework.io/initialization-resource: |- { diff --git a/bundle/tests/scorecard/config.yaml b/bundle/tests/scorecard/config.yaml index 3bdd1b03a..c4cdfad27 100644 --- a/bundle/tests/scorecard/config.yaml +++ b/bundle/tests/scorecard/config.yaml @@ -70,7 +70,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - operator-install - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240416204604 labels: suite: cryostat test: operator-install @@ -80,7 +80,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240416204604 labels: suite: cryostat test: cryostat-cr @@ -90,7 +90,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-multi-namespace - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240416204604 labels: suite: cryostat test: cryostat-multi-namespace @@ -100,7 +100,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-recording - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240416204604 labels: suite: cryostat test: cryostat-recording @@ -110,7 +110,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-config-change - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240416204604 labels: suite: cryostat test: cryostat-config-change @@ -120,7 +120,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-report - image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240416204604 labels: suite: cryostat test: cryostat-report diff --git a/config/scorecard/patches/custom.config.yaml b/config/scorecard/patches/custom.config.yaml index fb8b6b2ce..f1f7f897b 100644 --- a/config/scorecard/patches/custom.config.yaml +++ b/config/scorecard/patches/custom.config.yaml @@ -8,7 +8,7 @@ entrypoint: - cryostat-scorecard-tests - operator-install - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240416204604" labels: suite: cryostat test: operator-install @@ -18,7 +18,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240416204604" labels: suite: cryostat test: cryostat-cr @@ -28,7 +28,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-multi-namespace - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240416204604" labels: suite: cryostat test: cryostat-multi-namespace @@ -38,7 +38,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-recording - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240416204604" labels: suite: cryostat test: cryostat-recording @@ -48,7 +48,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-config-change - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240416204604" labels: suite: cryostat test: cryostat-config-change @@ -58,7 +58,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-report - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240405224154" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240416204604" labels: suite: cryostat test: cryostat-report diff --git a/internal/test/scorecard/common_utils.go b/internal/test/scorecard/common_utils.go index 981259bd4..e311cd42b 100644 --- a/internal/test/scorecard/common_utils.go +++ b/internal/test/scorecard/common_utils.go @@ -31,11 +31,13 @@ import ( corev1 "k8s.io/api/core/v1" netv1 "k8s.io/api/networking/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/util/retry" ) const ( @@ -511,11 +513,34 @@ func (r *TestResources) sendHealthRequest(base *url.URL, healthCheck func(resp * return err } -func (r *TestResources) updateAndWaitTillCryostatAvailable(cr *operatorv1beta1.Cryostat) error { - cr, err := r.Client.OperatorCRDs().Cryostats(cr.Namespace).Update(context.Background(), cr) - if err != nil { - r.Log += fmt.Sprintf("failed to update Cryostat CR: %s", err.Error()) +func (r *TestResources) updateAndWaitTillCryostatAvailable(cr *operatorv1beta1.Cryostat) (*operatorv1beta1.Cryostat, error) { + ctx := context.Background() + + err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + var err error + cr, err = r.Client.OperatorCRDs().Cryostats(cr.Namespace).Get(ctx, cr.Name) + if err != nil { + return fmt.Errorf("failed to get Cryostat CR: %s", err.Error()) + } + + cr.Spec.StorageOptions = &operatorv1beta1.StorageConfiguration{ + PVC: &operatorv1beta1.PersistentVolumeClaimConfig{ + Spec: &corev1.PersistentVolumeClaimSpec{ + StorageClassName: nil, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: resource.MustParse("1Gi"), + }, + }, + }, + }, + } + + cr, err = r.Client.OperatorCRDs().Cryostats(cr.Namespace).Update(context.Background(), cr) return err + }) + if err != nil { + return nil, fmt.Errorf("failed to update Cryostat CR: %s", err.Error()) } // Poll the deployment until it becomes available or we timeout @@ -569,9 +594,9 @@ func (r *TestResources) updateAndWaitTillCryostatAvailable(cr *operatorv1beta1.C return false, nil }) if err != nil { - return fmt.Errorf("failed to look up deployment errors: %s", err.Error()) + return nil, fmt.Errorf("failed to look up deployment errors: %s", err.Error()) } - return err + return cr, err } func (r *TestResources) cleanupAndLogs(name string, namespace string) { diff --git a/internal/test/scorecard/tests.go b/internal/test/scorecard/tests.go index 751105a28..c741b0f6a 100644 --- a/internal/test/scorecard/tests.go +++ b/internal/test/scorecard/tests.go @@ -23,8 +23,6 @@ import ( operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" scapiv1alpha3 "github.com/operator-framework/api/pkg/apis/scorecard/v1alpha3" apimanifests "github.com/operator-framework/api/pkg/manifests" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -128,34 +126,14 @@ func CryostatConfigChangeTest(bundle *apimanifests.Bundle, namespace string, ope } // Switch Cryostat CR to PVC for redeployment - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - - cr, err = r.Client.OperatorCRDs().Cryostats(namespace).Get(ctx, CryostatConfigChangeTestName) - if err != nil { - return r.fail(fmt.Sprintf("failed to get Cryostat CR: %s", err.Error())) - } - cr.Spec.StorageOptions = &operatorv1beta1.StorageConfiguration{ - PVC: &operatorv1beta1.PersistentVolumeClaimConfig{ - Spec: &corev1.PersistentVolumeClaimSpec{ - StorageClassName: nil, - Resources: corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceStorage: resource.MustParse("1Gi"), - }, - }, - }, - }, - } - - // Wait for redeployment of Cryostat CR - err = r.updateAndWaitTillCryostatAvailable(cr) + cr, err = r.updateAndWaitTillCryostatAvailable(cr) if err != nil { return r.fail(fmt.Sprintf("Cryostat redeployment did not become available: %s", err.Error())) } r.Log += "Cryostat deployment has successfully updated with new spec template\n" base, err := url.Parse(cr.Status.ApplicationURL) + r.Log += fmt.Sprintf("base url: %s\n", base) if err != nil { return r.fail(fmt.Sprintf("application URL is invalid: %s", err.Error())) } From 0a426ad312bd1a247753e50abfe40cdfcdb7c28d Mon Sep 17 00:00:00 2001 From: Ming Yu Wang <90855268+mwangggg@users.noreply.github.com> Date: Fri, 10 May 2024 15:31:02 -0400 Subject: [PATCH 16/16] fix(reports): update deprecated quarkus properties (#818) --- .../cryostat-operator.clusterserviceversion.yaml | 2 +- config/scorecard/patches/custom.config.yaml | 12 ++++++------ .../resource_definitions/resource_definitions.go | 4 ++-- internal/test/resources.go | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index 846bd4a61..af99073ae 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -54,7 +54,7 @@ metadata: capabilities: Seamless Upgrades categories: Monitoring, Developer Tools containerImage: quay.io/cryostat/cryostat-operator:2.5.0-dev - createdAt: "2024-04-16T20:46:24Z" + createdAt: "2024-05-10T19:04:02Z" description: JVM monitoring and profiling tool operatorframework.io/initialization-resource: |- { diff --git a/config/scorecard/patches/custom.config.yaml b/config/scorecard/patches/custom.config.yaml index f1f7f897b..03b47bf13 100644 --- a/config/scorecard/patches/custom.config.yaml +++ b/config/scorecard/patches/custom.config.yaml @@ -8,7 +8,7 @@ entrypoint: - cryostat-scorecard-tests - operator-install - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240416204604" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240510190403" labels: suite: cryostat test: operator-install @@ -18,7 +18,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240416204604" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240510190403" labels: suite: cryostat test: cryostat-cr @@ -28,7 +28,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-multi-namespace - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240416204604" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240510190403" labels: suite: cryostat test: cryostat-multi-namespace @@ -38,7 +38,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-recording - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240416204604" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240510190403" labels: suite: cryostat test: cryostat-recording @@ -48,7 +48,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-config-change - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240416204604" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240510190403" labels: suite: cryostat test: cryostat-config-change @@ -58,7 +58,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-report - image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240416204604" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240510190403" labels: suite: cryostat test: cryostat-report diff --git a/internal/controllers/common/resource_definitions/resource_definitions.go b/internal/controllers/common/resource_definitions/resource_definitions.go index 5386aea96..d11aa42d8 100644 --- a/internal/controllers/common/resource_definitions/resource_definitions.go +++ b/internal/controllers/common/resource_definitions/resource_definitions.go @@ -477,11 +477,11 @@ func NewPodForReports(cr *model.CryostatInstance, imageTags *ImageTags, tls *TLS Value: strconv.Itoa(int(constants.ReportsContainerPort)), }, { - Name: "QUARKUS_HTTP_SSL_CERTIFICATE_KEY_FILE", + Name: "QUARKUS_HTTP_SSL_CERTIFICATE_KEY_FILES", Value: fmt.Sprintf("/var/run/secrets/operator.cryostat.io/%s/%s", tls.ReportsSecret, corev1.TLSPrivateKeyKey), }, { - Name: "QUARKUS_HTTP_SSL_CERTIFICATE_FILE", + Name: "QUARKUS_HTTP_SSL_CERTIFICATE_FILES", Value: fmt.Sprintf("/var/run/secrets/operator.cryostat.io/%s/%s", tls.ReportsSecret, corev1.TLSCertKey), }, { diff --git a/internal/test/resources.go b/internal/test/resources.go index d72f7b305..1aece6daa 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -1556,10 +1556,10 @@ func (r *TestResources) NewReportsEnvironmentVariables(resources *corev1.Resourc Name: "QUARKUS_HTTP_SSL_PORT", Value: "10000", }, corev1.EnvVar{ - Name: "QUARKUS_HTTP_SSL_CERTIFICATE_KEY_FILE", + Name: "QUARKUS_HTTP_SSL_CERTIFICATE_KEY_FILES", Value: fmt.Sprintf("/var/run/secrets/operator.cryostat.io/%s-reports-tls/tls.key", r.Name), }, corev1.EnvVar{ - Name: "QUARKUS_HTTP_SSL_CERTIFICATE_FILE", + Name: "QUARKUS_HTTP_SSL_CERTIFICATE_FILES", Value: fmt.Sprintf("/var/run/secrets/operator.cryostat.io/%s-reports-tls/tls.crt", r.Name), }, corev1.EnvVar{ Name: "QUARKUS_HTTP_INSECURE_REQUESTS",