From 830046db5cfde32606ed4da6718fbc958c1ccde9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 28 Feb 2024 15:16:20 -0500 Subject: [PATCH 01/17] add scorecard test/suite selection (#746) (#747) (cherry picked from commit 2af4362802fd7389c18e40c82ee155df61485c33) Co-authored-by: Ming Yu Wang <90855268+mwangggg@users.noreply.github.com> --- Makefile | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index df9c88915..81dc3a4ea 100644 --- a/Makefile +++ b/Makefile @@ -134,6 +134,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 @@ -158,9 +166,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 f204ab3e56da72a0a1f6aa18c0d2e07562400e7f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 28 Feb 2024 15:17:18 -0500 Subject: [PATCH 02/17] ci(gh): add comment when /build_test is finished (#745) (#748) (cherry picked from commit 6b2e997d823a5ffbc744423591f7ade1e376691f) Co-authored-by: Ming Yu Wang <90855268+mwangggg@users.noreply.github.com> --- .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 46c70ce9254c99fa9a8845534637b422470dd67c Mon Sep 17 00:00:00 2001 From: Ming Yu Wang <90855268+mwangggg@users.noreply.github.com> Date: Tue, 5 Mar 2024 09:55:06 -0500 Subject: [PATCH 03/17] chore(api): remove MaxWsConnections (#742) * remove MaxWsConnections * keep v1beta1 spec * add test v1->v2 --- api/v1beta1/cryostat_conversion.go | 2 -- api/v1beta1/cryostat_conversion_test.go | 15 +++++++++++---- api/v1beta2/cryostat_types.go | 5 ----- .../cryostat-operator.clusterserviceversion.yaml | 12 ------------ .../manifests/operator.cryostat.io_cryostats.yaml | 12 ------------ .../crd/bases/operator.cryostat.io_cryostats.yaml | 12 ------------ .../cryostat-operator.clusterserviceversion.yaml | 12 ------------ docs/config.md | 12 ------------ .../resource_definitions/resource_definitions.go | 11 ----------- internal/controllers/reconciler_test.go | 8 -------- internal/test/resources.go | 15 --------------- 11 files changed, 11 insertions(+), 105 deletions(-) diff --git a/api/v1beta1/cryostat_conversion.go b/api/v1beta1/cryostat_conversion.go index fd818f608..bb56a27cd 100644 --- a/api/v1beta1/cryostat_conversion.go +++ b/api/v1beta1/cryostat_conversion.go @@ -50,7 +50,6 @@ func convertSpecTo(src *CryostatSpec, dst *operatorv1beta2.CryostatSpec) { dst.ServiceOptions = convertServiceOptionsTo(src.ServiceOptions) dst.NetworkOptions = convertNetworkOptionsTo(src.NetworkOptions) dst.ReportOptions = convertReportOptionsTo(src.ReportOptions) - dst.MaxWsConnections = src.MaxWsConnections dst.JmxCacheOptions = convertJmxCacheOptionsTo(src.JmxCacheOptions) dst.Resources = convertResourceOptionsTo(src.Resources) dst.AuthProperties = convertAuthPropertiesTo(src.AuthProperties) @@ -336,7 +335,6 @@ func convertSpecFrom(src *operatorv1beta2.CryostatSpec, dst *CryostatSpec) { dst.ServiceOptions = convertServiceOptionsFrom(src.ServiceOptions) dst.NetworkOptions = convertNetworkOptionsFrom(src.NetworkOptions) dst.ReportOptions = convertReportOptionsFrom(src.ReportOptions) - dst.MaxWsConnections = src.MaxWsConnections dst.JmxCacheOptions = convertJmxCacheOptionsFrom(src.JmxCacheOptions) dst.Resources = convertResourceOptionsFrom(src.Resources) dst.AuthProperties = convertAuthPropertiesFrom(src.AuthProperties) diff --git a/api/v1beta1/cryostat_conversion_test.go b/api/v1beta1/cryostat_conversion_test.go index ae9129692..80a7dc41b 100644 --- a/api/v1beta1/cryostat_conversion_test.go +++ b/api/v1beta1/cryostat_conversion_test.go @@ -55,7 +55,7 @@ var _ = Describe("Cryostat", func() { Expect(converted).To(Equal(newCR)) }, - tableEntries(), + tableEntriesTo(), ) DescribeTable("converting from v1beta2 to v1beta1", @@ -71,11 +71,20 @@ var _ = Describe("Cryostat", func() { Expect(converted).To(Equal(oldCR)) }, - tableEntries(), + tableEntriesFrom(), ) }) +func tableEntriesTo() []TableEntry { + return append(tableEntries(), Entry("WS connections", (*test.TestResources).NewCryostatWithWsConnectionsSpecV1Beta1, + (*test.TestResources).NewCryostat)) +} + +func tableEntriesFrom() []TableEntry { + return tableEntries() +} + func tableEntries() []TableEntry { return []TableEntry{ Entry("defaults", (*test.TestResources).NewCryostatV1Beta1, @@ -134,8 +143,6 @@ func tableEntries() []TableEntry { (*test.TestResources).NewCryostatWithBuiltInPortConfigDisabled), Entry("JMX cache options", (*test.TestResources).NewCryostatWithJmxCacheOptionsSpecV1Beta1, (*test.TestResources).NewCryostatWithJmxCacheOptionsSpec), - Entry("WS connections", (*test.TestResources).NewCryostatWithWsConnectionsSpecV1Beta1, - (*test.TestResources).NewCryostatWithWsConnectionsSpec), Entry("subprocess heap", (*test.TestResources).NewCryostatWithReportSubprocessHeapSpecV1Beta1, (*test.TestResources).NewCryostatWithReportSubprocessHeapSpec), Entry("security", (*test.TestResources).NewCryostatWithSecurityOptionsV1Beta1, diff --git a/api/v1beta2/cryostat_types.go b/api/v1beta2/cryostat_types.go index e0e1f5279..5bd3a8584 100644 --- a/api/v1beta2/cryostat_types.go +++ b/api/v1beta2/cryostat_types.go @@ -64,11 +64,6 @@ type CryostatSpec struct { // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec ReportOptions *ReportConfiguration `json:"reportOptions,omitempty"` - // The maximum number of WebSocket client connections allowed (minimum 1, default unlimited). - // +optional - // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Max WebSocket Connections",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:number"} - // +kubebuilder:validation:Minimum=1 - MaxWsConnections int32 `json:"maxWsConnections,omitempty"` // Options to customize the JMX target connections cache for the Cryostat application. // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="JMX Connections Cache Options" diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index 5cb15f3da..80c9c28d2 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -182,12 +182,6 @@ spec: path: jmxCredentialsDatabaseOptions.databaseSecretName x-descriptors: - urn:alm:descriptor:io.kubernetes:Secret - - description: The maximum number of WebSocket client connections allowed (minimum - 1, default unlimited). - displayName: Max WebSocket Connections - path: maxWsConnections - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:number - description: Options to control how the operator exposes the application outside of the cluster, such as using an Ingress or Route. displayName: Network Options @@ -588,12 +582,6 @@ spec: path: jmxCredentialsDatabaseOptions.databaseSecretName x-descriptors: - urn:alm:descriptor:io.kubernetes:Secret - - description: The maximum number of WebSocket client connections allowed (minimum - 1, default unlimited). - displayName: Max WebSocket Connections - path: maxWsConnections - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:number - description: Options to control how the operator exposes the application outside of the cluster, such as using an Ingress or Route. displayName: Network Options diff --git a/bundle/manifests/operator.cryostat.io_cryostats.yaml b/bundle/manifests/operator.cryostat.io_cryostats.yaml index 26a20df2b..cf3940874 100644 --- a/bundle/manifests/operator.cryostat.io_cryostats.yaml +++ b/bundle/manifests/operator.cryostat.io_cryostats.yaml @@ -127,12 +127,6 @@ spec: credentials database. type: string type: object - maxWsConnections: - description: The maximum number of WebSocket client connections allowed - (minimum 1, default unlimited). - format: int32 - minimum: 1 - type: integer minimal: description: Deploy a pared-down Cryostat instance with no Grafana Dashboard or JFR Data Source. @@ -4844,12 +4838,6 @@ spec: credentials database. type: string type: object - maxWsConnections: - description: The maximum number of WebSocket client connections allowed - (minimum 1, default unlimited). - format: int32 - minimum: 1 - type: integer minimal: description: Deploy a pared-down Cryostat instance with no Grafana Dashboard or JFR Data Source. diff --git a/config/crd/bases/operator.cryostat.io_cryostats.yaml b/config/crd/bases/operator.cryostat.io_cryostats.yaml index 694eaaa07..11de5de7c 100644 --- a/config/crd/bases/operator.cryostat.io_cryostats.yaml +++ b/config/crd/bases/operator.cryostat.io_cryostats.yaml @@ -117,12 +117,6 @@ spec: credentials database. type: string type: object - maxWsConnections: - description: The maximum number of WebSocket client connections allowed - (minimum 1, default unlimited). - format: int32 - minimum: 1 - type: integer minimal: description: Deploy a pared-down Cryostat instance with no Grafana Dashboard or JFR Data Source. @@ -4834,12 +4828,6 @@ spec: credentials database. type: string type: object - maxWsConnections: - description: The maximum number of WebSocket client connections allowed - (minimum 1, default unlimited). - format: int32 - minimum: 1 - type: integer minimal: description: Deploy a pared-down Cryostat instance with no Grafana Dashboard or JFR Data Source. diff --git a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml index 6b5b54117..a53b0a9f5 100644 --- a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml @@ -138,12 +138,6 @@ spec: path: jmxCredentialsDatabaseOptions.databaseSecretName x-descriptors: - urn:alm:descriptor:io.kubernetes:Secret - - description: The maximum number of WebSocket client connections allowed (minimum - 1, default unlimited). - displayName: Max WebSocket Connections - path: maxWsConnections - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:number - description: Options to control how the operator exposes the application outside of the cluster, such as using an Ingress or Route. displayName: Network Options @@ -542,12 +536,6 @@ spec: path: jmxCredentialsDatabaseOptions.databaseSecretName x-descriptors: - urn:alm:descriptor:io.kubernetes:Secret - - description: The maximum number of WebSocket client connections allowed (minimum - 1, default unlimited). - displayName: Max WebSocket Connections - path: maxWsConnections - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:number - description: Options to control how the operator exposes the application outside of the cluster, such as using an Ingress or Route. displayName: Network Options diff --git a/docs/config.md b/docs/config.md index a57840b0c..afb374637 100644 --- a/docs/config.md +++ b/docs/config.md @@ -281,18 +281,6 @@ spec: When running on OpenShift, labels and annotations specified in `coreConfig` and `grafanaConfig` will be applied to the coresponding Routes created by the operator. -### Cryostat Client Options -The `maxWsConnections` property optionally specifies the maximum number of WebSocket client connections allowed. -The default number of `maxWsConnections` is unlimited. -```yaml -apiVersion: operator.cryostat.io/v1beta1 -kind: Cryostat -metadata: - name: cryostat-sample -spec: - maxWsConnections: 2 -``` - ### JMX Cache Configuration Options Cryostat's target JMX connection cache can be optionally configured with `targetCacheSize` and `targetCacheTTL`. `targetCacheSize` sets the maximum number of JMX connections cached by Cryostat. diff --git a/internal/controllers/common/resource_definitions/resource_definitions.go b/internal/controllers/common/resource_definitions/resource_definitions.go index 5386aea96..06c1a9d65 100644 --- a/internal/controllers/common/resource_definitions/resource_definitions.go +++ b/internal/controllers/common/resource_definitions/resource_definitions.go @@ -737,17 +737,6 @@ func NewCoreContainer(cr *model.CryostatInstance, specs *ServiceSpecs, imageTag envs = append(envs, insightsEnvs...) } - if cr.Spec.MaxWsConnections != 0 { - maxWsConnections := strconv.Itoa(int(cr.Spec.MaxWsConnections)) - maxWsConnectionsEnv := []corev1.EnvVar{ - { - Name: "CRYOSTAT_MAX_WS_CONNECTIONS", - Value: maxWsConnections, - }, - } - envs = append(envs, maxWsConnectionsEnv...) - } - targetCacheSize := "-1" targetCacheTTL := "10" if cr.Spec.JmxCacheOptions != nil { diff --git a/internal/controllers/reconciler_test.go b/internal/controllers/reconciler_test.go index 306455510..b6e2657ba 100644 --- a/internal/controllers/reconciler_test.go +++ b/internal/controllers/reconciler_test.go @@ -1622,14 +1622,6 @@ func (c *controllerTest) commonTests() { JustBeforeEach(func() { t.reconcileCryostatFully() }) - Context("containing MaxWsConnections", func() { - BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostatWithWsConnectionsSpec().Object) - }) - It("should set max WebSocket connections", func() { - t.checkCoreHasEnvironmentVariables(t.NewWsConnectionsEnv()) - }) - }) Context("containing SubProcessMaxHeapSize", func() { BeforeEach(func() { t.objs = append(t.objs, t.NewCryostatWithReportSubprocessHeapSpec().Object) diff --git a/internal/test/resources.go b/internal/test/resources.go index 1f4e16889..d94ac5403 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -560,12 +560,6 @@ func (r *TestResources) NewCryostatWithJmxCacheOptionsSpec() *model.CryostatInst return cr } -func (r *TestResources) NewCryostatWithWsConnectionsSpec() *model.CryostatInstance { - cr := r.NewCryostat() - cr.Spec.MaxWsConnections = 10 - return cr -} - func (r *TestResources) NewCryostatWithReportSubprocessHeapSpec() *model.CryostatInstance { cr := r.NewCryostat() if cr.Spec.ReportOptions == nil { @@ -1578,15 +1572,6 @@ func (r *TestResources) NewGrafanaEnvFromSource() []corev1.EnvFromSource { } } -func (r *TestResources) NewWsConnectionsEnv() []corev1.EnvVar { - return []corev1.EnvVar{ - { - Name: "CRYOSTAT_MAX_WS_CONNECTIONS", - Value: "10", - }, - } -} - func (r *TestResources) NewReportSubprocessHeapEnv() []corev1.EnvVar { return []corev1.EnvVar{ { From fcf7a8c50493ee4de3036fd766e4c19137a8966a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 6 Mar 2024 16:28:43 -0500 Subject: [PATCH 04/17] test(scorecard): scorecard tests for recording management (backport #698) (#752) * 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 (cherry picked from commit cfcbfc74fab65b78bf5fb63a8c5abe658d65fe6a) # Conflicts: # bundle/manifests/cryostat-operator.clusterserviceversion.yaml * Fix conflicts --------- Co-authored-by: Thuan Vo Co-authored-by: Elliott Baron --- .github/workflows/test-ci-reusable.yml | 26 +- ...yostat-operator.clusterserviceversion.yaml | 8 +- .../operator.cryostat.io_cryostats.yaml | 6 + bundle/tests/scorecard/config.yaml | 15 +- .../bases/operator.cryostat.io_cryostats.yaml | 6 + ...yostat-operator.clusterserviceversion.yaml | 6 + 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 +++++ 17 files changed, 1282 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 80c9c28d2..04a0f5f58 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-15T20:45:48Z" + createdAt: "2024-03-06T21:13:39Z" description: JVM monitoring and profiling tool operatorframework.io/initialization-resource: |- { @@ -182,6 +182,12 @@ spec: path: jmxCredentialsDatabaseOptions.databaseSecretName x-descriptors: - urn:alm:descriptor:io.kubernetes:Secret + - description: The maximum number of WebSocket client connections allowed (minimum + 1, default unlimited). + displayName: Max WebSocket Connections + path: maxWsConnections + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:number - description: Options to control how the operator exposes the application outside of the cluster, such as using an Ingress or Route. displayName: Network Options diff --git a/bundle/manifests/operator.cryostat.io_cryostats.yaml b/bundle/manifests/operator.cryostat.io_cryostats.yaml index cf3940874..13918ffcc 100644 --- a/bundle/manifests/operator.cryostat.io_cryostats.yaml +++ b/bundle/manifests/operator.cryostat.io_cryostats.yaml @@ -127,6 +127,12 @@ spec: credentials database. type: string type: object + maxWsConnections: + description: The maximum number of WebSocket client connections allowed + (minimum 1, default unlimited). + format: int32 + minimum: 1 + type: integer minimal: description: Deploy a pared-down Cryostat instance with no Grafana Dashboard or JFR Data Source. 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/crd/bases/operator.cryostat.io_cryostats.yaml b/config/crd/bases/operator.cryostat.io_cryostats.yaml index 11de5de7c..afa2befdd 100644 --- a/config/crd/bases/operator.cryostat.io_cryostats.yaml +++ b/config/crd/bases/operator.cryostat.io_cryostats.yaml @@ -117,6 +117,12 @@ spec: credentials database. type: string type: object + maxWsConnections: + description: The maximum number of WebSocket client connections allowed + (minimum 1, default unlimited). + format: int32 + minimum: 1 + type: integer minimal: description: Deploy a pared-down Cryostat instance with no Grafana Dashboard or JFR Data Source. diff --git a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml index a53b0a9f5..17d9dda93 100644 --- a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml @@ -536,6 +536,12 @@ spec: path: jmxCredentialsDatabaseOptions.databaseSecretName x-descriptors: - urn:alm:descriptor:io.kubernetes:Secret + - description: The maximum number of WebSocket client connections allowed (minimum + 1, default unlimited). + displayName: Max WebSocket Connections + path: maxWsConnections + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:number - description: Options to control how the operator exposes the application outside of the cluster, such as using an Ingress or Route. displayName: Network Options 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 385f81e2f21feac09454414dfe124cbd8727a739 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 8 Mar 2024 13:36:15 -0500 Subject: [PATCH 05/17] test(scorecard): scorecard test for Cryostat CR configuration changes (backport #739) (#756) * test(scorecard): scorecard test for Cryostat CR configuration changes (#739) * CR config scorecard * reformat * reviews * add kubectl license (cherry picked from commit bf8df159a12fb448d9c371da3807562a64523952) # Conflicts: # bundle/manifests/cryostat-operator.clusterserviceversion.yaml * Fix conflicts --------- Co-authored-by: Ming Yu Wang <90855268+mwangggg@users.noreply.github.com> Co-authored-by: Elliott Baron --- ...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 04a0f5f58..0d959bbe2 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-06T21:13:39Z" + createdAt: "2024-03-08T17:20:54Z" 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..7b8e524fc 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-20240307154322 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-20240307154322 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-20240307154322 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-20240307154322 + 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 5917c22f503627ed7a60a10ed5f817d5f292e8ec Mon Sep 17 00:00:00 2001 From: Ming Yu Wang <90855268+mwangggg@users.noreply.github.com> Date: Fri, 15 Mar 2024 16:30:20 -0400 Subject: [PATCH 06/17] chore(api): remove CommandConfig from NetworkOptions (#743) * remove CommandConfig * udpate * make bundle * correct test --- api/v1beta1/cryostat_conversion.go | 2 - api/v1beta1/cryostat_conversion_test.go | 7 +- api/v1beta2/cryostat_types.go | 7 - api/v1beta2/zz_generated.deepcopy.go | 5 - ...yostat-operator.clusterserviceversion.yaml | 21 +- .../operator.cryostat.io_cryostats.yaml | 315 ------------------ .../bases/operator.cryostat.io_cryostats.yaml | 315 ------------------ ...yostat-operator.clusterserviceversion.yaml | 19 -- internal/test/conversion.go | 15 + internal/test/resources.go | 37 ++ 10 files changed, 58 insertions(+), 685 deletions(-) diff --git a/api/v1beta1/cryostat_conversion.go b/api/v1beta1/cryostat_conversion.go index bb56a27cd..99a4af703 100644 --- a/api/v1beta1/cryostat_conversion.go +++ b/api/v1beta1/cryostat_conversion.go @@ -157,7 +157,6 @@ func convertNetworkOptionsTo(srcOpts *NetworkConfigurationList) *operatorv1beta2 dstOpts = &operatorv1beta2.NetworkConfigurationList{ CoreConfig: convertNetworkConfigTo(srcOpts.CoreConfig), GrafanaConfig: convertNetworkConfigTo(srcOpts.GrafanaConfig), - CommandConfig: convertNetworkConfigTo(srcOpts.CommandConfig), // TODO Remove this from v1beta2 API } } return dstOpts @@ -442,7 +441,6 @@ func convertNetworkOptionsFrom(srcOpts *operatorv1beta2.NetworkConfigurationList dstOpts = &NetworkConfigurationList{ CoreConfig: convertNetworkConfigFrom(srcOpts.CoreConfig), GrafanaConfig: convertNetworkConfigFrom(srcOpts.GrafanaConfig), - CommandConfig: convertNetworkConfigFrom(srcOpts.CommandConfig), // TODO Remove this from v1beta2 API } } return dstOpts diff --git a/api/v1beta1/cryostat_conversion_test.go b/api/v1beta1/cryostat_conversion_test.go index 80a7dc41b..5a60d5e73 100644 --- a/api/v1beta1/cryostat_conversion_test.go +++ b/api/v1beta1/cryostat_conversion_test.go @@ -77,8 +77,11 @@ var _ = Describe("Cryostat", func() { }) func tableEntriesTo() []TableEntry { - return append(tableEntries(), Entry("WS connections", (*test.TestResources).NewCryostatWithWsConnectionsSpecV1Beta1, - (*test.TestResources).NewCryostat)) + return append(tableEntries(), + Entry("WS connections", (*test.TestResources).NewCryostatWithWsConnectionsSpecV1Beta1, + (*test.TestResources).NewCryostat), + Entry("command config", (*test.TestResources).NewCryostatWithCommandConfigV1Beta1, + (*test.TestResources).NewCryostatWithIngress)) } func tableEntriesFrom() []TableEntry { diff --git a/api/v1beta2/cryostat_types.go b/api/v1beta2/cryostat_types.go index 5bd3a8584..6facea387 100644 --- a/api/v1beta2/cryostat_types.go +++ b/api/v1beta2/cryostat_types.go @@ -351,13 +351,6 @@ type NetworkConfigurationList struct { // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec CoreConfig *NetworkConfiguration `json:"coreConfig,omitempty"` - // Specifications for how to expose the Cryostat command service, - // which serves the WebSocket command channel. - // +optional - // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:hidden"} - // - // Deprecated: CommandConfig is no longer used. - CommandConfig *NetworkConfiguration `json:"commandConfig,omitempty"` // Specifications for how to expose Cryostat's Grafana service, // which serves the Grafana dashboard. // +optional diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index a8111e005..96818377e 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -413,11 +413,6 @@ func (in *NetworkConfigurationList) DeepCopyInto(out *NetworkConfigurationList) *out = new(NetworkConfiguration) (*in).DeepCopyInto(*out) } - if in.CommandConfig != nil { - in, out := &in.CommandConfig, &out.CommandConfig - *out = new(NetworkConfiguration) - (*in).DeepCopyInto(*out) - } if in.GrafanaConfig != nil { in, out := &in.GrafanaConfig, &out.GrafanaConfig *out = new(NetworkConfiguration) diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index 0d959bbe2..9c7321459 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-08T17:20:54Z" + createdAt: "2024-03-12T21:00:17Z" description: JVM monitoring and profiling tool operatorframework.io/initialization-resource: |- { @@ -592,25 +592,6 @@ spec: of the cluster, such as using an Ingress or Route. displayName: Network Options path: networkOptions - - description: "Specifications for how to expose the Cryostat command service, - which serves the WebSocket command channel. \n Deprecated: CommandConfig - is no longer used." - displayName: Command Config - path: networkOptions.commandConfig - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:hidden - - description: Annotations to add to the Ingress or Route during its creation. - displayName: Annotations - path: networkOptions.commandConfig.annotations - - description: Configuration for an Ingress object. Currently subpaths are not - supported, so unique hosts must be specified (if a single external IP is - being used) to differentiate between ingresses/services. - displayName: Ingress Spec - path: networkOptions.commandConfig.ingressSpec - - description: Labels to add to the Ingress or Route during its creation. The - label with key "app" is reserved for use by the operator. - displayName: Labels - path: networkOptions.commandConfig.labels - description: Specifications for how to expose the Cryostat service, which serves the Cryostat application. displayName: Core Config diff --git a/bundle/manifests/operator.cryostat.io_cryostats.yaml b/bundle/manifests/operator.cryostat.io_cryostats.yaml index 13918ffcc..44f3e7098 100644 --- a/bundle/manifests/operator.cryostat.io_cryostats.yaml +++ b/bundle/manifests/operator.cryostat.io_cryostats.yaml @@ -4852,321 +4852,6 @@ spec: description: Options to control how the operator exposes the application outside of the cluster, such as using an Ingress or Route. properties: - commandConfig: - description: "Specifications for how to expose the Cryostat command - service, which serves the WebSocket command channel. \n Deprecated: - CommandConfig is no longer used." - properties: - annotations: - additionalProperties: - type: string - description: Annotations to add to the Ingress or Route during - its creation. - type: object - ingressSpec: - description: Configuration for an Ingress object. Currently - subpaths are not supported, so unique hosts must be specified - (if a single external IP is being used) to differentiate - between ingresses/services. - properties: - defaultBackend: - description: DefaultBackend is the backend that should - handle requests that don't match any rule. If Rules - are not specified, DefaultBackend must be specified. - If DefaultBackend is not set, the handling of requests - that do not match any of the rules will be up to the - Ingress controller. - properties: - resource: - description: Resource is an ObjectRef to another Kubernetes - resource in the namespace of the Ingress object. - If resource is specified, a service.Name and service.Port - must not be specified. This is a mutually exclusive - setting with "Service". - properties: - apiGroup: - description: APIGroup is the group for the resource - being referenced. If APIGroup is not specified, - the specified Kind must be in the core API group. - For any other third-party types, APIGroup is - required. - type: string - kind: - description: Kind is the type of resource being - referenced - type: string - name: - description: Name is the name of resource being - referenced - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - service: - description: Service references a Service as a Backend. - This is a mutually exclusive setting with "Resource". - properties: - name: - description: Name is the referenced service. The - service must exist in the same namespace as - the Ingress object. - type: string - port: - description: Port of the referenced service. A - port name or port number is required for a IngressServiceBackend. - properties: - name: - description: Name is the name of the port - on the Service. This is a mutually exclusive - setting with "Number". - type: string - number: - description: Number is the numerical port - number (e.g. 80) on the Service. This is - a mutually exclusive setting with "Name". - format: int32 - type: integer - type: object - required: - - name - type: object - type: object - ingressClassName: - description: IngressClassName is the name of an IngressClass - cluster resource. Ingress controller implementations - use this field to know whether they should be serving - this Ingress resource, by a transitive connection (controller - -> IngressClass -> Ingress resource). Although the `kubernetes.io/ingress.class` - annotation (simple constant name) was never formally - defined, it was widely supported by Ingress controllers - to create a direct binding between Ingress controller - and Ingress resources. Newly created Ingress resources - should prefer using the field. However, even though - the annotation is officially deprecated, for backwards - compatibility reasons, ingress controllers should still - honor that annotation if present. - type: string - rules: - description: A list of host rules used to configure the - Ingress. If unspecified, or no rule matches, all traffic - is sent to the default backend. - items: - description: IngressRule represents the rules mapping - the paths under a specified host to the related backend - services. Incoming requests are first evaluated for - a host match, then routed to the backend associated - with the matching IngressRuleValue. - properties: - host: - description: "Host is the fully qualified domain - name of a network host, as defined by RFC 3986. - Note the following deviations from the \"host\" - part of the URI as defined in RFC 3986: 1. IPs - are not allowed. Currently an IngressRuleValue - can only apply to the IP in the Spec of the parent - Ingress. 2. The `:` delimiter is not respected - because ports are not allowed. Currently the port - of an Ingress is implicitly :80 for http and :443 - for https. Both these may change in the future. - Incoming requests are matched against the host - before the IngressRuleValue. If the host is unspecified, - the Ingress routes all traffic based on the specified - IngressRuleValue. \n Host can be \"precise\" which - is a domain name without the terminating dot of - a network host (e.g. \"foo.bar.com\") or \"wildcard\", - which is a domain name prefixed with a single - wildcard label (e.g. \"*.foo.com\"). The wildcard - character '*' must appear by itself as the first - DNS label and matches only a single label. You - cannot have a wildcard label by itself (e.g. Host - == \"*\"). Requests will be matched against the - Host field in the following way: 1. If Host is - precise, the request matches this rule if the - http host header is equal to Host. 2. If Host - is a wildcard, then the request matches this rule - if the http host header is to equal to the suffix - (removing the first label) of the wildcard rule." - type: string - http: - description: 'HTTPIngressRuleValue is a list of - http selectors pointing to backends. In the example: - http:///? -> backend where - where parts of the url correspond to RFC 3986, - this resource will be used to match against everything - after the last ''/'' and before the first ''?'' - or ''#''.' - properties: - paths: - description: A collection of paths that map - requests to backends. - items: - description: HTTPIngressPath associates a - path with a backend. Incoming urls matching - the path are forwarded to the backend. - properties: - backend: - description: Backend defines the referenced - service endpoint to which the traffic - will be forwarded to. - properties: - resource: - description: Resource is an ObjectRef - to another Kubernetes resource in - the namespace of the Ingress object. - If resource is specified, a service.Name - and service.Port must not be specified. - This is a mutually exclusive setting - with "Service". - properties: - apiGroup: - description: APIGroup is the group - for the resource being referenced. - If APIGroup is not specified, - the specified Kind must be in - the core API group. For any - other third-party types, APIGroup - is required. - type: string - kind: - description: Kind is the type - of resource being referenced - type: string - name: - description: Name is the name - of resource being referenced - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - service: - description: Service references a - Service as a Backend. This is a - mutually exclusive setting with - "Resource". - properties: - name: - description: Name is the referenced - service. The service must exist - in the same namespace as the - Ingress object. - type: string - port: - description: Port of the referenced - service. A port name or port - number is required for a IngressServiceBackend. - properties: - name: - description: Name is the name - of the port on the Service. - This is a mutually exclusive - setting with "Number". - type: string - number: - description: Number is the - numerical port number (e.g. - 80) on the Service. This - is a mutually exclusive - setting with "Name". - format: int32 - type: integer - type: object - required: - - name - type: object - type: object - path: - description: Path is matched against the - path of an incoming request. Currently - it can contain characters disallowed - from the conventional "path" part of - a URL as defined by RFC 3986. Paths - must begin with a '/' and must be present - when using PathType with value "Exact" - or "Prefix". - type: string - pathType: - description: 'PathType determines the - interpretation of the Path matching. - PathType can be one of the following - values: * Exact: Matches the URL path - exactly. * Prefix: Matches based on - a URL path prefix split by ''/''. Matching - is done on a path element by element - basis. A path element refers is the - list of labels in the path split by - the ''/'' separator. A request is a - match for path p if every p is an element-wise - prefix of p of the request path. Note - that if the last element of the path - is a substring of the last element in - request path, it is not a match (e.g. - /foo/bar matches /foo/bar/baz, but does - not match /foo/barbaz). * ImplementationSpecific: - Interpretation of the Path matching - is up to the IngressClass. Implementations - can treat this as a separate PathType - or treat it identically to Prefix or - Exact path types. Implementations are - required to support all path types.' - type: string - required: - - backend - - pathType - type: object - type: array - x-kubernetes-list-type: atomic - required: - - paths - type: object - type: object - type: array - x-kubernetes-list-type: atomic - tls: - description: TLS configuration. Currently the Ingress - only supports a single TLS port, 443. If multiple members - of this list specify different hosts, they will be multiplexed - on the same port according to the hostname specified - through the SNI TLS extension, if the ingress controller - fulfilling the ingress supports SNI. - items: - description: IngressTLS describes the transport layer - security associated with an Ingress. - properties: - hosts: - description: Hosts are a list of hosts included - in the TLS certificate. The values in this list - must match the name/s used in the tlsSecret. Defaults - to the wildcard host setting for the loadbalancer - controller fulfilling this Ingress, if left unspecified. - items: - type: string - type: array - x-kubernetes-list-type: atomic - secretName: - description: SecretName is the name of the secret - used to terminate TLS traffic on port 443. Field - is left optional to allow TLS routing based on - SNI hostname alone. If the SNI host in a listener - conflicts with the "Host" header field used by - an IngressRule, the SNI host is used for termination - and value of the Host header is used for routing. - type: string - type: object - type: array - x-kubernetes-list-type: atomic - type: object - labels: - additionalProperties: - type: string - description: Labels to add to the Ingress or Route during - its creation. The label with key "app" is reserved for use - by the operator. - type: object - type: object coreConfig: description: Specifications for how to expose the Cryostat service, which serves the Cryostat application. diff --git a/config/crd/bases/operator.cryostat.io_cryostats.yaml b/config/crd/bases/operator.cryostat.io_cryostats.yaml index afa2befdd..558887e1a 100644 --- a/config/crd/bases/operator.cryostat.io_cryostats.yaml +++ b/config/crd/bases/operator.cryostat.io_cryostats.yaml @@ -4842,321 +4842,6 @@ spec: description: Options to control how the operator exposes the application outside of the cluster, such as using an Ingress or Route. properties: - commandConfig: - description: "Specifications for how to expose the Cryostat command - service, which serves the WebSocket command channel. \n Deprecated: - CommandConfig is no longer used." - properties: - annotations: - additionalProperties: - type: string - description: Annotations to add to the Ingress or Route during - its creation. - type: object - ingressSpec: - description: Configuration for an Ingress object. Currently - subpaths are not supported, so unique hosts must be specified - (if a single external IP is being used) to differentiate - between ingresses/services. - properties: - defaultBackend: - description: DefaultBackend is the backend that should - handle requests that don't match any rule. If Rules - are not specified, DefaultBackend must be specified. - If DefaultBackend is not set, the handling of requests - that do not match any of the rules will be up to the - Ingress controller. - properties: - resource: - description: Resource is an ObjectRef to another Kubernetes - resource in the namespace of the Ingress object. - If resource is specified, a service.Name and service.Port - must not be specified. This is a mutually exclusive - setting with "Service". - properties: - apiGroup: - description: APIGroup is the group for the resource - being referenced. If APIGroup is not specified, - the specified Kind must be in the core API group. - For any other third-party types, APIGroup is - required. - type: string - kind: - description: Kind is the type of resource being - referenced - type: string - name: - description: Name is the name of resource being - referenced - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - service: - description: Service references a Service as a Backend. - This is a mutually exclusive setting with "Resource". - properties: - name: - description: Name is the referenced service. The - service must exist in the same namespace as - the Ingress object. - type: string - port: - description: Port of the referenced service. A - port name or port number is required for a IngressServiceBackend. - properties: - name: - description: Name is the name of the port - on the Service. This is a mutually exclusive - setting with "Number". - type: string - number: - description: Number is the numerical port - number (e.g. 80) on the Service. This is - a mutually exclusive setting with "Name". - format: int32 - type: integer - type: object - required: - - name - type: object - type: object - ingressClassName: - description: IngressClassName is the name of an IngressClass - cluster resource. Ingress controller implementations - use this field to know whether they should be serving - this Ingress resource, by a transitive connection (controller - -> IngressClass -> Ingress resource). Although the `kubernetes.io/ingress.class` - annotation (simple constant name) was never formally - defined, it was widely supported by Ingress controllers - to create a direct binding between Ingress controller - and Ingress resources. Newly created Ingress resources - should prefer using the field. However, even though - the annotation is officially deprecated, for backwards - compatibility reasons, ingress controllers should still - honor that annotation if present. - type: string - rules: - description: A list of host rules used to configure the - Ingress. If unspecified, or no rule matches, all traffic - is sent to the default backend. - items: - description: IngressRule represents the rules mapping - the paths under a specified host to the related backend - services. Incoming requests are first evaluated for - a host match, then routed to the backend associated - with the matching IngressRuleValue. - properties: - host: - description: "Host is the fully qualified domain - name of a network host, as defined by RFC 3986. - Note the following deviations from the \"host\" - part of the URI as defined in RFC 3986: 1. IPs - are not allowed. Currently an IngressRuleValue - can only apply to the IP in the Spec of the parent - Ingress. 2. The `:` delimiter is not respected - because ports are not allowed. Currently the port - of an Ingress is implicitly :80 for http and :443 - for https. Both these may change in the future. - Incoming requests are matched against the host - before the IngressRuleValue. If the host is unspecified, - the Ingress routes all traffic based on the specified - IngressRuleValue. \n Host can be \"precise\" which - is a domain name without the terminating dot of - a network host (e.g. \"foo.bar.com\") or \"wildcard\", - which is a domain name prefixed with a single - wildcard label (e.g. \"*.foo.com\"). The wildcard - character '*' must appear by itself as the first - DNS label and matches only a single label. You - cannot have a wildcard label by itself (e.g. Host - == \"*\"). Requests will be matched against the - Host field in the following way: 1. If Host is - precise, the request matches this rule if the - http host header is equal to Host. 2. If Host - is a wildcard, then the request matches this rule - if the http host header is to equal to the suffix - (removing the first label) of the wildcard rule." - type: string - http: - description: 'HTTPIngressRuleValue is a list of - http selectors pointing to backends. In the example: - http:///? -> backend where - where parts of the url correspond to RFC 3986, - this resource will be used to match against everything - after the last ''/'' and before the first ''?'' - or ''#''.' - properties: - paths: - description: A collection of paths that map - requests to backends. - items: - description: HTTPIngressPath associates a - path with a backend. Incoming urls matching - the path are forwarded to the backend. - properties: - backend: - description: Backend defines the referenced - service endpoint to which the traffic - will be forwarded to. - properties: - resource: - description: Resource is an ObjectRef - to another Kubernetes resource in - the namespace of the Ingress object. - If resource is specified, a service.Name - and service.Port must not be specified. - This is a mutually exclusive setting - with "Service". - properties: - apiGroup: - description: APIGroup is the group - for the resource being referenced. - If APIGroup is not specified, - the specified Kind must be in - the core API group. For any - other third-party types, APIGroup - is required. - type: string - kind: - description: Kind is the type - of resource being referenced - type: string - name: - description: Name is the name - of resource being referenced - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - service: - description: Service references a - Service as a Backend. This is a - mutually exclusive setting with - "Resource". - properties: - name: - description: Name is the referenced - service. The service must exist - in the same namespace as the - Ingress object. - type: string - port: - description: Port of the referenced - service. A port name or port - number is required for a IngressServiceBackend. - properties: - name: - description: Name is the name - of the port on the Service. - This is a mutually exclusive - setting with "Number". - type: string - number: - description: Number is the - numerical port number (e.g. - 80) on the Service. This - is a mutually exclusive - setting with "Name". - format: int32 - type: integer - type: object - required: - - name - type: object - type: object - path: - description: Path is matched against the - path of an incoming request. Currently - it can contain characters disallowed - from the conventional "path" part of - a URL as defined by RFC 3986. Paths - must begin with a '/' and must be present - when using PathType with value "Exact" - or "Prefix". - type: string - pathType: - description: 'PathType determines the - interpretation of the Path matching. - PathType can be one of the following - values: * Exact: Matches the URL path - exactly. * Prefix: Matches based on - a URL path prefix split by ''/''. Matching - is done on a path element by element - basis. A path element refers is the - list of labels in the path split by - the ''/'' separator. A request is a - match for path p if every p is an element-wise - prefix of p of the request path. Note - that if the last element of the path - is a substring of the last element in - request path, it is not a match (e.g. - /foo/bar matches /foo/bar/baz, but does - not match /foo/barbaz). * ImplementationSpecific: - Interpretation of the Path matching - is up to the IngressClass. Implementations - can treat this as a separate PathType - or treat it identically to Prefix or - Exact path types. Implementations are - required to support all path types.' - type: string - required: - - backend - - pathType - type: object - type: array - x-kubernetes-list-type: atomic - required: - - paths - type: object - type: object - type: array - x-kubernetes-list-type: atomic - tls: - description: TLS configuration. Currently the Ingress - only supports a single TLS port, 443. If multiple members - of this list specify different hosts, they will be multiplexed - on the same port according to the hostname specified - through the SNI TLS extension, if the ingress controller - fulfilling the ingress supports SNI. - items: - description: IngressTLS describes the transport layer - security associated with an Ingress. - properties: - hosts: - description: Hosts are a list of hosts included - in the TLS certificate. The values in this list - must match the name/s used in the tlsSecret. Defaults - to the wildcard host setting for the loadbalancer - controller fulfilling this Ingress, if left unspecified. - items: - type: string - type: array - x-kubernetes-list-type: atomic - secretName: - description: SecretName is the name of the secret - used to terminate TLS traffic on port 443. Field - is left optional to allow TLS routing based on - SNI hostname alone. If the SNI host in a listener - conflicts with the "Host" header field used by - an IngressRule, the SNI host is used for termination - and value of the Host header is used for routing. - type: string - type: object - type: array - x-kubernetes-list-type: atomic - type: object - labels: - additionalProperties: - type: string - description: Labels to add to the Ingress or Route during - its creation. The label with key "app" is reserved for use - by the operator. - type: object - type: object coreConfig: description: Specifications for how to expose the Cryostat service, which serves the Cryostat application. diff --git a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml index 17d9dda93..f119d07de 100644 --- a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml @@ -142,25 +142,6 @@ spec: of the cluster, such as using an Ingress or Route. displayName: Network Options path: networkOptions - - description: "Specifications for how to expose the Cryostat command service, - which serves the WebSocket command channel. \n Deprecated: CommandConfig - is no longer used." - displayName: Command Config - path: networkOptions.commandConfig - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:hidden - - description: Annotations to add to the Ingress or Route during its creation. - displayName: Annotations - path: networkOptions.commandConfig.annotations - - description: Configuration for an Ingress object. Currently subpaths are not - supported, so unique hosts must be specified (if a single external IP is - being used) to differentiate between ingresses/services. - displayName: Ingress Spec - path: networkOptions.commandConfig.ingressSpec - - description: Labels to add to the Ingress or Route during its creation. The - label with key "app" is reserved for use by the operator. - displayName: Labels - path: networkOptions.commandConfig.labels - description: Specifications for how to expose the Cryostat service, which serves the Cryostat application. displayName: Core Config diff --git a/internal/test/conversion.go b/internal/test/conversion.go index ab123ddd3..246e9ca6a 100644 --- a/internal/test/conversion.go +++ b/internal/test/conversion.go @@ -478,6 +478,21 @@ func (r *TestResources) NewCryostatWithWsConnectionsSpecV1Beta1() *operatorv1bet return cr } +func (r *TestResources) NewCryostatWithCommandConfigV1Beta1() *operatorv1beta1.Cryostat { + commandSVC := r.NewCommandService() + commandIng := r.newNetworkConfigurationV1Beta1(commandSVC.Name, commandSVC.Spec.Ports[0].Port) + commandIng.Annotations["command"] = "annotation" + commandIng.Labels["command"] = "label" + + cr := r.NewCryostatWithIngressV1Beta1() + cr.Spec.NetworkOptions = &operatorv1beta1.NetworkConfigurationList{ + CoreConfig: cr.Spec.NetworkOptions.CoreConfig, + GrafanaConfig: cr.Spec.NetworkOptions.GrafanaConfig, + CommandConfig: &commandIng, + } + return cr +} + func (r *TestResources) NewCryostatWithReportSubprocessHeapSpecV1Beta1() *operatorv1beta1.Cryostat { cr := r.NewCryostatV1Beta1() if cr.Spec.ReportOptions == nil { diff --git a/internal/test/resources.go b/internal/test/resources.go index d94ac5403..da76eaed0 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -761,6 +761,43 @@ func (r *TestResources) NewGrafanaService() *corev1.Service { } } +func (r *TestResources) NewCommandService() *corev1.Service { + c := true + return &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: r.Name + "-command", + Namespace: r.Namespace, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: operatorv1beta2.GroupVersion.String(), + Kind: "Cryostat", + Name: r.Name, + UID: "", + Controller: &c, + }, + }, + Labels: map[string]string{ + "app": r.Name, + "component": "cryostat", + }, + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeClusterIP, + Selector: map[string]string{ + "app": r.Name, + "component": "cryostat", + }, + Ports: []corev1.ServicePort{ + { + Name: "http", + Port: 10001, + TargetPort: intstr.FromInt(10001), + }, + }, + }, + } +} + func (r *TestResources) NewReportsService() *corev1.Service { c := true return &corev1.Service{ From 8f807ef91fdc0098a905ed1b8f452c76941b2a2b Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:51:51 -0400 Subject: [PATCH 07/17] test(scorecard): scorecard test for report generator (backport #753) (#767) * test(scorecard): scorecard test for report generator (#753) * deploy reports sidecar * report scorecard test * update * rebase fix * query health (cherry picked from commit 96ea4cb7ae2c219eb37cf5583a2607c31d7889a7) # Conflicts: # bundle/manifests/cryostat-operator.clusterserviceversion.yaml # bundle/tests/scorecard/config.yaml * Fix conflicts --------- Co-authored-by: Ming Yu Wang <90855268+mwangggg@users.noreply.github.com> Co-authored-by: Elliott Baron --- ...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 9c7321459..a7af2fe52 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-12T21:00:17Z" + createdAt: "2024-03-15T21:38:16Z" 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 7b8e524fc..8822c74a5 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-20240307154322 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313155212 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-20240307154322 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313155212 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-20240307154322 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313155212 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-20240307154322 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240313155212 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-20240313155212 + 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 8fd5307feb772c5f5ff56bfed333f83dd6390f1a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:52:21 -0400 Subject: [PATCH 08/17] fix(build-ci): fix scorecard image tag returned as null (#760) (#768) Signed-off-by: Thuan Vo Co-authored-by: Elliott Baron (cherry picked from commit 2201704822677f0e1ad94fee0d64250a7af2e67c) Co-authored-by: Thuan Vo --- .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 2619ae0ab12a2d766dcd6e26b31f60e46401d3b5 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Tue, 19 Mar 2024 10:34:48 -0400 Subject: [PATCH 09/17] chore(api): remove minimal option from CR spec (#751) --- api/v1beta1/cryostat_conversion.go | 2 - api/v1beta1/cryostat_conversion_test.go | 5 +- api/v1beta2/cryostat_types.go | 3 - ...yostat-operator.clusterserviceversion.yaml | 10 +- .../operator.cryostat.io_cryostats.yaml | 6 - .../bases/operator.cryostat.io_cryostats.yaml | 6 - ...yostat-operator.clusterserviceversion.yaml | 7 - config/samples/operator_v1beta2_cryostat.yaml | 1 - docs/config.md | 11 -- internal/controllers/certmanager.go | 45 +---- .../resource_definitions.go | 83 ++++----- internal/controllers/ingresses.go | 5 +- internal/controllers/reconciler.go | 2 - internal/controllers/reconciler_test.go | 164 ++---------------- internal/controllers/routes.go | 16 +- internal/controllers/secrets.go | 36 ++-- internal/controllers/services.go | 38 ++-- internal/test/conversion.go | 13 +- internal/test/resources.go | 88 +++++----- 19 files changed, 143 insertions(+), 398 deletions(-) diff --git a/api/v1beta1/cryostat_conversion.go b/api/v1beta1/cryostat_conversion.go index 99a4af703..de2b74a35 100644 --- a/api/v1beta1/cryostat_conversion.go +++ b/api/v1beta1/cryostat_conversion.go @@ -42,7 +42,6 @@ func (src *Cryostat) ConvertTo(dstRaw conversion.Hub) error { } func convertSpecTo(src *CryostatSpec, dst *operatorv1beta2.CryostatSpec) { - dst.Minimal = src.Minimal dst.EnableCertManager = src.EnableCertManager dst.TrustedCertSecrets = convertCertSecretsTo(src.TrustedCertSecrets) dst.EventTemplates = convertEventTemplatesTo(src.EventTemplates) @@ -326,7 +325,6 @@ func (dst *Cryostat) ConvertFrom(srcRaw conversion.Hub) error { } func convertSpecFrom(src *operatorv1beta2.CryostatSpec, dst *CryostatSpec) { - dst.Minimal = src.Minimal dst.EnableCertManager = src.EnableCertManager dst.TrustedCertSecrets = convertCertSecretsFrom(src.TrustedCertSecrets) dst.EventTemplates = convertEventTemplatesFrom(src.EventTemplates) diff --git a/api/v1beta1/cryostat_conversion_test.go b/api/v1beta1/cryostat_conversion_test.go index 5a60d5e73..a7cbc1ec0 100644 --- a/api/v1beta1/cryostat_conversion_test.go +++ b/api/v1beta1/cryostat_conversion_test.go @@ -81,7 +81,10 @@ func tableEntriesTo() []TableEntry { Entry("WS connections", (*test.TestResources).NewCryostatWithWsConnectionsSpecV1Beta1, (*test.TestResources).NewCryostat), Entry("command config", (*test.TestResources).NewCryostatWithCommandConfigV1Beta1, - (*test.TestResources).NewCryostatWithIngress)) + (*test.TestResources).NewCryostatWithIngress), + Entry("minimal mode", (*test.TestResources).NewCryostatWithMinimalModeV1Beta1, + (*test.TestResources).NewCryostat), + ) } func tableEntriesFrom() []TableEntry { diff --git a/api/v1beta2/cryostat_types.go b/api/v1beta2/cryostat_types.go index 6facea387..db2257e90 100644 --- a/api/v1beta2/cryostat_types.go +++ b/api/v1beta2/cryostat_types.go @@ -31,9 +31,6 @@ type CryostatSpec struct { // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,order=2 TargetNamespaces []string `json:"targetNamespaces,omitempty"` - // Deploy a pared-down Cryostat instance with no Grafana Dashboard or JFR Data Source. - // +operator-sdk:csv:customresourcedefinitions:type=spec,order=4,displayName="Minimal Deployment",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} - Minimal bool `json:"minimal"` // List of TLS certificates to trust when connecting to targets. // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Trusted TLS Certificates" diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index a7af2fe52..d3894b2e1 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -36,7 +36,6 @@ metadata: "spec": { "enableCertManager": true, "eventTemplates": [], - "minimal": false, "reportOptions": { "replicas": 0 }, @@ -54,7 +53,7 @@ metadata: capabilities: Seamless Upgrades categories: Monitoring, Developer Tools containerImage: quay.io/cryostat/cryostat-operator:2.5.0-dev - createdAt: "2024-03-15T21:38:16Z" + createdAt: "2024-03-18T06:34:51Z" description: JVM monitoring and profiling tool operatorframework.io/initialization-resource: |- { @@ -65,7 +64,6 @@ metadata: }, "spec": { "enableCertManager": true, - "minimal": false, "reportOptions": { "replicas": 0 } @@ -527,12 +525,6 @@ spec: path: enableCertManager x-descriptors: - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - - description: Deploy a pared-down Cryostat instance with no Grafana Dashboard - or JFR Data Source. - displayName: Minimal Deployment - path: minimal - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - description: Override default authorization properties for Cryostat on OpenShift. displayName: Authorization Properties path: authProperties diff --git a/bundle/manifests/operator.cryostat.io_cryostats.yaml b/bundle/manifests/operator.cryostat.io_cryostats.yaml index 44f3e7098..32b212f9e 100644 --- a/bundle/manifests/operator.cryostat.io_cryostats.yaml +++ b/bundle/manifests/operator.cryostat.io_cryostats.yaml @@ -4844,10 +4844,6 @@ spec: credentials database. type: string type: object - minimal: - description: Deploy a pared-down Cryostat instance with no Grafana - Dashboard or JFR Data Source. - type: boolean networkOptions: description: Options to control how the operator exposes the application outside of the cluster, such as using an Ingress or Route. @@ -9056,8 +9052,6 @@ spec: - secretName type: object type: array - required: - - minimal type: object status: description: CryostatStatus defines the observed state of Cryostat. diff --git a/config/crd/bases/operator.cryostat.io_cryostats.yaml b/config/crd/bases/operator.cryostat.io_cryostats.yaml index 558887e1a..96567c509 100644 --- a/config/crd/bases/operator.cryostat.io_cryostats.yaml +++ b/config/crd/bases/operator.cryostat.io_cryostats.yaml @@ -4834,10 +4834,6 @@ spec: credentials database. type: string type: object - minimal: - description: Deploy a pared-down Cryostat instance with no Grafana - Dashboard or JFR Data Source. - type: boolean networkOptions: description: Options to control how the operator exposes the application outside of the cluster, such as using an Ingress or Route. @@ -9046,8 +9042,6 @@ spec: - secretName type: object type: array - required: - - minimal type: object status: description: CryostatStatus defines the observed state of Cryostat. diff --git a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml index f119d07de..5595c1aa6 100644 --- a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml @@ -15,7 +15,6 @@ metadata: }, "spec": { "enableCertManager": true, - "minimal": false, "reportOptions": { "replicas": 0 } @@ -77,12 +76,6 @@ spec: path: enableCertManager x-descriptors: - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - - description: Deploy a pared-down Cryostat instance with no Grafana Dashboard - or JFR Data Source. - displayName: Minimal Deployment - path: minimal - x-descriptors: - - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - description: Override default authorization properties for Cryostat on OpenShift. displayName: Authorization Properties path: authProperties diff --git a/config/samples/operator_v1beta2_cryostat.yaml b/config/samples/operator_v1beta2_cryostat.yaml index 69cc3b956..8f014f31d 100644 --- a/config/samples/operator_v1beta2_cryostat.yaml +++ b/config/samples/operator_v1beta2_cryostat.yaml @@ -3,7 +3,6 @@ kind: Cryostat metadata: name: cryostat-sample spec: - minimal: false enableCertManager: true trustedCertSecrets: [] eventTemplates: [] diff --git a/docs/config.md b/docs/config.md index afb374637..1633b3f95 100644 --- a/docs/config.md +++ b/docs/config.md @@ -20,17 +20,6 @@ When installed in a multi-namespace manner, all users with access to a Cryostat For now, all authorization checks are done against the namespace where Cryostat is installed. For a user to use Cryostat with workloads in a target namespace, that user must have the necessary Kubernetes permissions in the namespace where Cryostat is installed. -### Minimal Deployment -The `spec.minimal` property determines what is deployed alongside Cryostat. This value is set to `false` by default, which tells the operator to deploy Cryostat, with a [customized Grafana](https://github.com/cryostatio/cryostat-grafana-dashboard) and a [Grafana Data Source for JFR files](https://github.com/cryostatio/jfr-datasource) as 3 containers within a Pod. When `minimal` is set to `true`, the Deployment consists of only the Cryostat container. -```yaml -apiVersion: operator.cryostat.io/v1beta1 -kind: Cryostat -metadata: - name: cryostat-sample -spec: - minimal: true -``` - ### Disabling cert-manager Integration By default, the operator expects [cert-manager](https://cert-manager.io/) to be available in the cluster. The operator uses cert-manager to generate a self-signed CA to allow traffic between Cryostat components within the cluster to use HTTPS. If cert-manager is not available in the cluster, this integration can be disabled with the `spec.enableCertManager` property. ```yaml diff --git a/internal/controllers/certmanager.go b/internal/controllers/certmanager.go index 45b305b7a..294a47eea 100644 --- a/internal/controllers/certmanager.go +++ b/internal/controllers/certmanager.go @@ -24,7 +24,6 @@ import ( resources "github.com/cryostatio/cryostat-operator/internal/controllers/common/resource_definitions" "github.com/cryostatio/cryostat-operator/internal/controllers/model" corev1 "k8s.io/api/core/v1" - kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -95,26 +94,13 @@ func (r *Reconciler) setupTLS(ctx context.Context, cr *model.CryostatInstance) ( } certificates := []*certv1.Certificate{caCert, cryostatCert, reportsCert} // Create a certificate for Grafana signed by the Cryostat CA - if !cr.Spec.Minimal { - grafanaCert := resources.NewGrafanaCert(cr) - err = r.createOrUpdateCertificate(ctx, grafanaCert, cr.Object) - if err != nil { - return nil, err - } - certificates = append(certificates, grafanaCert) - tlsConfig.GrafanaSecret = grafanaCert.Spec.SecretName - } else { - grafanaCert := resources.NewGrafanaCert(cr) - secret := secretForCertificate(grafanaCert) - err = r.deleteSecret(ctx, secret) - if err != nil { - return nil, err - } - err = r.deleteCert(ctx, grafanaCert) - if err != nil { - return nil, err - } + grafanaCert := resources.NewGrafanaCert(cr) + err = r.createOrUpdateCertificate(ctx, grafanaCert, cr.Object) + if err != nil { + return nil, err } + certificates = append(certificates, grafanaCert) + tlsConfig.GrafanaSecret = grafanaCert.Spec.SecretName // Update owner references of TLS secrets created by cert-manager to ensure proper cleanup err = r.setCertSecretOwner(ctx, cr.Object, certificates...) @@ -211,15 +197,6 @@ func (r *Reconciler) setCertSecretOwner(ctx context.Context, owner metav1.Object return nil } -func secretForCertificate(cert *certv1.Certificate) *corev1.Secret { - return &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: cert.Spec.SecretName, - Namespace: cert.Namespace, - }, - } -} - func (r *Reconciler) certManagerAvailable() (bool, error) { // Check if cert-manager API is available. Checking just one should be enough. _, err := r.RESTMapper.RESTMapping(schema.GroupKind{ @@ -316,16 +293,6 @@ func (r *Reconciler) createOrUpdateCertSecret(ctx context.Context, secret *corev return nil } -func (r *Reconciler) deleteCert(ctx context.Context, cert *certv1.Certificate) error { - err := r.Client.Delete(ctx, cert) - if err != nil && !kerrors.IsNotFound(err) { - r.Log.Error(err, "Could not delete certificate", "name", cert.Name, "namespace", cert.Namespace) - return err - } - r.Log.Info("Cert deleted", "name", cert.Name, "namespace", cert.Namespace) - return nil -} - func (r *Reconciler) getCertficateBytes(ctx context.Context, cert *certv1.Certificate) ([]byte, error) { secret, err := r.GetCertificateSecret(ctx, cert) if err != nil { diff --git a/internal/controllers/common/resource_definitions/resource_definitions.go b/internal/controllers/common/resource_definitions/resource_definitions.go index 06c1a9d65..a3cf2bf43 100644 --- a/internal/controllers/common/resource_definitions/resource_definitions.go +++ b/internal/controllers/common/resource_definitions/resource_definitions.go @@ -237,17 +237,10 @@ func NewDeploymentForReports(cr *model.CryostatInstance, imageTags *ImageTags, t func NewPodForCR(cr *model.CryostatInstance, specs *ServiceSpecs, imageTags *ImageTags, tls *TLSConfig, fsGroup int64, openshift bool) *corev1.PodSpec { - var containers []corev1.Container - if cr.Spec.Minimal { - containers = []corev1.Container{ - NewCoreContainer(cr, specs, imageTags.CoreImageTag, tls, openshift), - } - } else { - containers = []corev1.Container{ - NewCoreContainer(cr, specs, imageTags.CoreImageTag, tls, openshift), - NewGrafanaContainer(cr, imageTags.GrafanaImageTag, tls), - NewJfrDatasourceContainer(cr, imageTags.DatasourceImageTag), - } + containers := []corev1.Container{ + NewCoreContainer(cr, specs, imageTags.CoreImageTag, tls, openshift), + NewGrafanaContainer(cr, imageTags.GrafanaImageTag, tls), + NewJfrDatasourceContainer(cr, imageTags.DatasourceImageTag), } volumes := newVolumeForCR(cr) @@ -296,35 +289,31 @@ func NewPodForCR(cr *model.CryostatInstance, specs *ServiceSpecs, imageTags *Ima }, }) - keyVolume := corev1.Volume{ - Name: "keystore", - VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: tls.CryostatSecret, - Items: []corev1.KeyToPath{ - { - Key: "keystore.p12", - Path: "keystore.p12", - Mode: &readOnlyMode, + volumes = append(volumes, + corev1.Volume{ + Name: "keystore", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: tls.CryostatSecret, + Items: []corev1.KeyToPath{ + { + Key: "keystore.p12", + Path: "keystore.p12", + Mode: &readOnlyMode, + }, }, }, }, }, - } - - volumes = append(volumes, keyVolume) - - if !cr.Spec.Minimal { - grafanaSecretVolume := corev1.Volume{ + corev1.Volume{ Name: "grafana-tls-secret", VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ SecretName: tls.GrafanaSecret, }, }, - } - volumes = append(volumes, grafanaSecretVolume) - } + }, + ) } // Project certificate secrets into deployment @@ -886,26 +875,24 @@ func NewCoreContainer(cr *model.CryostatInstance, specs *ServiceSpecs, imageTag }, }) - if !cr.Spec.Minimal { - grafanaVars := []corev1.EnvVar{ - { - Name: "GRAFANA_DATASOURCE_URL", - Value: datasourceURL, + grafanaVars := []corev1.EnvVar{ + { + Name: "GRAFANA_DATASOURCE_URL", + Value: datasourceURL, + }, + } + if specs.GrafanaURL != nil { + grafanaVars = append(grafanaVars, + corev1.EnvVar{ + Name: "GRAFANA_DASHBOARD_EXT_URL", + Value: specs.GrafanaURL.String(), }, - } - if specs.GrafanaURL != nil { - grafanaVars = append(grafanaVars, - corev1.EnvVar{ - Name: "GRAFANA_DASHBOARD_EXT_URL", - Value: specs.GrafanaURL.String(), - }, - corev1.EnvVar{ - Name: "GRAFANA_DASHBOARD_URL", - Value: getInternalDashboardURL(tls), - }) - } - envs = append(envs, grafanaVars...) + corev1.EnvVar{ + Name: "GRAFANA_DASHBOARD_URL", + Value: getInternalDashboardURL(tls), + }) } + envs = append(envs, grafanaVars...) livenessProbeScheme := corev1.URISchemeHTTP if tls == nil { diff --git a/internal/controllers/ingresses.go b/internal/controllers/ingresses.go index 33cde225a..307307875 100644 --- a/internal/controllers/ingresses.go +++ b/internal/controllers/ingresses.go @@ -61,10 +61,9 @@ func (r *Reconciler) reconcileGrafanaIngress(ctx context.Context, cr *model.Cryo }, } - if cr.Spec.Minimal || cr.Spec.NetworkOptions == nil || cr.Spec.NetworkOptions.GrafanaConfig == nil || + if cr.Spec.NetworkOptions == nil || cr.Spec.NetworkOptions.GrafanaConfig == nil || cr.Spec.NetworkOptions.GrafanaConfig.IngressSpec == nil { - // User has either chosen a minimal deployment or not requested - // an Ingress, delete if it exists + // User has not requested an Ingress, delete if it exists return r.deleteIngress(ctx, ingress) } grafanaConfig := configureGrafanaIngress(cr) diff --git a/internal/controllers/reconciler.go b/internal/controllers/reconciler.go index 812da566f..2973af1ef 100644 --- a/internal/controllers/reconciler.go +++ b/internal/controllers/reconciler.go @@ -176,8 +176,6 @@ func (r *Reconciler) reconcileCryostat(ctx context.Context, cr *model.CryostatIn } } - reqLogger.Info("Spec", "Minimal", cr.Spec.Minimal) - // Create lock config map or fail if owned by another CR err := r.reconcileLockConfigMap(ctx, cr) if err != nil { diff --git a/internal/controllers/reconciler_test.go b/internal/controllers/reconciler_test.go index b6e2657ba..57c7402ef 100644 --- a/internal/controllers/reconciler_test.go +++ b/internal/controllers/reconciler_test.go @@ -275,46 +275,6 @@ func (c *controllerTest) commonTests() { }) } }) - Context("succesfully creates required resources for minimal deployment", func() { - BeforeEach(func() { - t.Minimal = true - t.GeneratedPasswords = []string{"credentials_database", "jmx", "keystore"} - t.objs = append(t.objs, t.NewCryostat().Object) - }) - JustBeforeEach(func() { - t.reconcileCryostatFully() - }) - It("should create certificates", func() { - t.expectCertificates() - }) - It("should create RBAC", func() { - t.expectRBAC() - }) - It("should create routes", func() { - t.expectRoutes() - }) - It("should create persistent volume claim and set owner", func() { - t.expectPVC(t.NewDefaultPVC()) - }) - It("should create Credentials Database secret and set owner", func() { - t.expectCredentialsDatabaseSecret() - }) - It("should create JMX secret and set owner", func() { - t.expectJMXSecret() - }) - It("should create core service and set owner", func() { - t.expectCoreService() - }) - It("should set ApplicationURL in CR Status", func() { - t.expectStatusApplicationURL() - }) - It("should not set GrafanaSecret in CR Status", func() { - t.expectStatusGrafanaSecretName("") - }) - It("should create deployment and set owner", func() { - t.expectMainDeployment() - }) - }) Context("after cryostat reconciled successfully", func() { BeforeEach(func() { t.objs = append(t.objs, t.NewCryostat().Object) @@ -326,18 +286,6 @@ func (c *controllerTest) commonTests() { t.expectIdempotence() }) }) - Context("After a minimal cryostat reconciled successfully", func() { - BeforeEach(func() { - t.Minimal = true - t.objs = append(t.objs, t.NewCryostat().Object) - }) - JustBeforeEach(func() { - t.reconcileCryostatFully() - }) - It("should be idempotent", func() { - t.expectIdempotence() - }) - }) Context("Cryostat does not exist", func() { It("should do nothing", func() { result, err := t.reconcileWithName("does-not-exist") @@ -609,88 +557,17 @@ func (c *controllerTest) commonTests() { t.reconcileCryostatFully() }) It("should update the Routes", func() { - if !t.Minimal { - expected := t.NewGrafanaRoute() - metav1.SetMetaDataAnnotation(&expected.ObjectMeta, "grafana", "annotation") - metav1.SetMetaDataLabel(&expected.ObjectMeta, "grafana", "label") - t.checkRoute(expected) - } - expected := t.NewCoreRoute() + expected := t.NewGrafanaRoute() + metav1.SetMetaDataAnnotation(&expected.ObjectMeta, "grafana", "annotation") + metav1.SetMetaDataLabel(&expected.ObjectMeta, "grafana", "label") + t.checkRoute(expected) + + expected = t.NewCoreRoute() metav1.SetMetaDataAnnotation(&expected.ObjectMeta, "custom", "annotation") metav1.SetMetaDataLabel(&expected.ObjectMeta, "custom", "label") t.checkRoute(expected) }) }) - Context("Switching from a minimal to a non-minimal deployment", func() { - BeforeEach(func() { - t.Minimal = true - t.GeneratedPasswords = []string{"credentials_database", "jmx", "keystore", "grafana"} - t.objs = append(t.objs, t.NewCryostat().Object) - }) - JustBeforeEach(func() { - t.reconcileCryostatFully() - - cryostat := t.getCryostatInstance() - - t.Minimal = false - cryostat.Spec.Minimal = false - t.updateCryostatInstance(cryostat) - - t.reconcileCryostatFully() - }) - It("should create Grafana network resources", func() { - t.expectGrafanaService() - }) - It("should create the Grafana secret", func() { - t.expectGrafanaSecret() - t.expectStatusGrafanaSecretName(t.NewGrafanaSecret().Name) - }) - It("should configure deployment appropriately", func() { - t.expectMainDeployment() - }) - It("should create certificates", func() { - t.expectCertificates() - }) - }) - Context("Switching from a non-minimal to a minimal deployment", func() { - BeforeEach(func() { - t.objs = append(t.objs, t.NewCryostat().Object) - }) - JustBeforeEach(func() { - t.reconcileCryostatFully() - - cryostat := t.getCryostatInstance() - - t.Minimal = true - cryostat.Spec.Minimal = true - t.updateCryostatInstance(cryostat) - - t.reconcileCryostatFully() - }) - It("should delete Grafana network resources", func() { - service := &corev1.Service{} - err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-grafana", Namespace: t.Namespace}, service) - Expect(kerrors.IsNotFound(err)).To(BeTrue()) - - route := &openshiftv1.Route{} - err = t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-grafana", Namespace: t.Namespace}, route) - Expect(kerrors.IsNotFound(err)).To(BeTrue()) - }) - It("should delete the Grafana secret", func() { - secret := &corev1.Secret{} - notExpected := t.NewGrafanaSecret() - err := t.Client.Get(context.Background(), types.NamespacedName{Name: notExpected.Name, Namespace: notExpected.Namespace}, secret) - Expect(kerrors.IsNotFound(err)).To(BeTrue()) - - t.expectStatusGrafanaSecretName("") - }) - It("should configure deployment appropriately", func() { - t.expectMainDeployment() - }) - It("should create certificates", func() { - t.expectCertificates() - }) - }) Context("with report generator service", func() { var cr *model.CryostatInstance BeforeEach(func() { @@ -2323,9 +2200,7 @@ func (c *controllerTest) commonTests() { } func (t *cryostatTestInput) expectRoutes() { - if !t.Minimal { - t.checkRoute(t.NewGrafanaRoute()) - } + t.checkRoute(t.NewGrafanaRoute()) t.checkRoute(t.NewCoreRoute()) } @@ -2420,16 +2295,7 @@ func (t *cryostatTestInput) expectWaitingForCertificate() { func (t *cryostatTestInput) expectCertificates() { // Check certificates - certs := []*certv1.Certificate{t.NewCryostatCert(), t.NewCACert(), t.NewReportsCert()} - if !t.Minimal { - certs = append(certs, t.NewGrafanaCert()) - } else { - actual := &certv1.Certificate{} - expected := t.NewGrafanaCert() - err := t.Client.Get(context.Background(), types.NamespacedName{Name: expected.Name, Namespace: expected.Namespace}, actual) - Expect(err).To(HaveOccurred()) - Expect(kerrors.IsNotFound(err)) - } + certs := []*certv1.Certificate{t.NewCryostatCert(), t.NewCACert(), t.NewReportsCert(), t.NewGrafanaCert()} for _, expected := range certs { actual := &certv1.Certificate{} err := t.Client.Get(context.Background(), types.NamespacedName{Name: expected.Name, Namespace: expected.Namespace}, actual) @@ -2867,15 +2733,13 @@ func (t *cryostatTestInput) checkMainPodTemplate(deployment *appsv1.Deployment, dbSecretProvided, t.NewCoreContainerResource(cr), t.NewCoreSecurityContext(cr)) - if !t.Minimal { - // Check that Grafana is configured properly, depending on the environment - grafanaContainer := template.Spec.Containers[1] - t.checkGrafanaContainer(&grafanaContainer, t.NewGrafanaContainerResource(cr), t.NewGrafanaSecurityContext(cr)) + // Check that Grafana is configured properly, depending on the environment + grafanaContainer := template.Spec.Containers[1] + t.checkGrafanaContainer(&grafanaContainer, t.NewGrafanaContainerResource(cr), t.NewGrafanaSecurityContext(cr)) - // Check that JFR Datasource is configured properly - datasourceContainer := template.Spec.Containers[2] - t.checkDatasourceContainer(&datasourceContainer, t.NewDatasourceContainerResource(cr), t.NewDatasourceSecurityContext(cr)) - } + // Check that JFR Datasource is configured properly + datasourceContainer := template.Spec.Containers[2] + t.checkDatasourceContainer(&datasourceContainer, t.NewDatasourceContainerResource(cr), t.NewDatasourceSecurityContext(cr)) // Check that the proper Service Account is set Expect(template.Spec.ServiceAccountName).To(Equal(t.Name)) diff --git a/internal/controllers/routes.go b/internal/controllers/routes.go index 6f2a6145d..ef0b3920d 100644 --- a/internal/controllers/routes.go +++ b/internal/controllers/routes.go @@ -27,7 +27,6 @@ import ( "github.com/cryostatio/cryostat-operator/internal/controllers/model" routev1 "github.com/openshift/api/route/v1" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) @@ -61,10 +60,7 @@ func (r *Reconciler) reconcileGrafanaRoute(ctx context.Context, svc *corev1.Serv Namespace: cr.InstallNamespace, }, } - if cr.Spec.Minimal { - // Delete route if it exists - return r.deleteRoute(ctx, route) - } + grafanaConfig := configureGrafanaRoute(cr) url, err := r.reconcileRoute(ctx, route, svc, cr, tls, grafanaConfig) if err != nil { @@ -145,16 +141,6 @@ func getProtocol(route *routev1.Route) string { return "https" } -func (r *Reconciler) deleteRoute(ctx context.Context, route *routev1.Route) error { - err := r.Client.Delete(ctx, route) - if err != nil && !errors.IsNotFound(err) { - r.Log.Error(err, "Could not delete route", "name", route.Name, "namespace", route.Namespace) - return err - } - r.Log.Info("Route deleted", "name", route.Name, "namespace", route.Namespace) - return nil -} - func getHTTPPort(svc *corev1.Service) (*corev1.ServicePort, error) { for _, port := range svc.Spec.Ports { if port.Name == constants.HttpPortName { diff --git a/internal/controllers/secrets.go b/internal/controllers/secrets.go index 66ff48c49..278ff5813 100644 --- a/internal/controllers/secrets.go +++ b/internal/controllers/secrets.go @@ -43,34 +43,24 @@ func (r *Reconciler) reconcileGrafanaSecret(ctx context.Context, cr *model.Cryos }, } - var secretName string - if cr.Spec.Minimal { - err := r.deleteSecret(ctx, secret) - if err != nil { - return err + err := r.createOrUpdateSecret(ctx, secret, cr.Object, func() error { + if secret.StringData == nil { + secret.StringData = map[string]string{} } - secretName = "" - } else { - err := r.createOrUpdateSecret(ctx, secret, cr.Object, func() error { - if secret.StringData == nil { - secret.StringData = map[string]string{} - } - secret.StringData["GF_SECURITY_ADMIN_USER"] = "admin" - - // Password is generated, so don't regenerate it when updating - if secret.CreationTimestamp.IsZero() { - secret.StringData["GF_SECURITY_ADMIN_PASSWORD"] = r.GenPasswd(20) - } - return nil - }) - if err != nil { - return err + secret.StringData["GF_SECURITY_ADMIN_USER"] = "admin" + + // Password is generated, so don't regenerate it when updating + if secret.CreationTimestamp.IsZero() { + secret.StringData["GF_SECURITY_ADMIN_PASSWORD"] = r.GenPasswd(20) } - secretName = secret.Name + return nil + }) + if err != nil { + return err } // Set the Grafana secret in the CR status - cr.Status.GrafanaSecret = secretName + cr.Status.GrafanaSecret = secret.Name return r.Client.Status().Update(ctx, cr.Object) } diff --git a/internal/controllers/services.go b/internal/controllers/services.go index 6718c2d2f..9a4c87408 100644 --- a/internal/controllers/services.go +++ b/internal/controllers/services.go @@ -81,31 +81,23 @@ func (r *Reconciler) reconcileGrafanaService(ctx context.Context, cr *model.Cryo }, } - if cr.Spec.Minimal { - // Delete service if it exists - err := r.deleteService(ctx, svc) - if err != nil { - return err + config := configureGrafanaService(cr) + err := r.createOrUpdateService(ctx, svc, cr.Object, &config.ServiceConfig, func() error { + svc.Spec.Selector = map[string]string{ + "app": cr.Name, + "component": "cryostat", } - } else { - config := configureGrafanaService(cr) - err := r.createOrUpdateService(ctx, svc, cr.Object, &config.ServiceConfig, func() error { - svc.Spec.Selector = map[string]string{ - "app": cr.Name, - "component": "cryostat", - } - svc.Spec.Ports = []corev1.ServicePort{ - { - Name: "http", - Port: *config.HTTPPort, - TargetPort: intstr.IntOrString{IntVal: 3000}, - }, - } - return nil - }) - if err != nil { - return err + svc.Spec.Ports = []corev1.ServicePort{ + { + Name: "http", + Port: *config.HTTPPort, + TargetPort: intstr.IntOrString{IntVal: 3000}, + }, } + return nil + }) + if err != nil { + return err } if r.IsOpenShift { diff --git a/internal/test/conversion.go b/internal/test/conversion.go index 246e9ca6a..71eb6485e 100644 --- a/internal/test/conversion.go +++ b/internal/test/conversion.go @@ -43,12 +43,23 @@ func (r *TestResources) newCryostatSpecV1Beta1() operatorv1beta1.CryostatSpec { } } return operatorv1beta1.CryostatSpec{ - Minimal: r.Minimal, EnableCertManager: &certManager, ReportOptions: reportOptions, } } +func (r *TestResources) NewCryostatWithMinimalModeV1Beta1() *operatorv1beta1.Cryostat { + spec := r.newCryostatSpecV1Beta1() + spec.Minimal = true + return &operatorv1beta1.Cryostat{ + ObjectMeta: metav1.ObjectMeta{ + Name: r.Name, + Namespace: r.Namespace, + }, + Spec: spec, + } +} + func (r *TestResources) NewCryostatWithSecretsV1Beta1() *operatorv1beta1.Cryostat { cr := r.NewCryostatV1Beta1() key := "test.crt" diff --git a/internal/test/resources.go b/internal/test/resources.go index da76eaed0..3454d6da8 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -44,7 +44,6 @@ import ( type TestResources struct { Name string Namespace string - Minimal bool TLS bool ExternalTLS bool OpenShift bool @@ -106,7 +105,6 @@ func (r *TestResources) newCryostatSpec() operatorv1beta2.CryostatSpec { } return operatorv1beta2.CryostatSpec{ TargetNamespaces: r.TargetNamespaces, - Minimal: r.Minimal, EnableCertManager: &certManager, ReportOptions: reportOptions, } @@ -1340,15 +1338,13 @@ func (r *TestResources) NewCoreEnvironmentVariables(reportsUrl string, authProps Optional: &optional, }, }, - }) + }, + corev1.EnvVar{ + Name: "GRAFANA_DATASOURCE_URL", + Value: "http://127.0.0.1:8080", + }, + ) - if !r.Minimal { - envs = append(envs, - corev1.EnvVar{ - Name: "GRAFANA_DATASOURCE_URL", - Value: "http://127.0.0.1:8080", - }) - } if !r.TLS { envs = append(envs, corev1.EnvVar{ @@ -1472,33 +1468,31 @@ func (r *TestResources) newNetworkEnvironmentVariables() []corev1.EnvVar { Value: "80", }) } - if !r.Minimal { - if r.ExternalTLS { - envs = append(envs, - corev1.EnvVar{ - Name: "GRAFANA_DASHBOARD_EXT_URL", - Value: fmt.Sprintf("https://%s-grafana.example.com", r.Name), - }) - } else { - envs = append(envs, - corev1.EnvVar{ - Name: "GRAFANA_DASHBOARD_EXT_URL", - Value: fmt.Sprintf("http://%s-grafana.example.com", r.Name), - }) - } - if r.TLS { - envs = append(envs, - corev1.EnvVar{ - Name: "GRAFANA_DASHBOARD_URL", - Value: "https://cryostat-health.local:3000", - }) - } else { - envs = append(envs, - corev1.EnvVar{ - Name: "GRAFANA_DASHBOARD_URL", - Value: "http://cryostat-health.local:3000", - }) - } + if r.ExternalTLS { + envs = append(envs, + corev1.EnvVar{ + Name: "GRAFANA_DASHBOARD_EXT_URL", + Value: fmt.Sprintf("https://%s-grafana.example.com", r.Name), + }) + } else { + envs = append(envs, + corev1.EnvVar{ + Name: "GRAFANA_DASHBOARD_EXT_URL", + Value: fmt.Sprintf("http://%s-grafana.example.com", r.Name), + }) + } + if r.TLS { + envs = append(envs, + corev1.EnvVar{ + Name: "GRAFANA_DASHBOARD_URL", + Value: "https://cryostat-health.local:3000", + }) + } else { + envs = append(envs, + corev1.EnvVar{ + Name: "GRAFANA_DASHBOARD_URL", + Value: "http://cryostat-health.local:3000", + }) } return envs } @@ -2058,18 +2052,16 @@ func (r *TestResources) newVolumes(certProjections []corev1.VolumeProjection) [] }, }, }, - }) - if !r.Minimal { - volumes = append(volumes, - corev1.Volume{ - Name: "grafana-tls-secret", - VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: r.Name + "-grafana-tls", - }, + }, + corev1.Volume{ + Name: "grafana-tls-secret", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: r.Name + "-grafana-tls", }, - }) - } + }, + }, + ) } volumes = append(volumes, From 9f1fdee2a70b7bb232d8ca5f910f87aee3f19cf8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 18:01:55 -0400 Subject: [PATCH 10/17] test(scorecard): add container logs to scorecard results (backport #758) (#770) * 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 (cherry picked from commit d01e0d283d958b6540c9319c964d50f56964a330) # Conflicts: # bundle/manifests/cryostat-operator.clusterserviceversion.yaml # bundle/tests/scorecard/config.yaml * Fix conflicts --------- Co-authored-by: Thuan Vo Co-authored-by: Elliott Baron --- ...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 d3894b2e1..788b71fc2 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -53,7 +53,7 @@ metadata: capabilities: Seamless Upgrades categories: Monitoring, Developer Tools containerImage: quay.io/cryostat/cryostat-operator:2.5.0-dev - createdAt: "2024-03-18T06:34:51Z" + createdAt: "2024-03-19T22:00:40Z" 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 8822c74a5..b71a1c1a3 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-20240313155212 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240319215831 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-20240313155212 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240319215831 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-20240313155212 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240319215831 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-20240313155212 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240319215831 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-20240313155212 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240319215831 labels: suite: cryostat test: cryostat-report diff --git a/config/scorecard/patches/custom.config.yaml b/config/scorecard/patches/custom.config.yaml index f6e806ece..47d2cb999 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-20240319215831" 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-20240319215831" 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-20240319215831" 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-20240319215831" 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-20240319215831" 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 b19e8f9b6c1e14b756daf017ad66e8de825ad969 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 18:04:11 -0400 Subject: [PATCH 11/17] add permission to publish comment when ci fails (#769) (#771) Co-authored-by: Elliott Baron (cherry picked from commit 6498b0f92236ceb6374cfeeedfc230c73ac0e67f) Co-authored-by: Ming Yu Wang <90855268+mwangggg@users.noreply.github.com> --- .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 ecd45b3ac6d83ed208ffd329da4b61db1dda3a9c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 27 Mar 2024 12:56:15 -0400 Subject: [PATCH 12/17] build(go): update Golang to 1.21 (#777) (#778) (cherry picked from commit 903e93fa58f34b6c2182944ee0c334d705997904) Co-authored-by: Elliott Baron --- .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 d62951b64..daee119a6 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 3306fffe9ed7b2c47af32c8183215e6312d0d769 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:49:01 -0400 Subject: [PATCH 13/17] test(scorecard): logWorkloadEvent for cryostat-recording errors (backport #759) (#779) * test(scorecard): logWorkloadEvent for cryostat-recording errors (#759) * logWorkLoadEvent for cryostat-recording errors * reviews * tr.LogChannel --------- Co-authored-by: Elliott Baron (cherry picked from commit baebe17122b18cfef30d6b7259979a5c5ffec519) # Conflicts: # bundle/manifests/cryostat-operator.clusterserviceversion.yaml # bundle/tests/scorecard/config.yaml # config/scorecard/patches/custom.config.yaml * Fix conflicts --------- Co-authored-by: Ming Yu Wang <90855268+mwangggg@users.noreply.github.com> 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 788b71fc2..963dbf3de 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -53,7 +53,7 @@ metadata: capabilities: Seamless Upgrades categories: Monitoring, Developer Tools containerImage: quay.io/cryostat/cryostat-operator:2.5.0-dev - createdAt: "2024-03-19T22:00:40Z" + createdAt: "2024-03-27T17:35:46Z" 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 b71a1c1a3..2ba49230e 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-20240319215831 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327173340 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-20240319215831 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327173340 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-20240319215831 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327173340 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-20240319215831 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327173340 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-20240319215831 + image: quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327173340 labels: suite: cryostat test: cryostat-report diff --git a/config/scorecard/patches/custom.config.yaml b/config/scorecard/patches/custom.config.yaml index 47d2cb999..20303827e 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-20240319215831" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327173340" 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-20240319215831" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327173340" 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-20240319215831" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327173340" 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-20240319215831" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327173340" 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-20240319215831" + image: "quay.io/cryostat/cryostat-operator-scorecard:2.5.0-20240327173340" 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 db115b3902fa7236b58c86e8ebb49a331f2d8e34 Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Wed, 27 Mar 2024 14:28:02 -0400 Subject: [PATCH 14/17] chore(version): bump version to 3.0.0-dev (#781) --- Makefile | 4 ++-- .../cryostat-operator.clusterserviceversion.yaml | 10 +++++----- bundle/tests/scorecard/config.yaml | 10 +++++----- config/manager/kustomization.yaml | 2 +- .../bases/cryostat-operator.clusterserviceversion.yaml | 2 +- config/scorecard/patches/custom.config.yaml | 10 +++++----- internal/controllers/const_generated.go | 2 +- internal/controllers/cryostat_controller.go | 3 +++ internal/controllers/insights/test/resources.go | 2 +- internal/tools/const_generator.go | 6 +++--- 10 files changed, 27 insertions(+), 24 deletions(-) diff --git a/Makefile b/Makefile index 81dc3a4ea..fc33bbbd9 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ OS = $(shell go env GOOS) ARCH = $(shell go env GOARCH) # Current Operator version -export OPERATOR_VERSION ?= 2.5.0-dev +export OPERATOR_VERSION ?= 3.0.0-dev IMAGE_VERSION ?= $(OPERATOR_VERSION) BUNDLE_VERSION ?= $(IMAGE_VERSION) DEFAULT_NAMESPACE ?= quay.io/cryostat @@ -92,7 +92,7 @@ ENVTEST_K8S_VERSION ?= 1.26 # See: https://github.com/operator-framework/operator-sdk/pull/4762 # # Suffix is the timestamp of the image build, compute with: date -u '+%Y%m%d%H%M%S' -CUSTOM_SCORECARD_VERSION ?= 2.5.0-$(shell date -u '+%Y%m%d%H%M%S') +CUSTOM_SCORECARD_VERSION ?= 3.0.0-$(shell date -u '+%Y%m%d%H%M%S') export CUSTOM_SCORECARD_IMG ?= $(IMAGE_TAG_BASE)-scorecard:$(CUSTOM_SCORECARD_VERSION) DEPLOY_NAMESPACE ?= cryostat-operator-system diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index 963dbf3de..295b91d3d 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -52,8 +52,8 @@ metadata: ] capabilities: Seamless Upgrades categories: Monitoring, Developer Tools - containerImage: quay.io/cryostat/cryostat-operator:2.5.0-dev - createdAt: "2024-03-27T17:35:46Z" + containerImage: quay.io/cryostat/cryostat-operator:3.0.0-dev + createdAt: "2024-03-27T18:00:37Z" description: JVM monitoring and profiling tool operatorframework.io/initialization-resource: |- { @@ -77,7 +77,7 @@ metadata: operatorframework.io/arch.amd64: supported operatorframework.io/arch.arm64: supported operatorframework.io/os.linux: supported - name: cryostat-operator.v2.5.0-dev + name: cryostat-operator.v3.0.0-dev namespace: placeholder spec: apiservicedefinitions: {} @@ -1106,7 +1106,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: quay.io/cryostat/cryostat-operator:2.5.0-dev + image: quay.io/cryostat/cryostat-operator:3.0.0-dev imagePullPolicy: Always livenessProbe: httpGet: @@ -1234,7 +1234,7 @@ spec: name: grafana - image: quay.io/cryostat/cryostat-reports:latest name: reports - version: 2.5.0-dev + version: 3.0.0-dev webhookdefinitions: - admissionReviewVersions: - v1 diff --git a/bundle/tests/scorecard/config.yaml b/bundle/tests/scorecard/config.yaml index 2ba49230e..2d65c780a 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-20240327173340 + image: quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327175250 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-20240327173340 + image: quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327175250 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-20240327173340 + image: quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327175250 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-20240327173340 + image: quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327175250 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-20240327173340 + image: quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327175250 labels: suite: cryostat test: cryostat-report diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 116f3c815..55c8015cb 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -13,4 +13,4 @@ kind: Kustomization images: - name: controller newName: quay.io/cryostat/cryostat-operator - newTag: 2.5.0-dev + newTag: 3.0.0-dev diff --git a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml index 5595c1aa6..a4b0dad69 100644 --- a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml @@ -4,7 +4,7 @@ metadata: annotations: capabilities: Seamless Upgrades categories: Monitoring, Developer Tools - containerImage: quay.io/cryostat/cryostat-operator:2.5.0-dev + containerImage: quay.io/cryostat/cryostat-operator:3.0.0-dev 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 20303827e..5b91daab8 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-20240327173340" + image: "quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327175250" 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-20240327173340" + image: "quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327175250" 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-20240327173340" + image: "quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327175250" 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-20240327173340" + image: "quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327175250" 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-20240327173340" + image: "quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327175250" labels: suite: cryostat test: cryostat-report diff --git a/internal/controllers/const_generated.go b/internal/controllers/const_generated.go index 52d842a9c..94f769ea5 100644 --- a/internal/controllers/const_generated.go +++ b/internal/controllers/const_generated.go @@ -5,7 +5,7 @@ package controllers const AppName = "Cryostat" // Version of the Cryostat Operator -const OperatorVersion = "2.5.0-dev" +const OperatorVersion = "3.0.0-dev" // Default image tag for the core application image const DefaultCoreImageTag = "quay.io/cryostat/cryostat:latest" diff --git a/internal/controllers/cryostat_controller.go b/internal/controllers/cryostat_controller.go index e32027d23..37541e6b1 100644 --- a/internal/controllers/cryostat_controller.go +++ b/internal/controllers/cryostat_controller.go @@ -26,6 +26,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" ) +// Generates constants from environment variables at build time +//go:generate go run ../tools/const_generator.go + // Verify that *CryostatReconciler implements CommonReconciler. var _ CommonReconciler = (*CryostatReconciler)(nil) diff --git a/internal/controllers/insights/test/resources.go b/internal/controllers/insights/test/resources.go index dffbe9848..8c7937059 100644 --- a/internal/controllers/insights/test/resources.go +++ b/internal/controllers/insights/test/resources.go @@ -31,7 +31,7 @@ type InsightsTestResources struct { Resources *corev1.ResourceRequirements } -const expectedOperatorVersion = "2.5.0-dev" +const expectedOperatorVersion = "3.0.0-dev" func (r *InsightsTestResources) NewGlobalPullSecret() *corev1.Secret { config := `{"auths":{"example.com":{"auth":"hello"},"cloud.openshift.com":{"auth":"world"}}}` diff --git a/internal/tools/const_generator.go b/internal/tools/const_generator.go index b2b932c10..eadea2eeb 100644 --- a/internal/tools/const_generator.go +++ b/internal/tools/const_generator.go @@ -29,9 +29,9 @@ const datasourceImageEnv = "DATASOURCE_IMG" const grafanaImageEnv = "GRAFANA_IMG" const reportsImageEnv = "REPORTS_IMG" -// This program generates a imagetag_generated.go file containing image tag -// constants for each container image deployed by the operator. These constants -// are populated using environment variables. +// This program generates a const_generated.go file containing image tag +// constants for each container image deployed by the operator, along with +// other constants. These constants are populated using environment variables. func main() { // Fill in image tags struct from the environment variables consts := struct { From 0b380d227f7b047571f8adf0a2467281ac3a5758 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:49:14 -0400 Subject: [PATCH 15/17] test(scorecard): fix rebasing skipped commit (#780) (#782) * Merge pull request #8 from ebaron/scorecard-methods test(scorecard): use methods for more easily passing data * update bundle image (cherry picked from commit b3970953d2bfcc2cf2add227a8a0687f6a65b52c) Co-authored-by: Ming Yu Wang <90855268+mwangggg@users.noreply.github.com> --- ...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 295b91d3d..d9a0669a7 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -53,7 +53,7 @@ metadata: capabilities: Seamless Upgrades categories: Monitoring, Developer Tools containerImage: quay.io/cryostat/cryostat-operator:3.0.0-dev - createdAt: "2024-03-27T18:00:37Z" + createdAt: "2024-03-27T18:30:58Z" 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 2d65c780a..30f793a8d 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:3.0.0-20240327175250 + image: quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327182927 labels: suite: cryostat test: operator-install @@ -80,7 +80,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327175250 + image: quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327182927 labels: suite: cryostat test: cryostat-cr @@ -90,7 +90,7 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-recording - image: quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327175250 + image: quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327182927 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:3.0.0-20240327175250 + image: quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327182927 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:3.0.0-20240327175250 + image: quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327182927 labels: suite: cryostat test: cryostat-report diff --git a/config/scorecard/patches/custom.config.yaml b/config/scorecard/patches/custom.config.yaml index 5b91daab8..cba5454f8 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:3.0.0-20240327175250" + image: "quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327182927" labels: suite: cryostat test: operator-install @@ -18,7 +18,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: "quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327175250" + image: "quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327182927" labels: suite: cryostat test: cryostat-cr @@ -28,7 +28,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-recording - image: "quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327175250" + image: "quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327182927" labels: suite: cryostat test: cryostat-recording @@ -38,7 +38,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-config-change - image: "quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327175250" + image: "quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327182927" labels: suite: cryostat test: cryostat-config-change @@ -48,7 +48,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-report - image: "quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327175250" + image: "quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327182927" 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 3fcbfab356fb38594e9957c548738615a5ccdf03 Mon Sep 17 00:00:00 2001 From: Elliott Baron Date: Wed, 3 Apr 2024 16:26:26 -0400 Subject: [PATCH 16/17] ci(branch): run push workflows on cryostat3 branch (#786) --- .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 461096ccfcbad99ddfb002f5bcf803fb76d6c733 Mon Sep 17 00:00:00 2001 From: Ming Yu Wang <90855268+mwangggg@users.noreply.github.com> Date: Fri, 5 Apr 2024 17:47:34 -0400 Subject: [PATCH 17/17] test(scorecard): multi-namespace scorecard test for 3.0 (#773) * 3.0 multi-namespace scorecard test * refractor testResources --- api/v1beta1/cryostat_types.go | 2 +- ...yostat-operator.clusterserviceversion.yaml | 6 +- .../operator.cryostat.io_cryostats.yaml | 5 +- bundle/tests/scorecard/config.yaml | 20 +- .../bases/operator.cryostat.io_cryostats.yaml | 5 +- ...yostat-operator.clusterserviceversion.yaml | 4 +- config/scorecard/patches/custom.config.yaml | 20 +- hack/custom.config.yaml.in | 10 + .../images/custom-scorecard-tests/main.go | 4 + .../rbac/scorecard_role.yaml | 9 + internal/test/scorecard/clients.go | 18 +- internal/test/scorecard/common_utils.go | 249 +++++++++++------- internal/test/scorecard/logger.go | 4 +- internal/test/scorecard/tests.go | 89 ++++--- 14 files changed, 286 insertions(+), 159 deletions(-) diff --git a/api/v1beta1/cryostat_types.go b/api/v1beta1/cryostat_types.go index a480a28c6..9d554e068 100644 --- a/api/v1beta1/cryostat_types.go +++ b/api/v1beta1/cryostat_types.go @@ -419,7 +419,7 @@ type JmxCacheOptions struct { // Cryostat allows you to install Cryostat for a single namespace. // It contains configuration options for controlling the Deployment of the Cryostat // application and its related components. -// A ClusterCryostat or Cryostat instance must be created to instruct the operator +// A Cryostat instance must be created to instruct the operator // to deploy the Cryostat application. // +operator-sdk:csv:customresourcedefinitions:resources={{Deployment,v1},{Ingress,v1},{PersistentVolumeClaim,v1},{Secret,v1},{Service,v1},{Route,v1},{ConsoleLink,v1}} // +kubebuilder:printcolumn:name="Application URL",type=string,JSONPath=`.status.applicationUrl` diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index d9a0669a7..c8e3d1a59 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -53,7 +53,7 @@ metadata: capabilities: Seamless Upgrades categories: Monitoring, Developer Tools containerImage: quay.io/cryostat/cryostat-operator:3.0.0-dev - createdAt: "2024-03-27T18:30:58Z" + createdAt: "2024-04-04T17:19:02Z" description: JVM monitoring and profiling tool operatorframework.io/initialization-resource: |- { @@ -85,8 +85,8 @@ spec: owned: - description: Cryostat allows you to install Cryostat for a single namespace. It contains configuration options for controlling the Deployment of the Cryostat - application and its related components. A ClusterCryostat or Cryostat instance - must be created to instruct the operator to deploy the Cryostat application. + application and its related components. A Cryostat instance must be created + to instruct the operator to deploy the Cryostat application. displayName: Cryostat kind: Cryostat name: cryostats.operator.cryostat.io diff --git a/bundle/manifests/operator.cryostat.io_cryostats.yaml b/bundle/manifests/operator.cryostat.io_cryostats.yaml index 32b212f9e..6b5826616 100644 --- a/bundle/manifests/operator.cryostat.io_cryostats.yaml +++ b/bundle/manifests/operator.cryostat.io_cryostats.yaml @@ -37,9 +37,8 @@ spec: openAPIV3Schema: description: Cryostat allows you to install Cryostat for a single namespace. It contains configuration options for controlling the Deployment of the - Cryostat application and its related components. A ClusterCryostat or Cryostat - instance must be created to instruct the operator to deploy the Cryostat - application. + Cryostat application and its related components. A Cryostat instance must + be created to instruct the operator to deploy the Cryostat application. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation diff --git a/bundle/tests/scorecard/config.yaml b/bundle/tests/scorecard/config.yaml index 30f793a8d..acc4e5d26 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:3.0.0-20240327182927 + image: quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240404165315 labels: suite: cryostat test: operator-install @@ -80,17 +80,27 @@ stages: - entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327182927 + image: quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240404165315 labels: suite: cryostat test: cryostat-cr storage: spec: mountPath: {} + - entrypoint: + - cryostat-scorecard-tests + - cryostat-multi-namespace + image: quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240404165315 + labels: + suite: cryostat + test: cryostat-multi-namespace + storage: + spec: + mountPath: {} - entrypoint: - cryostat-scorecard-tests - cryostat-recording - image: quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327182927 + image: quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240404165315 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:3.0.0-20240327182927 + image: quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240404165315 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:3.0.0-20240327182927 + image: quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240404165315 labels: suite: cryostat test: cryostat-report diff --git a/config/crd/bases/operator.cryostat.io_cryostats.yaml b/config/crd/bases/operator.cryostat.io_cryostats.yaml index 96567c509..0739136ab 100644 --- a/config/crd/bases/operator.cryostat.io_cryostats.yaml +++ b/config/crd/bases/operator.cryostat.io_cryostats.yaml @@ -27,9 +27,8 @@ spec: openAPIV3Schema: description: Cryostat allows you to install Cryostat for a single namespace. It contains configuration options for controlling the Deployment of the - Cryostat application and its related components. A ClusterCryostat or Cryostat - instance must be created to instruct the operator to deploy the Cryostat - application. + Cryostat application and its related components. A Cryostat instance must + be created to instruct the operator to deploy the Cryostat application. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation diff --git a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml index a4b0dad69..f78ca7ac2 100644 --- a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml @@ -415,8 +415,8 @@ spec: version: v1beta2 - description: Cryostat allows you to install Cryostat for a single namespace. It contains configuration options for controlling the Deployment of the Cryostat - application and its related components. A ClusterCryostat or Cryostat instance - must be created to instruct the operator to deploy the Cryostat application. + application and its related components. A Cryostat instance must be created + to instruct the operator to deploy the Cryostat application. displayName: Cryostat kind: Cryostat name: cryostats.operator.cryostat.io diff --git a/config/scorecard/patches/custom.config.yaml b/config/scorecard/patches/custom.config.yaml index cba5454f8..f095f1d50 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:3.0.0-20240327182927" + image: "quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240404171904" labels: suite: cryostat test: operator-install @@ -18,17 +18,27 @@ entrypoint: - cryostat-scorecard-tests - cryostat-cr - image: "quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327182927" + image: "quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240404171904" 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:3.0.0-20240404171904" + 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:3.0.0-20240327182927" + image: "quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240404171904" labels: suite: cryostat test: cryostat-recording @@ -38,7 +48,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-config-change - image: "quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327182927" + image: "quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240404171904" labels: suite: cryostat test: cryostat-config-change @@ -48,7 +58,7 @@ entrypoint: - cryostat-scorecard-tests - cryostat-report - image: "quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240327182927" + image: "quay.io/cryostat/cryostat-operator-scorecard:3.0.0-20240404171904" 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..ca90abb1f 100644 --- a/internal/images/custom-scorecard-tests/rbac/scorecard_role.yaml +++ b/internal/images/custom-scorecard-tests/rbac/scorecard_role.yaml @@ -150,3 +150,12 @@ rules: - namespaces verbs: - create + - delete +- apiGroups: + - operator.cryostat.io + resources: + - cryostats + verbs: + - create + - delete + - get diff --git a/internal/test/scorecard/clients.go b/internal/test/scorecard/clients.go index 2c831e72f..05965499f 100644 --- a/internal/test/scorecard/clients.go +++ b/internal/test/scorecard/clients.go @@ -26,7 +26,7 @@ import ( "strings" "time" - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" + operatorv1beta2 "github.com/cryostatio/cryostat-operator/api/v1beta2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" @@ -101,10 +101,10 @@ func newOperatorCRDClient(config *rest.Config) (*OperatorCRDClient, error) { func newCRDClient(config *rest.Config) (*rest.RESTClient, error) { scheme := runtime.NewScheme() - if err := operatorv1beta1.AddToScheme(scheme); err != nil { + if err := operatorv1beta2.AddToScheme(scheme); err != nil { return nil, err } - return newRESTClientForGV(config, scheme, &operatorv1beta1.GroupVersion) + return newRESTClientForGV(config, scheme, &operatorv1beta2.GroupVersion) } func newRESTClientForGV(config *rest.Config, scheme *runtime.Scheme, gv *schema.GroupVersion) (*rest.RESTClient, error) { @@ -125,18 +125,18 @@ type CryostatClient struct { } // 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{}) +func (c *CryostatClient) Get(ctx context.Context, name string) (*operatorv1beta2.Cryostat, error) { + return get(ctx, c.restClient, c.resource, c.namespace, name, &operatorv1beta2.Cryostat{}) } // Create creates the provided Cryostat CR -func (c *CryostatClient) Create(ctx context.Context, obj *operatorv1beta1.Cryostat) (*operatorv1beta1.Cryostat, error) { - return create(ctx, c.restClient, c.resource, c.namespace, obj, &operatorv1beta1.Cryostat{}) +func (c *CryostatClient) Create(ctx context.Context, obj *operatorv1beta2.Cryostat) (*operatorv1beta2.Cryostat, error) { + return create(ctx, c.restClient, c.resource, c.namespace, obj, &operatorv1beta2.Cryostat{}) } // 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{}, obj.Name) +func (c *CryostatClient) Update(ctx context.Context, obj *operatorv1beta2.Cryostat) (*operatorv1beta2.Cryostat, error) { + return update(ctx, c.restClient, c.resource, c.namespace, obj, &operatorv1beta2.Cryostat{}, obj.Name) } // Delete deletes the Cryostat CR with the given name diff --git a/internal/test/scorecard/common_utils.go b/internal/test/scorecard/common_utils.go index 99c639ee0..c85c2c2f9 100644 --- a/internal/test/scorecard/common_utils.go +++ b/internal/test/scorecard/common_utils.go @@ -24,7 +24,7 @@ import ( "net/url" "time" - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" + operatorv1beta2 "github.com/cryostatio/cryostat-operator/api/v1beta2" scapiv1alpha3 "github.com/operator-framework/api/pkg/apis/scorecard/v1alpha3" appsv1 "k8s.io/api/apps/v1" @@ -44,13 +44,16 @@ const ( ) type TestResources struct { - OpenShift bool - Client *CryostatClientset - LogChannel chan *ContainerLog + Name string + Namespace string + TargetNamespaces []string + OpenShift bool + Client *CryostatClientset + LogChannel chan *ContainerLog *scapiv1alpha3.TestResult } -func (r *TestResources) waitForDeploymentAvailability(ctx context.Context, namespace string, name string) error { +func (r *TestResources) waitForDeploymentAvailability(ctx context.Context, name string, namespace string) error { err := wait.PollImmediateUntilWithContext(ctx, time.Second, func(ctx context.Context) (done bool, err error) { deploy, err := r.Client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { @@ -77,7 +80,7 @@ func (r *TestResources) waitForDeploymentAvailability(ctx context.Context, names return false, nil }) if err != nil { - logErr := r.logWorkloadEvents(namespace, name) + logErr := r.logWorkloadEvents(r.Name) if logErr != nil { r.Log += fmt.Sprintf("failed to look up deployment errors: %s\n", logErr.Error()) } @@ -96,9 +99,9 @@ func (r *TestResources) fail(message string) *scapiv1alpha3.TestResult { return r.TestResult } -func (r *TestResources) logWorkloadEvents(namespace string, name string) error { +func (r *TestResources) logWorkloadEvents(name string) error { ctx := context.Background() - deploy, err := r.Client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{}) + deploy, err := r.Client.AppsV1().Deployments(r.Namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { if kerrors.IsNotFound(err) { return nil @@ -113,7 +116,7 @@ func (r *TestResources) logWorkloadEvents(namespace string, name string) error { } r.Log += fmt.Sprintf("deployment %s warning events:\n", deploy.Name) - err = r.logEvents(namespace, scheme.Scheme, deploy) + err = r.logEvents(scheme.Scheme, deploy) if err != nil { return err } @@ -123,7 +126,7 @@ func (r *TestResources) logWorkloadEvents(namespace string, name string) error { if err != nil { return err } - replicaSets, err := r.Client.AppsV1().ReplicaSets(namespace).List(ctx, metav1.ListOptions{ + replicaSets, err := r.Client.AppsV1().ReplicaSets(r.Namespace).List(ctx, metav1.ListOptions{ LabelSelector: selector.String(), }) if err != nil { @@ -136,14 +139,14 @@ func (r *TestResources) logWorkloadEvents(namespace string, name string) error { condition.Reason, condition.Message) } r.Log += fmt.Sprintf("replica set %s warning events:\n", rs.Name) - err = r.logEvents(namespace, scheme.Scheme, &rs) + err = r.logEvents(scheme.Scheme, &rs) if err != nil { return err } } // Look up pods for deployment and log conditions and events - pods, err := r.Client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{ + pods, err := r.Client.CoreV1().Pods(r.Namespace).List(ctx, metav1.ListOptions{ LabelSelector: selector.String(), }) if err != nil { @@ -157,7 +160,7 @@ func (r *TestResources) logWorkloadEvents(namespace string, name string) error { condition.Reason, condition.Message) } r.Log += fmt.Sprintf("pod %s warning events:\n", pod.Name) - err = r.logEvents(namespace, scheme.Scheme, &pod) + err = r.logEvents(scheme.Scheme, &pod) if err != nil { return err } @@ -165,8 +168,8 @@ func (r *TestResources) logWorkloadEvents(namespace string, name string) error { return nil } -func (r *TestResources) logEvents(namespace string, scheme *runtime.Scheme, obj runtime.Object) error { - events, err := r.Client.CoreV1().Events(namespace).Search(scheme, obj) +func (r *TestResources) logEvents(scheme *runtime.Scheme, obj runtime.Object) error { + events, err := r.Client.CoreV1().Events(r.Namespace).Search(scheme, obj) if err != nil { return err } @@ -178,11 +181,11 @@ func (r *TestResources) logEvents(namespace string, scheme *runtime.Scheme, obj return nil } -func (r *TestResources) LogWorkloadEventsOnError(namespace string, name string) { +func (r *TestResources) LogWorkloadEventsOnError() { if len(r.Errors) > 0 { r.Log += "\nWORKLOAD EVENTS:\n" - for _, deployName := range []string{name, name + "-reports"} { - logErr := r.logWorkloadEvents(namespace, deployName) + for _, deployName := range []string{r.Name, r.Name + "-reports"} { + logErr := r.logWorkloadEvents(deployName) if logErr != nil { r.Log += fmt.Sprintf("failed to get workload logs: %s", logErr) } @@ -199,8 +202,10 @@ func newEmptyTestResult(testName string) *scapiv1alpha3.TestResult { } } -func newTestResources(testName string) *TestResources { +func newTestResources(testName string, namespace string) *TestResources { return &TestResources{ + Name: testName, + Namespace: namespace, TestResult: newEmptyTestResult(testName), } } @@ -231,42 +236,82 @@ func (r *TestResources) setupCRTestResources(openShiftCertManager bool) error { return nil } -func newCryostatCR(name string, namespace string, withIngress bool) *operatorv1beta1.Cryostat { - cr := &operatorv1beta1.Cryostat{ +func (r *TestResources) setupTargetNamespace() error { + ctx := context.Background() + + for _, namespaceName := range r.TargetNamespaces { + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespaceName, + }, + } + ns, err := r.Client.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("failed to create namespace %s: %s", namespaceName, err.Error()) + } + r.Log += fmt.Sprintf("created namespace: %s\n", ns.Name) + } + return nil +} + +func (r *TestResources) newCryostatCR() *operatorv1beta2.Cryostat { + cr := &operatorv1beta2.Cryostat{ ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, + Name: r.Name, + Namespace: r.Namespace, }, - Spec: operatorv1beta1.CryostatSpec{ - Minimal: false, + Spec: operatorv1beta2.CryostatSpec{ EnableCertManager: &[]bool{true}[0], }, } + if !r.OpenShift { + configureIngress(cr.Name, &cr.Spec) + } - 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, - }, + return cr +} + +func (r *TestResources) newMultiNamespaceCryostatCR() *operatorv1beta2.Cryostat { + cr := &operatorv1beta2.Cryostat{ + ObjectMeta: metav1.ObjectMeta{ + Name: r.Name, + Namespace: r.Namespace, + }, + Spec: operatorv1beta2.CryostatSpec{ + TargetNamespaces: r.TargetNamespaces, + EnableCertManager: &[]bool{true}[0], + }, + } + if !r.OpenShift { + configureIngress(cr.Name, &cr.Spec) + } + + return cr +} + +func configureIngress(name string, cryostatSpec *operatorv1beta2.CryostatSpec) { + pathType := netv1.PathTypePrefix + cryostatSpec.NetworkOptions = &operatorv1beta2.NetworkConfigurationList{ + CoreConfig: &operatorv1beta2.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 +322,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: &operatorv1beta2.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,12 +353,11 @@ func newCryostatCR(name string, namespace string, withIngress bool) *operatorv1b }, }, }, - } + }, } - return cr } -func (r *TestResources) createAndWaitTillCryostatAvailable(cr *operatorv1beta1.Cryostat) (*operatorv1beta1.Cryostat, error) { +func (r *TestResources) createAndWaitTillCryostatAvailable(cr *operatorv1beta2.Cryostat) (*operatorv1beta2.Cryostat, error) { cr, err := r.Client.OperatorCRDs().Cryostats(cr.Namespace).Create(context.Background(), cr) if err != nil { r.logError(fmt.Sprintf("failed to create Cryostat CR: %s", err.Error())) @@ -323,7 +367,7 @@ func (r *TestResources) createAndWaitTillCryostatAvailable(cr *operatorv1beta1.C // Poll the deployment until it becomes available or we timeout ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - err = r.waitForDeploymentAvailability(ctx, cr.Namespace, cr.Name) + err = r.waitForDeploymentAvailability(ctx, r.Name, r.Namespace) if err != nil { r.logError(fmt.Sprintf("Cryostat main deployment did not become available: %s", err.Error())) return nil, err @@ -334,23 +378,35 @@ func (r *TestResources) createAndWaitTillCryostatAvailable(cr *operatorv1beta1.C if err != nil { return false, fmt.Errorf("failed to get Cryostat CR: %s", err.Error()) } - if len(cr.Status.ApplicationURL) > 0 { - return true, nil + if len(cr.Spec.TargetNamespaces) > 0 { + if len(cr.Status.TargetNamespaces) == 0 { + r.Log += "application's target namespaces are not 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") + } + } } - r.Log += "application URL is not yet available\n" - return false, nil + if len(cr.Status.ApplicationURL) == 0 { + r.Log += "application URL is not yet available\n" + return false, nil + } + 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.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) { + return r.sendHealthRequest(base, func(resp *http.Response, result *scapiv1alpha3.TestResult) (done bool, err error) { health := &HealthResponse{} err = ReadJSON(resp, health) if err != nil { @@ -367,28 +423,28 @@ func (r *TestResources) waitTillCryostatReady(base *url.URL) error { }) } -func (r *TestResources) waitTillReportReady(name string, namespace string, port int32) error { +func (r *TestResources) waitTillReportReady(port int32) error { ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - err := r.waitForDeploymentAvailability(ctx, namespace, name) + err := r.waitForDeploymentAvailability(ctx, r.Name+"-reports", r.Namespace) 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) + reportsUrl := fmt.Sprintf("https://%s.%s.svc.cluster.local:%d", r.Name+"-reports", r.Namespace, port) base, err := url.Parse(reportsUrl) if err != nil { return fmt.Errorf("application URL is invalid: %s", err.Error()) } - return r.sendHealthRequest(base, func(resp *http.Response, r *scapiv1alpha3.TestResult) (done bool, err error) { + return r.sendHealthRequest(base, func(resp *http.Response, result *scapiv1alpha3.TestResult) (done bool, err error) { r.Log += fmt.Sprintf("reports sidecar is ready at %s\n", base.String()) return true, nil }) } -func (r *TestResources) sendHealthRequest(base *url.URL, 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, result *scapiv1alpha3.TestResult) (done bool, err error)) error { client := NewHttpClient() ctx, cancel := context.WithTimeout(context.Background(), testTimeout) @@ -423,7 +479,7 @@ func (r *TestResources) sendHealthRequest(base *url.URL, healthCheck func(resp * return err } -func (r *TestResources) updateAndWaitTillCryostatAvailable(cr *operatorv1beta1.Cryostat) error { +func (r *TestResources) updateAndWaitTillCryostatAvailable(cr *operatorv1beta2.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()) @@ -486,29 +542,32 @@ func (r *TestResources) updateAndWaitTillCryostatAvailable(cr *operatorv1beta1.C return err } -func (r *TestResources) cleanupAndLogs(name string, namespace string) { - r.LogWorkloadEventsOnError(namespace, name) +func (r *TestResources) cleanupAndLogs() { + r.LogWorkloadEventsOnError() - cr := &operatorv1beta1.Cryostat{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - } ctx := context.Background() - err := r.Client.OperatorCRDs().Cryostats(cr.Namespace).Delete(ctx, cr.Name, &metav1.DeleteOptions{}) + err := r.Client.OperatorCRDs().Cryostats(r.Namespace).Delete(ctx, r.Name, &metav1.DeleteOptions{}) if err != nil { if !kerrors.IsNotFound(err) { r.Log += fmt.Sprintf("failed to delete Cryostat: %s\n", err.Error()) } } + for _, namespaceName := range r.TargetNamespaces { + err := r.Client.CoreV1().Namespaces().Delete(ctx, namespaceName, metav1.DeleteOptions{}) + if err != nil { + if !kerrors.IsNotFound(err) { + r.Log += fmt.Sprintf("failed to delete namespace %s: %s", namespaceName, err.Error()) + } + } + } + if r.LogChannel != nil { r.CollectContainersLogsToResult() } } -func (r *TestResources) getCryostatPodNameForCR(cr *operatorv1beta1.Cryostat) (string, error) { +func (r *TestResources) getCryostatPodNameForCR(cr *operatorv1beta2.Cryostat) (string, error) { selector := metav1.LabelSelector{ MatchLabels: map[string]string{ "app": cr.Name, diff --git a/internal/test/scorecard/logger.go b/internal/test/scorecard/logger.go index dd38d79b7..385338055 100644 --- a/internal/test/scorecard/logger.go +++ b/internal/test/scorecard/logger.go @@ -20,7 +20,7 @@ import ( "io" "strings" - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" + operatorv1beta2 "github.com/cryostatio/cryostat-operator/api/v1beta2" v1 "k8s.io/api/core/v1" ) @@ -82,7 +82,7 @@ func (r *TestResources) CollectContainersLogsToResult() { } } -func (r *TestResources) StartLogs(cr *operatorv1beta1.Cryostat) error { +func (r *TestResources) StartLogs(cr *operatorv1beta2.Cryostat) error { podName, err := r.getCryostatPodNameForCR(cr) if err != nil { return fmt.Errorf("failed to get pod name for CR: %s", err.Error()) diff --git a/internal/test/scorecard/tests.go b/internal/test/scorecard/tests.go index 9d3d251a6..518ac08fe 100644 --- a/internal/test/scorecard/tests.go +++ b/internal/test/scorecard/tests.go @@ -20,7 +20,7 @@ import ( "net/url" "time" - operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" + operatorv1beta2 "github.com/cryostatio/cryostat-operator/api/v1beta2" scapiv1alpha3 "github.com/operator-framework/api/pkg/apis/scorecard/v1alpha3" apimanifests "github.com/operator-framework/api/pkg/manifests" corev1 "k8s.io/api/core/v1" @@ -29,66 +29,92 @@ 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" + CryostatRecordingTestName string = "cryostat-recording" + CryostatConfigChangeTestName string = "cryostat-config-change" + CryostatReportTestName string = "cryostat-report" ) // OperatorInstallTest checks that the operator installed correctly func OperatorInstallTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) *scapiv1alpha3.TestResult { - r := newTestResources(OperatorInstallTestName) + r := newTestResources(OperatorInstallTestName, namespace) // Create a new Kubernetes REST client for this test err := r.setupCRTestResources(openShiftCertManager) if err != nil { return r.fail(fmt.Sprintf("failed to set up %s test: %s", OperatorInstallTestName, err.Error())) } - defer r.cleanupAndLogs(OperatorInstallTestName, namespace) + defer r.cleanupAndLogs() // Poll the deployment until it becomes available or we timeout ctx, cancel := context.WithTimeout(context.Background(), testTimeout) defer cancel() - err = r.waitForDeploymentAvailability(ctx, namespace, operatorDeploymentName) + err = r.waitForDeploymentAvailability(ctx, operatorDeploymentName, namespace) if err != nil { return r.fail(fmt.Sprintf("operator deployment did not become available: %s", err.Error())) } - 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) *scapiv1alpha3.TestResult { - r := newTestResources(CryostatCRTestName) + r := newTestResources(CryostatCRTestName, namespace) err := r.setupCRTestResources(openShiftCertManager) if err != nil { return r.fail(fmt.Sprintf("failed to set up %s test: %s", CryostatCRTestName, err.Error())) } - defer r.cleanupAndLogs(CryostatCRTestName, namespace) + defer r.cleanupAndLogs() // Create a default Cryostat CR - _, err = r.createAndWaitTillCryostatAvailable(newCryostatCR(CryostatCRTestName, namespace, !r.OpenShift)) + _, err = r.createAndWaitTillCryostatAvailable(r.newCryostatCR()) if err != nil { return r.fail(fmt.Sprintf("%s test failed: %s", CryostatCRTestName, err.Error())) } return r.TestResult } +// CryostatMultiNamespaceTest checks that the operator installs multi-namespace Cryostat in response to a multi-namespace Cryostat CR +func CryostatMultiNamespaceTest(bundle *apimanifests.Bundle, namespace string, openShiftCertManager bool) *scapiv1alpha3.TestResult { + r := newTestResources(CryostatMultiNamespaceTestName, namespace) + r.TargetNamespaces = []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.cleanupAndLogs() + + err = r.setupTargetNamespace() + if err != nil { + return r.fail(fmt.Sprintf("failed to create target namespaces for %s test: %s", CryostatMultiNamespaceTestName, err.Error())) + } + + // Create a default ClusterCryostat CR + _, err = r.createAndWaitTillCryostatAvailable(r.newMultiNamespaceCryostatCR()) + 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) + r := newTestResources(CryostatConfigChangeTestName, namespace) err := r.setupCRTestResources(openShiftCertManager) if err != nil { return r.fail(fmt.Sprintf("failed to set up %s test: %s", CryostatConfigChangeTestName, err.Error())) } - defer r.cleanupAndLogs(CryostatConfigChangeTestName, namespace) + defer r.cleanupAndLogs() // Create a default Cryostat CR with default empty dir - cr := newCryostatCR(CryostatConfigChangeTestName, namespace, !r.OpenShift) - cr.Spec.StorageOptions = &operatorv1beta1.StorageConfiguration{ - EmptyDir: &operatorv1beta1.EmptyDirConfig{ + cr := r.newCryostatCR() + cr.Spec.StorageOptions = &operatorv1beta2.StorageConfiguration{ + EmptyDir: &operatorv1beta2.EmptyDirConfig{ Enabled: true, }, } @@ -106,8 +132,8 @@ func CryostatConfigChangeTest(bundle *apimanifests.Bundle, namespace string, ope if err != nil { return r.fail(fmt.Sprintf("failed to get Cryostat CR: %s", err.Error())) } - cr.Spec.StorageOptions = &operatorv1beta1.StorageConfiguration{ - PVC: &operatorv1beta1.PersistentVolumeClaimConfig{ + cr.Spec.StorageOptions = &operatorv1beta2.StorageConfiguration{ + PVC: &operatorv1beta2.PersistentVolumeClaimConfig{ Spec: &corev1.PersistentVolumeClaimSpec{ StorageClassName: nil, Resources: corev1.ResourceRequirements{ @@ -141,16 +167,16 @@ 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 { - r := newTestResources(CryostatRecordingTestName) + r := newTestResources(CryostatRecordingTestName, namespace) err := r.setupCRTestResources(openShiftCertManager) if err != nil { return r.fail(fmt.Sprintf("failed to set up %s test: %s", CryostatRecordingTestName, err.Error())) } - defer r.cleanupAndLogs(CryostatRecordingTestName, namespace) + defer r.cleanupAndLogs() // Create a default Cryostat CR - cr, err := r.createAndWaitTillCryostatAvailable(newCryostatCR(CryostatRecordingTestName, namespace, !r.OpenShift)) + cr, err := r.createAndWaitTillCryostatAvailable(r.newCryostatCR()) if err != nil { return r.fail(fmt.Sprintf("failed to determine application URL: %s", err.Error())) } @@ -280,34 +306,35 @@ 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) + r := newTestResources(CryostatReportTestName, namespace) err := r.setupCRTestResources(openShiftCertManager) if err != nil { return r.fail(fmt.Sprintf("failed to set up %s test: %s", CryostatReportTestName, err.Error())) } - defer r.cleanupAndLogs(CryostatReportTestName, namespace) + defer r.cleanupAndLogs() port := int32(10000) - cr := newCryostatCR(CryostatReportTestName, namespace, !r.OpenShift) - cr.Spec.ReportOptions = &operatorv1beta1.ReportConfiguration{ + cr := r.newCryostatCR() + cr.Spec.ReportOptions = &operatorv1beta2.ReportConfiguration{ Replicas: 1, } - cr.Spec.ServiceOptions = &operatorv1beta1.ServiceConfigList{ - ReportsConfig: &operatorv1beta1.ReportsServiceConfig{ + cr.Spec.ServiceOptions = &operatorv1beta2.ServiceConfigList{ + ReportsConfig: &operatorv1beta2.ReportsServiceConfig{ HTTPPort: &port, }, } // Create a default Cryostat CR - cr, err = r.createAndWaitTillCryostatAvailable(cr) + _, err = r.createAndWaitTillCryostatAvailable(cr) if err != nil { return r.fail(fmt.Sprintf("%s test failed: %s", CryostatReportTestName, err.Error())) } // Query health of report sidecar - err = r.waitTillReportReady(cr.Name+"-reports", cr.Namespace, port) + err = r.waitTillReportReady(port) if err != nil { return r.fail(fmt.Sprintf("failed to reach the application: %s", err.Error())) }