From 8d9e15e2459f53d8cb7aa3ba2cd11c8781e4931b Mon Sep 17 00:00:00 2001 From: Bruno Andrade Date: Sun, 8 Feb 2026 02:33:47 -0300 Subject: [PATCH] UPSTREAM: : add ocp-87557 --- .../openshift_payload_olmv1.json | 20 +++ .../pkg/helpers/in_cluster_bundles.go | 6 +- .../tests-extension/test/olmv1-catalog.go | 131 ++++++++++++++++++ 3 files changed, 156 insertions(+), 1 deletion(-) 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 5e6496f89..ffad56dae 100644 --- a/openshift/tests-extension/.openshift-tests-extension/openshift_payload_olmv1.json +++ b/openshift/tests-extension/.openshift-tests-extension/openshift_payload_olmv1.json @@ -1038,6 +1038,26 @@ "lifecycle": "blocking", "environmentSelector": {} }, + { + "name": "[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected][Serial] OLMv1 ClusterExtension behavior after selected catalog removal should keep Installed=True and report Progressing=True/Retrying when the selected catalog is removed", + "labels": {}, + "resources": { + "isolation": {} + }, + "source": "openshift:payload:olmv1", + "lifecycle": "blocking", + "environmentSelector": {} + }, + { + "name": "[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected][Serial] OLMv1 ClusterExtension behavior after selected catalog removal should keep Installed=True when package source is removed by deleting the selected catalog", + "labels": {}, + "resources": { + "isolation": {} + }, + "source": "openshift:payload:olmv1", + "lifecycle": "blocking", + "environmentSelector": {} + }, { "name": "[sig-olmv1][OCPFeatureGate:NewOLM] OLMv1 operator installation should block cluster upgrades if an incompatible operator is installed", "originalName": "[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 operator installation should block cluster upgrades if an incompatible operator is installed", diff --git a/openshift/tests-extension/pkg/helpers/in_cluster_bundles.go b/openshift/tests-extension/pkg/helpers/in_cluster_bundles.go index 74f7664e8..127078c4a 100644 --- a/openshift/tests-extension/pkg/helpers/in_cluster_bundles.go +++ b/openshift/tests-extension/pkg/helpers/in_cluster_bundles.go @@ -19,6 +19,7 @@ import ( imagev1 "github.com/openshift/api/image/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/rand" @@ -170,7 +171,10 @@ func createClusterCatalog(name, namespace string) { Expect(k8sClient.Create(ctx, cc)).To(Succeed(), "failed to create ClusterCatalog") DeferCleanup(func() { By(fmt.Sprintf("deleting ClusterCatalog %q", name)) - Expect(k8sClient.Delete(context.Background(), cc)).To(Succeed()) + err := k8sClient.Delete(context.Background(), cc) + if err != nil && !apierrors.IsNotFound(err) { + Expect(err).NotTo(HaveOccurred()) + } }) waitForClusterCatalogServing(ctx, cc.Name) } diff --git a/openshift/tests-extension/test/olmv1-catalog.go b/openshift/tests-extension/test/olmv1-catalog.go index 5e1bfab1b..cd88ddac8 100644 --- a/openshift/tests-extension/test/olmv1-catalog.go +++ b/openshift/tests-extension/test/olmv1-catalog.go @@ -9,8 +9,10 @@ import ( //nolint:staticcheck // ST1001: dot-imports for readability . "github.com/onsi/gomega" + "github.com/openshift/origin/test/extended/util/image" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -19,6 +21,8 @@ import ( olmv1 "github.com/operator-framework/operator-controller/api/v1" + catalogdata "github.com/openshift/operator-framework-operator-controller/openshift/tests-extension/pkg/bindata/catalog" + operatordata "github.com/openshift/operator-framework-operator-controller/openshift/tests-extension/pkg/bindata/operator" "github.com/openshift/operator-framework-operator-controller/openshift/tests-extension/pkg/env" "github.com/openshift/operator-framework-operator-controller/openshift/tests-extension/pkg/helpers" ) @@ -203,6 +207,133 @@ var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 }) }) +var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected][Serial] OLMv1 ClusterExtension behavior after selected catalog removal", func() { + var ( + k8sClient client.Client + namespace string + catalog string + ceName string + packageRef string + ) + + BeforeEach(func(ctx SpecContext) { + helpers.RequireOLMv1CapabilityOnOpenshift() + helpers.RequireImageRegistry(ctx) + k8sClient = env.Get().K8sClient + + replacements := map[string]string{ + "{{ TEST-BUNDLE }}": "", // auto-filled + "{{ NAMESPACE }}": "", // auto-filled + "{{ TEST-CONTROLLER }}": image.ShellImage(), + } + unique, nsName, ccName, opName := helpers.NewCatalogAndClusterBundles(ctx, replacements, + catalogdata.AssetNames, catalogdata.Asset, + operatordata.AssetNames, operatordata.Asset, + ) + _ = unique + namespace = nsName + catalog = ccName + packageRef = opName + + By("waiting for the catalog to be serving") + Eventually(func(g Gomega) { + cc := &olmv1.ClusterCatalog{} + err := k8sClient.Get(ctx, client.ObjectKey{Name: catalog}, cc) + g.Expect(err).NotTo(HaveOccurred(), "failed to get ClusterCatalog") + + serving := meta.FindStatusCondition(cc.Status.Conditions, "Serving") + g.Expect(serving).NotTo(BeNil(), "expected Serving condition") + g.Expect(serving.Status).To(Equal(metav1.ConditionTrue), "expected Serving=True") + }).WithTimeout(helpers.DefaultTimeout).WithPolling(helpers.DefaultPolling).Should(Succeed()) + + By(fmt.Sprintf("creating ClusterExtension for package %q from catalog %q", packageRef, catalog)) + var cleanupCE func() + ceName, cleanupCE = helpers.CreateClusterExtension(packageRef, "", namespace, "", helpers.WithCatalogNameSelector(catalog)) + DeferCleanup(cleanupCE) + }) + + AfterEach(func(ctx SpecContext) { + if CurrentSpecReport().Failed() { + By("dumping for debugging") + helpers.DescribeAllClusterCatalogs(ctx) + helpers.DescribeAllClusterExtensions(ctx, namespace) + } + }) + + It("should keep Installed=True and report Progressing=True/Retrying when the selected catalog is removed", func(ctx SpecContext) { + By("waiting for the ClusterExtension to install") + helpers.ExpectClusterExtensionToBeInstalled(ctx, ceName) + + By("verifying Installed=True before deleting the catalog") + Eventually(func(g Gomega) { + ce := &olmv1.ClusterExtension{} + err := k8sClient.Get(ctx, client.ObjectKey{Name: ceName}, ce) + g.Expect(err).NotTo(HaveOccurred()) + + installed := meta.FindStatusCondition(ce.Status.Conditions, olmv1.TypeInstalled) + g.Expect(installed).NotTo(BeNil(), "Installed condition not found") + g.Expect(installed.Status).To(Equal(metav1.ConditionTrue), "expected Installed=True before catalog deletion") + }).WithTimeout(helpers.DefaultTimeout).WithPolling(helpers.DefaultPolling).Should(Succeed()) + + By(fmt.Sprintf("deleting ClusterCatalog %q", catalog)) + cc := &olmv1.ClusterCatalog{ObjectMeta: metav1.ObjectMeta{Name: catalog}} + Expect(k8sClient.Delete(ctx, cc)).To(Succeed(), "failed to delete ClusterCatalog") + + By("waiting for the ClusterCatalog to be deleted") + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKey{Name: catalog}, &olmv1.ClusterCatalog{}) + return apierrors.IsNotFound(err) + }).WithTimeout(helpers.DefaultTimeout).WithPolling(helpers.DefaultPolling).Should(BeTrue()) + + By("waiting for ClusterExtension conditions to reflect catalog resolution failure") + Eventually(func(g Gomega) { + ce := &olmv1.ClusterExtension{} + err := k8sClient.Get(ctx, client.ObjectKey{Name: ceName}, ce) + g.Expect(err).NotTo(HaveOccurred()) + + progressing := meta.FindStatusCondition(ce.Status.Conditions, olmv1.TypeProgressing) + g.Expect(progressing).NotTo(BeNil(), "Progressing condition not found") + g.Expect(progressing.Status).To(Equal(metav1.ConditionTrue), "expected Progressing=True after catalog deletion") + g.Expect(progressing.Reason).To(Equal("Retrying"), "expected Progressing reason=Retrying") + + installed := meta.FindStatusCondition(ce.Status.Conditions, olmv1.TypeInstalled) + g.Expect(installed).NotTo(BeNil(), "Installed condition not found") + g.Expect(installed.Status).To(Equal(metav1.ConditionTrue), "expected Installed=True after catalog deletion") + }).WithTimeout(helpers.DefaultTimeout).WithPolling(helpers.DefaultPolling).Should(Succeed()) + }) + + It("should keep Installed=True when package source is removed by deleting the selected catalog", func(ctx SpecContext) { + By("waiting for the ClusterExtension to install") + helpers.ExpectClusterExtensionToBeInstalled(ctx, ceName) + + By("simulating package removal by deleting the selected catalog") + cc := &olmv1.ClusterCatalog{ObjectMeta: metav1.ObjectMeta{Name: catalog}} + Expect(k8sClient.Delete(ctx, cc)).To(Succeed(), "failed to delete ClusterCatalog") + + By("waiting for the ClusterCatalog to be deleted") + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKey{Name: catalog}, &olmv1.ClusterCatalog{}) + return apierrors.IsNotFound(err) + }).WithTimeout(helpers.DefaultTimeout).WithPolling(helpers.DefaultPolling).Should(BeTrue()) + + By("verifying ClusterExtension conditions after package source removal") + Eventually(func(g Gomega) { + ce := &olmv1.ClusterExtension{} + err := k8sClient.Get(ctx, client.ObjectKey{Name: ceName}, ce) + g.Expect(err).NotTo(HaveOccurred()) + + progressing := meta.FindStatusCondition(ce.Status.Conditions, olmv1.TypeProgressing) + g.Expect(progressing).NotTo(BeNil(), "Progressing condition not found") + g.Expect(progressing.Status).To(Equal(metav1.ConditionTrue), "expected Progressing=True after source removal") + g.Expect(progressing.Reason).To(Equal("Retrying"), "expected Progressing reason=Retrying") + + installed := meta.FindStatusCondition(ce.Status.Conditions, olmv1.TypeInstalled) + g.Expect(installed).NotTo(BeNil(), "Installed condition not found") + g.Expect(installed.Status).To(Equal(metav1.ConditionTrue), "expected Installed=True after source removal") + }).WithTimeout(helpers.DefaultTimeout).WithPolling(helpers.DefaultPolling).Should(Succeed()) + }) +}) + func buildCurlJob(prefix, namespace, url string) *batchv1.Job { backoff := int32(1) // This means the k8s garbage collector will automatically delete the job 5 minutes