From 08c16327b050b4e766598e661b758e4f9aaedaf9 Mon Sep 17 00:00:00 2001 From: Camila Macedo <7708031+camilamacedo86@users.noreply.github.com> Date: Thu, 31 Jul 2025 19:33:54 +0100 Subject: [PATCH] UPSTREAM: : [OTE] add catalog tests from openshift/origin This commit migrates the olmv1_catalog set of tests from openshift/origin to OTE as part the broad effort to migrate all tests. Assisted-by: Gemini --- .../openshift_payload_olmv1.json | 100 +++++++ .../pkg/helpers/cluster_catalog.go | 77 ++++++ .../tests-extension/test/olmv1-catalog.go | 260 ++++++++++++++++++ 3 files changed, 437 insertions(+) create mode 100644 openshift/tests-extension/pkg/helpers/cluster_catalog.go create mode 100644 openshift/tests-extension/test/olmv1-catalog.go diff --git a/openshift/tests-extension/.openshift-tests-extension/openshift_payload_olmv1.json b/openshift/tests-extension/.openshift-tests-extension/openshift_payload_olmv1.json index d838eda39..6c321255f 100644 --- a/openshift/tests-extension/.openshift-tests-extension/openshift_payload_olmv1.json +++ b/openshift/tests-extension/.openshift-tests-extension/openshift_payload_olmv1.json @@ -1,4 +1,104 @@ [ + { + "name": "[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 Catalogs should be installed", + "labels": {}, + "resources": { + "isolation": {} + }, + "source": "openshift:payload:olmv1", + "lifecycle": "blocking", + "environmentSelector": {} + }, + { + "name": "[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 openshift-community-operators Catalog should serve FBC via the /v1/api/all endpoint", + "labels": {}, + "resources": { + "isolation": {} + }, + "source": "openshift:payload:olmv1", + "lifecycle": "blocking", + "environmentSelector": {} + }, + { + "name": "[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 openshift-certified-operators Catalog should serve FBC via the /v1/api/all endpoint", + "labels": {}, + "resources": { + "isolation": {} + }, + "source": "openshift:payload:olmv1", + "lifecycle": "blocking", + "environmentSelector": {} + }, + { + "name": "[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 openshift-redhat-marketplace Catalog should serve FBC via the /v1/api/all endpoint", + "labels": {}, + "resources": { + "isolation": {} + }, + "source": "openshift:payload:olmv1", + "lifecycle": "blocking", + "environmentSelector": {} + }, + { + "name": "[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 openshift-redhat-operators Catalog should serve FBC via the /v1/api/all endpoint", + "labels": {}, + "resources": { + "isolation": {} + }, + "source": "openshift:payload:olmv1", + "lifecycle": "blocking", + "environmentSelector": {} + }, + { + "name": "[sig-olmv1][OCPFeatureGate:NewOLMCatalogdAPIV1Metas][Skipped:Disconnected] OLMv1 openshift-community-operators Catalog should serve FBC via the /v1/api/metas endpoint", + "labels": {}, + "resources": { + "isolation": {} + }, + "source": "openshift:payload:olmv1", + "lifecycle": "blocking", + "environmentSelector": {} + }, + { + "name": "[sig-olmv1][OCPFeatureGate:NewOLMCatalogdAPIV1Metas][Skipped:Disconnected] OLMv1 openshift-certified-operators Catalog should serve FBC via the /v1/api/metas endpoint", + "labels": {}, + "resources": { + "isolation": {} + }, + "source": "openshift:payload:olmv1", + "lifecycle": "blocking", + "environmentSelector": {} + }, + { + "name": "[sig-olmv1][OCPFeatureGate:NewOLMCatalogdAPIV1Metas][Skipped:Disconnected] OLMv1 openshift-redhat-marketplace Catalog should serve FBC via the /v1/api/metas endpoint", + "labels": {}, + "resources": { + "isolation": {} + }, + "source": "openshift:payload:olmv1", + "lifecycle": "blocking", + "environmentSelector": {} + }, + { + "name": "[sig-olmv1][OCPFeatureGate:NewOLMCatalogdAPIV1Metas][Skipped:Disconnected] OLMv1 openshift-redhat-operators Catalog should serve FBC via the /v1/api/metas endpoint", + "labels": {}, + "resources": { + "isolation": {} + }, + "source": "openshift:payload:olmv1", + "lifecycle": "blocking", + "environmentSelector": {} + }, + { + "name": "[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 New Catalog Install should fail to install if it has an invalid reference", + "labels": {}, + "resources": { + "isolation": {} + }, + "source": "openshift:payload:olmv1", + "lifecycle": "blocking", + "environmentSelector": {} + }, { "name": "[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 operator installation should block cluster upgrades if an incompatible operator is installed", "labels": {}, diff --git a/openshift/tests-extension/pkg/helpers/cluster_catalog.go b/openshift/tests-extension/pkg/helpers/cluster_catalog.go new file mode 100644 index 000000000..a5432a1c0 --- /dev/null +++ b/openshift/tests-extension/pkg/helpers/cluster_catalog.go @@ -0,0 +1,77 @@ +package helpers + +import ( + "context" + "fmt" + "time" + + //nolint:staticcheck // ST1001: dot-imports for readability + . "github.com/onsi/ginkgo/v2" + //nolint:staticcheck // ST1001: dot-imports for readability + . "github.com/onsi/gomega" + + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + olmv1 "github.com/operator-framework/operator-controller/api/v1" + + "github/operator-framework-operator-controller/openshift/tests-extension/pkg/env" +) + +// CreateClusterCatalog creates a ClusterCatalog with the specified name and image reference +// Returns a cleanup function to delete the catalog after use. +func CreateClusterCatalog(ctx context.Context, name, imageRef string) (func(), error) { + k8sClient := env.Get().K8sClient + + catalog := &olmv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: olmv1.ClusterCatalogSpec{ + Source: olmv1.CatalogSource{ + Type: olmv1.SourceTypeImage, + Image: &olmv1.ImageSource{ + Ref: imageRef, + }, + }, + }, + } + + if err := k8sClient.Create(ctx, catalog); err != nil { + return nil, fmt.Errorf("failed to create ClusterCatalog: %w", err) + } + + return func() { + ctx := context.TODO() + k := env.Get().K8sClient + obj := &olmv1.ClusterCatalog{ObjectMeta: metav1.ObjectMeta{Name: name}} + + _ = k.Delete(ctx, obj) + EnsureCleanupClusterCatalog(ctx, name) + }, nil +} + +func EnsureCleanupClusterCatalog(ctx context.Context, name string) { + k8s := env.Get().K8sClient + cc := &olmv1.ClusterCatalog{} + key := client.ObjectKey{Name: name} + + if err := k8s.Get(ctx, key, cc); err != nil { + if !errors.IsNotFound(err) { + fmt.Fprintf(GinkgoWriter, "Warning: failed to get ClusterCatalog %q during cleanup: %v\n", name, err) + } + return + } + + By(fmt.Sprintf("deleting lingering ClusterCatalog %q", name)) + if err := k8s.Delete(ctx, cc); err != nil && !errors.IsNotFound(err) { + fmt.Fprintf(GinkgoWriter, "Warning: failed to delete ClusterCatalog %q: %v\n", name, err) + } + + Eventually(func() bool { + err := k8s.Get(ctx, key, &olmv1.ClusterCatalog{}) + return errors.IsNotFound(err) + }).WithTimeout(3*time.Minute).WithPolling(2*time.Second). + Should(BeTrue(), "ClusterCatalog %q failed to delete", name) +} diff --git a/openshift/tests-extension/test/olmv1-catalog.go b/openshift/tests-extension/test/olmv1-catalog.go new file mode 100644 index 000000000..3e19fe834 --- /dev/null +++ b/openshift/tests-extension/test/olmv1-catalog.go @@ -0,0 +1,260 @@ +package test + +import ( + "fmt" + "strings" + "time" + + //nolint:staticcheck // ST1001: dot-imports for readability + . "github.com/onsi/ginkgo/v2" + //nolint:staticcheck // ST1001: dot-imports for readability + . "github.com/onsi/gomega" + + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + olmv1 "github.com/operator-framework/operator-controller/api/v1" + + "github/operator-framework-operator-controller/openshift/tests-extension/pkg/env" + "github/operator-framework-operator-controller/openshift/tests-extension/pkg/helpers" +) + +var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 Catalogs", func() { + BeforeEach(func() { + helpers.RequireOLMv1CapabilityOnOpenshift() + }) + + It("should be installed", func(ctx SpecContext) { + if !env.Get().IsOpenShift { + Skip("Skipping test because it requires OpenShift Catalogs") + } + + k8sClient := env.Get().K8sClient + + catalogs := []string{ + "openshift-certified-operators", + "openshift-community-operators", + "openshift-redhat-marketplace", + "openshift-redhat-operators", + } + + for _, name := range catalogs { + By(fmt.Sprintf("checking that %q exists", name)) + catalog := &olmv1.ClusterCatalog{} + err := k8sClient.Get(ctx, client.ObjectKey{Name: name}, catalog) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("failed to get catalog %q", name)) + + conditions := catalog.Status.Conditions + Expect(conditions).NotTo(BeEmpty(), fmt.Sprintf("catalog %q has empty status.conditions", name)) + + By(fmt.Sprintf("checking that %q is serving", name)) + + Expect(meta.IsStatusConditionPresentAndEqual(conditions, "Serving", metav1.ConditionTrue)). + To(BeTrue(), fmt.Sprintf("expected catalog %q to have condition {type: Serving, status: True},"+ + " but it did not", name)) + } + }) +}) + +func verifyCatalogEndpoint(ctx SpecContext, catalog, endpoint, query string) { + k8sClient := env.Get().K8sClient + + By(fmt.Sprintf("Retrieving base URL from ClusterCatalog %q", catalog)) + cc := &olmv1.ClusterCatalog{} + err := k8sClient.Get(ctx, client.ObjectKey{Name: catalog}, cc) + Expect(err).NotTo(HaveOccurred(), "failed to get ClusterCatalog") + + Expect(cc.Status.URLs.Base).NotTo(BeEmpty(), fmt.Sprintf("catalog %q has empty base URL", catalog)) + serviceURL := fmt.Sprintf("%s/api/v1/%s%s", cc.Status.URLs.Base, endpoint, query) + + By(fmt.Sprintf("Creating curl Job to hit: %s", serviceURL)) + + jobNamePrefix := fmt.Sprintf("verify-%s-%s", + strings.ReplaceAll(endpoint, "?", ""), + strings.ReplaceAll(catalog, "-", "")) + + job := buildCurlJob(jobNamePrefix, "default", serviceURL) + err = k8sClient.Create(ctx, job) + Expect(err).NotTo(HaveOccurred(), "failed to create Job") + + DeferCleanup(func(ctx SpecContext) { + _ = k8sClient.Delete(ctx, job) + }) + + By("Waiting for Job to succeed") + Eventually(func(g Gomega) { + recheck := &batchv1.Job{} + g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(job), recheck)).NotTo(HaveOccurred()) + + for _, c := range recheck.Status.Conditions { + if c.Type == batchv1.JobComplete && c.Status == corev1.ConditionTrue { + return + } + if c.Type == batchv1.JobFailed && c.Status == corev1.ConditionTrue { + Fail(fmt.Sprintf("Job failed: %s", c.Message)) + } + } + }).WithTimeout(2 * time.Minute).WithPolling(5 * time.Second).Should(Succeed()) +} + +var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 openshift-community-operators Catalog", func() { + BeforeEach(func() { + helpers.RequireOLMv1CapabilityOnOpenshift() + if !env.Get().IsOpenShift { + Skip("This test requires OpenShift Catalogs") + } + }) + It("should serve FBC via the /v1/api/all endpoint", func(ctx SpecContext) { + verifyCatalogEndpoint(ctx, "openshift-community-operators", "all", "") + }) +}) + +var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 openshift-certified-operators Catalog", func() { + BeforeEach(func() { + helpers.RequireOLMv1CapabilityOnOpenshift() + if !env.Get().IsOpenShift { + Skip("This test requires OpenShift Catalogs") + } + }) + It("should serve FBC via the /v1/api/all endpoint", func(ctx SpecContext) { + verifyCatalogEndpoint(ctx, "openshift-certified-operators", "all", "") + }) +}) + +var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 openshift-redhat-marketplace Catalog", func() { + BeforeEach(func() { + helpers.RequireOLMv1CapabilityOnOpenshift() + if !env.Get().IsOpenShift { + Skip("This test requires OpenShift Catalogs") + } + }) + It("should serve FBC via the /v1/api/all endpoint", func(ctx SpecContext) { + verifyCatalogEndpoint(ctx, "openshift-redhat-marketplace", "all", "") + }) +}) + +var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 openshift-redhat-operators Catalog", func() { + BeforeEach(func() { + helpers.RequireOLMv1CapabilityOnOpenshift() + if !env.Get().IsOpenShift { + Skip("This test requires OpenShift Catalogs") + } + }) + It("should serve FBC via the /v1/api/all endpoint", func(ctx SpecContext) { + verifyCatalogEndpoint(ctx, "openshift-redhat-operators", "all", "") + }) +}) + +var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLMCatalogdAPIV1Metas][Skipped:Disconnected] OLMv1 openshift-community-operators Catalog", func() { + BeforeEach(func() { + helpers.RequireOLMv1CapabilityOnOpenshift() + if !env.Get().IsOpenShift { + Skip("This test requires OpenShift Catalogs") + } + }) + It("should serve FBC via the /v1/api/metas endpoint", func(ctx SpecContext) { + verifyCatalogEndpoint(ctx, "openshift-community-operators", "metas", "?schema=olm.package") + }) +}) + +var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLMCatalogdAPIV1Metas][Skipped:Disconnected] OLMv1 openshift-certified-operators Catalog", func() { + BeforeEach(func() { + helpers.RequireOLMv1CapabilityOnOpenshift() + if !env.Get().IsOpenShift { + Skip("This test requires OpenShift Catalogs") + } + }) + It("should serve FBC via the /v1/api/metas endpoint", func(ctx SpecContext) { + verifyCatalogEndpoint(ctx, "openshift-certified-operators", "metas", "?schema=olm.package") + }) +}) + +var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLMCatalogdAPIV1Metas][Skipped:Disconnected] OLMv1 openshift-redhat-marketplace Catalog", func() { + BeforeEach(func() { + helpers.RequireOLMv1CapabilityOnOpenshift() + if !env.Get().IsOpenShift { + Skip("This test requires OpenShift Catalogs") + } + }) + It("should serve FBC via the /v1/api/metas endpoint", func(ctx SpecContext) { + verifyCatalogEndpoint(ctx, "openshift-redhat-marketplace", "metas", "?schema=olm.package") + }) +}) + +var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLMCatalogdAPIV1Metas][Skipped:Disconnected] OLMv1 openshift-redhat-operators Catalog", func() { + BeforeEach(func() { + helpers.RequireOLMv1CapabilityOnOpenshift() + if !env.Get().IsOpenShift { + Skip("This test requires OpenShift Catalogs") + } + }) + It("should serve FBC via the /v1/api/metas endpoint", func(ctx SpecContext) { + verifyCatalogEndpoint(ctx, "openshift-redhat-operators", "metas", "?schema=olm.package") + }) +}) + +var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 New Catalog Install", func() { + BeforeEach(func() { + helpers.RequireOLMv1CapabilityOnOpenshift() + }) + It("should fail to install if it has an invalid reference", func(ctx SpecContext) { + catName := "bad-catalog" + imageRef := "example.com/does-not-exist:latest" + + By("creating the malformed catalog with an invalid image ref") + cleanup, err := helpers.CreateClusterCatalog(ctx, catName, imageRef) + Expect(err).NotTo(HaveOccurred(), "failed to create ClusterCatalog") + DeferCleanup(cleanup) + + k8sClient := env.Get().K8sClient + + By("waiting for the catalog to report failure via Progressing=True and reason=Retrying") + Eventually(func(g Gomega) { + catalog := &olmv1.ClusterCatalog{} + err := k8sClient.Get(ctx, client.ObjectKey{Name: catName}, catalog) + g.Expect(err).NotTo(HaveOccurred(), "failed to get catalog") + conditions := catalog.Status.Conditions + c := meta.FindStatusCondition(conditions, "Progressing") + g.Expect(c).NotTo(BeNil(), "expected 'Progressing' condition to be present") + g.Expect(c.Status).To(Equal(metav1.ConditionTrue), "expected Progressing=True") + g.Expect(c.Reason).To(Equal("Retrying"), "expected reason to be 'Retrying'") + g.Expect(c.Message).To(ContainSubstring("error creating image source"), "expected image source error") + }).WithTimeout(5 * time.Minute).WithPolling(1 * time.Second).Should(Succeed()) + }) +}) + +func buildCurlJob(prefix, namespace, url string) *batchv1.Job { + backoff := int32(1) + ttl := int32(60) // optional: auto-GC finished Jobs if TTL controller is enabled + + return &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: prefix + "-", + Namespace: namespace, + }, + Spec: batchv1.JobSpec{ + TTLSecondsAfterFinished: &ttl, + BackoffLimit: &backoff, + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyNever, + Containers: []corev1.Container{{ + Name: "curl", + Image: "curlimages/curl:latest", + Command: []string{"/bin/bash", "-c", fmt.Sprintf("curl -vk %q", url)}, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("10m"), + corev1.ResourceMemory: resource.MustParse("50Mi"), + }, + }, + }}, + }, + }, + }, + } +}