From a352b4ab2ead33311adbf07bbe01f821022c7fda Mon Sep 17 00:00:00 2001 From: Camila Macedo <7708031+camilamacedo86@users.noreply.github.com> Date: Fri, 8 Aug 2025 23:26:30 +0100 Subject: [PATCH] UPSTREAM: : [OTE] Refac: refac helper and olmv1 test to create namespace instead to use pre-existent --- .../pkg/helpers/cluster_extension.go | 180 ++++++++++++++---- openshift/tests-extension/test/olmv1.go | 53 ++++-- 2 files changed, 179 insertions(+), 54 deletions(-) diff --git a/openshift/tests-extension/pkg/helpers/cluster_extension.go b/openshift/tests-extension/pkg/helpers/cluster_extension.go index 6ad2fcbaf..df4a823ea 100644 --- a/openshift/tests-extension/pkg/helpers/cluster_extension.go +++ b/openshift/tests-extension/pkg/helpers/cluster_extension.go @@ -2,87 +2,195 @@ 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" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "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" + "sigs.k8s.io/controller-runtime/pkg/client" - ocv1 "github.com/operator-framework/operator-controller/api/v1" + olmv1 "github.com/operator-framework/operator-controller/api/v1" "github/operator-framework-operator-controller/openshift/tests-extension/pkg/env" ) -const openshiftOperatorsNs = "openshift-operators" - // CreateClusterExtension creates a ServiceAccount, ClusterRoleBinding, and ClusterExtension using typed APIs. // It returns the unique suffix and a cleanup function. -func CreateClusterExtension(packageName, version string) (string, func()) { +func CreateClusterExtension(packageName, version, namespace string) (string, func()) { ctx := context.TODO() k8sClient := env.Get().K8sClient - unique := rand.String(8) + unique := rand.String(4) saName := "install-test-sa-" + unique crbName := "install-test-crb-" + unique ceName := "install-test-ce-" + unique // 1. Create ServiceAccount - sa := &corev1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: saName, - Namespace: openshiftOperatorsNs, - }, - } - Expect(k8sClient.Create(ctx, sa)).To(Succeed(), "failed to create ServiceAccount") + sa := NewServiceAccount(saName, namespace) + Expect(k8sClient.Create(ctx, sa)).To(Succeed(), + "failed to create ServiceAccount") + By("ensuring ServiceAccount is available before proceeding") + ExpectServiceAccountExists(ctx, saName, namespace) // 2. Create ClusterRoleBinding - crb := &rbacv1.ClusterRoleBinding{ + crb := NewClusterRoleBinding(crbName, "cluster-admin", saName, namespace) + Expect(k8sClient.Create(ctx, crb)).To(Succeed(), "failed to create ClusterRoleBinding") + By("ensuring ClusterRoleBinding is available before proceeding") + ExpectClusterRoleBindingExists(ctx, crbName) + + // 3. Create ClusterExtension + ce := NewClusterExtensionObject(packageName, version, ceName, saName, namespace) + Expect(k8sClient.Create(ctx, ce)).To(Succeed(), "failed to create ClusterExtension") + + // Cleanup closure + return ceName, func() { + _ = k8sClient.Delete(ctx, ce) + _ = k8sClient.Delete(ctx, crb) + _ = k8sClient.Delete(ctx, sa) + } +} + +// NewServiceAccount creates a new ServiceAccount. +func NewServiceAccount(name, namespace string) *corev1.ServiceAccount { + return &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Name: crbName, + Name: name, + Namespace: namespace, }, + } +} + +// NewClusterRoleBinding creates a new ClusterRoleBinding object that binds a ClusterRole to a ServiceAccount. +func NewClusterRoleBinding(name, roleName, saName, namespace string) *rbacv1.ClusterRoleBinding { + return &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{Name: name}, RoleRef: rbacv1.RoleRef{ APIGroup: "rbac.authorization.k8s.io", Kind: "ClusterRole", - Name: "cluster-admin", + Name: roleName, }, Subjects: []rbacv1.Subject{{ Kind: "ServiceAccount", Name: saName, - Namespace: openshiftOperatorsNs, + Namespace: namespace, }}, } - Expect(k8sClient.Create(ctx, crb)).To(Succeed(), "failed to create ClusterRoleBinding") +} - // 3. Create ClusterExtension - ce := &ocv1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{ - Name: ceName, - }, - Spec: ocv1.ClusterExtensionSpec{ - Namespace: openshiftOperatorsNs, - ServiceAccount: ocv1.ServiceAccountReference{ +// NewClusterExtensionObject creates a new ClusterExtension object with the specified package, version, name, and ServiceAccount. +func NewClusterExtensionObject(pkg, version, ceName, saName, namespace string) *olmv1.ClusterExtension { + return &olmv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{Name: ceName}, + Spec: olmv1.ClusterExtensionSpec{ + Namespace: namespace, + ServiceAccount: olmv1.ServiceAccountReference{ Name: saName, }, - Source: ocv1.SourceConfig{ - SourceType: ocv1.SourceTypeCatalog, - Catalog: &ocv1.CatalogFilter{ - PackageName: packageName, + Source: olmv1.SourceConfig{ + SourceType: olmv1.SourceTypeCatalog, + Catalog: &olmv1.CatalogFilter{ + PackageName: pkg, Version: version, Selector: &metav1.LabelSelector{}, - UpgradeConstraintPolicy: ocv1.UpgradeConstraintPolicyCatalogProvided, + UpgradeConstraintPolicy: olmv1.UpgradeConstraintPolicyCatalogProvided, }, }, }, } - Expect(k8sClient.Create(ctx, ce)).To(Succeed(), "failed to create ClusterExtension") +} - // Cleanup closure - return ceName, func() { - _ = k8sClient.Delete(ctx, ce) - _ = k8sClient.Delete(ctx, crb) - _ = k8sClient.Delete(ctx, sa) +// ExpectClusterExtensionToBeInstalled checks that the ClusterExtension has both Progressing=True and Installed=True. +func ExpectClusterExtensionToBeInstalled(ctx context.Context, name string) { + k8sClient := env.Get().K8sClient + Eventually(func(g Gomega) { + var ext olmv1.ClusterExtension + err := k8sClient.Get(ctx, client.ObjectKey{Name: name}, &ext) + g.Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("failed to get ClusterExtension %q", name)) + + conditions := ext.Status.Conditions + g.Expect(conditions).NotTo(BeEmpty(), fmt.Sprintf("ClusterExtension %q has empty status.conditions", name)) + + progressing := meta.FindStatusCondition(conditions, string(olmv1.TypeProgressing)) + g.Expect(progressing).ToNot(BeNil(), "Progressing condition not found") + g.Expect(progressing.Status).To(Equal(metav1.ConditionTrue), "Progressing should be True") + + installed := meta.FindStatusCondition(conditions, string(olmv1.TypeInstalled)) + g.Expect(installed).ToNot(BeNil(), "Installed condition not found") + g.Expect(installed.Status).To(Equal(metav1.ConditionTrue), "Installed should be True") + }).WithTimeout(5 * time.Minute).WithPolling(1 * time.Second).Should(Succeed()) +} + +// EnsureCleanupClusterExtension attempts to delete any ClusterExtension and a specified CRD +// that might be left over from previous test runs. This helps prevent conflicts in serial tests. +func EnsureCleanupClusterExtension(ctx context.Context, packageName, crdName string) { + k8sClient := env.Get().K8sClient + + // 1. Clean up any ClusterExtensions related to this test/package + ceList := &olmv1.ClusterExtensionList{} + // List all ClusterExtensions, then filter in code by packageName + if err := k8sClient.List(ctx, ceList); err == nil { + for _, ce := range ceList.Items { + if ce.Spec.Source.Catalog.PackageName == packageName { + By(fmt.Sprintf("deleting ClusterExtension %s (package: %s)", ce.Name, packageName)) + propagationPolicy := metav1.DeletePropagationForeground + deleteOpts := &client.DeleteOptions{PropagationPolicy: &propagationPolicy} + if err := k8sClient.Delete(ctx, &ce, deleteOpts); err != nil && !errors.IsNotFound(err) { + fmt.Fprintf(GinkgoWriter, "Warning: Failed to delete remaning ClusterExtension %s: %v\n", ce.Name, err) + } + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKey{Name: ce.Name}, &olmv1.ClusterExtension{}) + return errors.IsNotFound(err) + }).WithTimeout(1*time.Minute).WithPolling(2*time.Second).Should(BeTrue(), "Cleanup ClusterExtension %s failed to delete", ce.Name) + } + } + } else if !errors.IsNotFound(err) { + fmt.Fprintf(GinkgoWriter, "Warning: Failed to list ClusterExtensions during cleanup: %v\n", err) + } + + // 2. Clean up specific operator-created CRD if it exists + if crdName != "" { + crd := &apiextensionsv1.CustomResourceDefinition{} + if err := k8sClient.Get(ctx, client.ObjectKey{Name: crdName}, crd); err == nil { + By(fmt.Sprintf("deleting CRD %s", crdName)) + if err := k8sClient.Delete(ctx, crd); err != nil && !errors.IsNotFound(err) { + fmt.Fprintf(GinkgoWriter, "Warning: Failed to delete lingering CRD %s: %v\n", crdName, err) + } + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKey{Name: crdName}, &apiextensionsv1.CustomResourceDefinition{}) + return errors.IsNotFound(err) + }).WithTimeout(1*time.Minute).WithPolling(2*time.Second).Should(BeTrue(), "Lingering CRD %s failed to delete", crdName) + } else if !errors.IsNotFound(err) { + fmt.Fprintf(GinkgoWriter, "Warning: Failed to get CRD %s during cleanup: %v\n", crdName, err) + } } } + +// ExpectServiceAccountExists waits for a ServiceAccount to be available and visible to the client. +func ExpectServiceAccountExists(ctx context.Context, name, namespace string) { + k8sClient := env.Get().K8sClient + sa := &corev1.ServiceAccount{} + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, client.ObjectKey{Name: name, Namespace: namespace}, sa) + g.Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("failed to get ServiceAccount %q/%q: %v", namespace, name, err)) + }).WithTimeout(10*time.Second).WithPolling(1*time.Second).Should(Succeed(), "ServiceAccount %q/%q did not become visible within timeout", namespace, name) +} + +// ExpectClusterRoleBindingExists waits for a ClusterRoleBinding to be available and visible to the client. +func ExpectClusterRoleBindingExists(ctx context.Context, name string) { + k8sClient := env.Get().K8sClient + crb := &rbacv1.ClusterRoleBinding{} + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, client.ObjectKey{Name: name}, crb) + g.Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("failed to get ClusterRoleBinding %q: %v", name, err)) + }).WithTimeout(10*time.Second).WithPolling(1*time.Second).Should(Succeed(), "ClusterRoleBinding %q did not become visible within timeout", name) +} diff --git a/openshift/tests-extension/test/olmv1.go b/openshift/tests-extension/test/olmv1.go index 09da4972d..df7138be2 100644 --- a/openshift/tests-extension/test/olmv1.go +++ b/openshift/tests-extension/test/olmv1.go @@ -11,9 +11,11 @@ import ( . "github.com/onsi/gomega" configv1 "github.com/openshift/api/config/v1" + corev1 "k8s.io/api/core/v1" apiextclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" "sigs.k8s.io/controller-runtime/pkg/client" olmv1 "github.com/operator-framework/operator-controller/api/v1" @@ -71,37 +73,49 @@ var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLM] OLMv1 CRDs", func() { }) var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 operator installation", func() { + var ( + namespace string + k8sClient client.Client + ) BeforeEach(func() { helpers.RequireOLMv1CapabilityOnOpenshift() + k8sClient = env.Get().K8sClient + namespace = "install-test-ns-" + rand.String(4) + + By(fmt.Sprintf("creating namespace %s", namespace)) + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + } + Expect(k8sClient.Create(context.Background(), ns)).To(Succeed(), "failed to create test namespace") + DeferCleanup(func() { + _ = k8sClient.Delete(context.Background(), ns) + }) }) + It("should install a cluster extension", func(ctx SpecContext) { if !env.Get().IsOpenShift { Skip("Requires OCP Catalogs: not OpenShift") } + + By("ensuring no ClusterExtension and CRD for quay-operator") + helpers.EnsureCleanupClusterExtension(context.Background(), "quay-operator", "quayregistries.quay.redhat.com") + By("applying the ClusterExtension resource") - name, cleanup := helpers.CreateClusterExtension("quay-operator", "3.13.0") + name, cleanup := helpers.CreateClusterExtension("quay-operator", "3.13.0", namespace) DeferCleanup(cleanup) By("waiting for the quay-operator ClusterExtension to be installed") - Eventually(func(g Gomega) { - k8sClient := env.Get().K8sClient - ce := &olmv1.ClusterExtension{} - err := k8sClient.Get(ctx, client.ObjectKey{Name: name}, ce) - g.Expect(err).ToNot(HaveOccurred()) - - progressing := meta.FindStatusCondition(ce.Status.Conditions, olmv1.TypeProgressing) - g.Expect(progressing).ToNot(BeNil()) - g.Expect(progressing.Status).To(Equal(metav1.ConditionTrue)) - - installed := meta.FindStatusCondition(ce.Status.Conditions, olmv1.TypeInstalled) - g.Expect(installed).ToNot(BeNil()) - g.Expect(installed.Status).To(Equal(metav1.ConditionTrue)) - }).WithTimeout(5 * time.Minute).WithPolling(1 * time.Second).Should(Succeed()) + helpers.ExpectClusterExtensionToBeInstalled(ctx, name) }) It("should fail to install a non-existing cluster extension", func(ctx SpecContext) { + By("ensuring no ClusterExtension and CRD for non-existing operator") + helpers.EnsureCleanupClusterExtension(context.Background(), "does-not-exist", "") // No CRD expected for non-existing operator + By("applying the ClusterExtension resource") - name, cleanup := helpers.CreateClusterExtension("does-not-exist", "99.99.99") + name, cleanup := helpers.CreateClusterExtension("does-not-exist", "99.99.99", namespace) DeferCleanup(cleanup) By("waiting for the ClusterExtension to exist") @@ -135,11 +149,14 @@ var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLM][Skipped:Disconnected] OLMv1 Skip("Requires OCP Catalogs: not OpenShift") } + By("ensuring no ClusterExtension no ClusterExtension and CRD for cluster-logging") + helpers.EnsureCleanupClusterExtension(context.Background(), "cluster-logging", "clusterloggings.logging.openshift.io") + By("applying the ClusterExtension resource") - name, cleanup := helpers.CreateClusterExtension("cluster-logging", "6.2.2") + name, cleanup := helpers.CreateClusterExtension("cluster-logging", "6.2.2", namespace) DeferCleanup(cleanup) - By("waiting for the function-mesh ClusterExtension to be installed") + By("waiting for the cluster-logging ClusterExtension to be installed") Eventually(func(g Gomega) { k8sClient := env.Get().K8sClient ce := &olmv1.ClusterExtension{}