From 80ddfb60479442cf6815ed8e0c7a8f0bb8c33263 Mon Sep 17 00:00:00 2001 From: Camila Macedo <7708031+camilamacedo86@users.noreply.github.com> Date: Mon, 6 Oct 2025 11:57:08 -0300 Subject: [PATCH] UPSTREAM: : Add new tests for single/own namespaces install modes --- .../openshift_payload_olmv1.json | 20 ++ openshift/tests-extension/pkg/env/cluster.go | 2 + .../test/olmv1-singleownnamespace.go | 275 ++++++++++++++++++ 3 files changed, 297 insertions(+) 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 5599657b0..0bfd3ffe2 100644 --- a/openshift/tests-extension/.openshift-tests-extension/openshift_payload_olmv1.json +++ b/openshift/tests-extension/.openshift-tests-extension/openshift_payload_olmv1.json @@ -441,6 +441,16 @@ "lifecycle": "blocking", "environmentSelector": {} }, + { + "name": "[sig-olmv1][OCPFeatureGate:NewOLMOwnSingleNamespace][Skipped:Disconnected][Serial] OLMv1 operator installation support for ownNamespace and single namespace watch mode with quay-operator should install cluster extensions successfully in both watch modes", + "labels": {}, + "resources": { + "isolation": {} + }, + "source": "openshift:payload:olmv1", + "lifecycle": "blocking", + "environmentSelector": {} + }, { "name": "[sig-olmv1][OCPFeatureGate:NewOLMOwnSingleNamespace][Skipped:Disconnected][Serial] OLMv1 operator installation support for ownNamespace watch mode with an operator that does not support ownNamespace installation mode should fail to install a cluster extension successfully", "originalName": "[sig-olmv1][OCPFeatureGate:NewOLMOwnSingleNamespace][Skipped:Disconnected] OLMv1 operator installation support for ownNamespace watch mode with an operator that does not support ownNamespace installation mode should fail to install a cluster extension successfully", @@ -454,6 +464,16 @@ "lifecycle": "blocking", "environmentSelector": {} }, + { + "name": "[sig-olmv1][OCPFeatureGate:NewOLMOwnSingleNamespace][Skipped:Disconnected][Serial] OLMv1 operator installation should reject invalid watch namespace configuration and update the status conditions accordingly should fail to install the ClusterExtension when watch namespace is invalid", + "labels": {}, + "resources": { + "isolation": {} + }, + "source": "openshift:payload:olmv1", + "lifecycle": "blocking", + "environmentSelector": {} + }, { "name": "[sig-olmv1] OLMv1 should pass a trivial sanity check", "labels": {}, diff --git a/openshift/tests-extension/pkg/env/cluster.go b/openshift/tests-extension/pkg/env/cluster.go index ece834f4d..8a89508dd 100644 --- a/openshift/tests-extension/pkg/env/cluster.go +++ b/openshift/tests-extension/pkg/env/cluster.go @@ -11,6 +11,7 @@ import ( configv1 "github.com/openshift/api/config/v1" imagev1 "github.com/openshift/api/image/v1" operatorv1 "github.com/openshift/api/operator/v1" + appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -83,6 +84,7 @@ func initTestEnv() *TestEnv { // Create the runtime scheme and register all necessary types scheme := runtime.NewScheme() utilruntime.Must(corev1.AddToScheme(scheme)) + utilruntime.Must(appsv1.AddToScheme(scheme)) utilruntime.Must(rbacv1.AddToScheme(scheme)) utilruntime.Must(batchv1.AddToScheme(scheme)) utilruntime.Must(apiextensionsv1.AddToScheme(scheme)) diff --git a/openshift/tests-extension/test/olmv1-singleownnamespace.go b/openshift/tests-extension/test/olmv1-singleownnamespace.go index 1fb290959..fc877d6ee 100644 --- a/openshift/tests-extension/test/olmv1-singleownnamespace.go +++ b/openshift/tests-extension/test/olmv1-singleownnamespace.go @@ -10,6 +10,7 @@ import ( //nolint:staticcheck // ST1001: dot-imports for readability . "github.com/onsi/gomega" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/api/meta" @@ -208,6 +209,174 @@ var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLMOwnSingleNamespace][Skipped:D }) }) +var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLMOwnSingleNamespace][Skipped:Disconnected][Serial] OLMv1 operator installation support for ownNamespace and single namespace watch mode with quay-operator", Ordered, Serial, func() { + var ( + k8sClient client.Client + activeNamespaces map[string]struct{} + ) + + BeforeEach(func() { + By("checking if OpenShift is available for tests") + if !env.Get().IsOpenShift { + Skip("Requires OpenShift for the tests") + } + helpers.RequireOLMv1CapabilityOnOpenshift() + k8sClient = env.Get().K8sClient + activeNamespaces = map[string]struct{}{} + }) + + AfterEach(func(ctx SpecContext) { + if CurrentSpecReport().Failed() { + for ns := range activeNamespaces { + helpers.DescribeAllClusterExtensions(ctx, ns) + } + } + }) + + It("should install cluster extensions successfully in both watch modes", + func(ctx SpecContext) { + scenarios := []struct { + id string + label string + watchN func(string) string + }{ + { + id: "singlens", + label: "singleNamespace watch mode", + watchN: func(installNamespace string) string { + return fmt.Sprintf("%s-watch", installNamespace) + }, + }, + { + id: "ownns", + label: "ownNamespace watch mode", + watchN: func(installNamespace string) string { + return installNamespace + }, + }, + } + + for _, scenario := range scenarios { + sc := scenario + suffix := rand.String(4) + installNamespace := fmt.Sprintf("olmv1-quay-bothns-%s-%s", sc.id, suffix) + watchNamespace := sc.watchN(installNamespace) + + activeNamespaces[installNamespace] = struct{}{} + if watchNamespace != installNamespace { + activeNamespaces[watchNamespace] = struct{}{} + } + + By(fmt.Sprintf("ensuring no ClusterExtension and CRD for quay-operator before %s scenario", sc.label)) + helpers.EnsureCleanupClusterExtension(context.Background(), "quay-operator", "quayregistries.quay.redhat.com") + + By(fmt.Sprintf("creating namespace %s for %s tests", installNamespace, sc.label)) + installNS := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: installNamespace, + }, + } + Expect(k8sClient.Create(ctx, installNS)).To(Succeed(), "failed to create install namespace %q", installNamespace) + installNamespaceCopy := installNamespace + DeferCleanup(func() { + By(fmt.Sprintf("cleanup: deleting install namespace %s", installNamespaceCopy)) + _ = k8sClient.Delete(context.Background(), &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: installNamespaceCopy}, + }, client.PropagationPolicy(metav1.DeletePropagationForeground)) + }) + + var watchNSObj *corev1.Namespace + if watchNamespace != installNamespace { + By(fmt.Sprintf("creating namespace %s for watch namespace in %s scenario", watchNamespace, sc.label)) + watchNSObj = &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: watchNamespace}, + } + Expect(k8sClient.Create(ctx, watchNSObj)).To(Succeed(), "failed to create watch namespace %q", watchNamespace) + watchNamespaceCopy := watchNamespace + DeferCleanup(func() { + By(fmt.Sprintf("cleanup: deleting watch namespace %s", watchNamespaceCopy)) + _ = k8sClient.Delete(context.Background(), &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: watchNamespaceCopy}, + }, client.PropagationPolicy(metav1.DeletePropagationForeground)) + }) + } + + saName := fmt.Sprintf("install-quay-bothns-%s-sa-%s", sc.id, suffix) + By(fmt.Sprintf("creating ServiceAccount %s for %s scenario", saName, sc.label)) + sa := helpers.NewServiceAccount(saName, installNamespace) + Expect(k8sClient.Create(ctx, sa)).To(Succeed(), "failed to create ServiceAccount %q", saName) + helpers.ExpectServiceAccountExists(ctx, saName, installNamespace) + DeferCleanup(func() { + By(fmt.Sprintf("cleanup: deleting ServiceAccount %s in namespace %s", sa.Name, sa.Namespace)) + _ = k8sClient.Delete(context.Background(), sa, client.PropagationPolicy(metav1.DeletePropagationForeground)) + }) + + crbName := fmt.Sprintf("install-quay-bothns-%s-crb-%s", sc.id, suffix) + By(fmt.Sprintf("creating ClusterRoleBinding %s for %s scenario", crbName, sc.label)) + crb := helpers.NewClusterRoleBinding(crbName, "cluster-admin", saName, installNamespace) + Expect(k8sClient.Create(ctx, crb)).To(Succeed(), "failed to create ClusterRoleBinding %q", crbName) + helpers.ExpectClusterRoleBindingExists(ctx, crbName) + DeferCleanup(func() { + By(fmt.Sprintf("cleanup: deleting ClusterRoleBinding %s", crb.Name)) + _ = k8sClient.Delete(context.Background(), crb, client.PropagationPolicy(metav1.DeletePropagationForeground)) + }) + + ceName := fmt.Sprintf("install-quay-bothns-%s-ce-%s", sc.id, suffix) + By(fmt.Sprintf("creating ClusterExtension %s for %s scenario", ceName, sc.label)) + ce := helpers.NewClusterExtensionObject("quay-operator", "3.14.2", ceName, saName, installNamespace) + ce.Spec.Config = &olmv1.ClusterExtensionConfig{ + ConfigType: olmv1.ClusterExtensionConfigTypeInline, + Inline: &apiextensionsv1.JSON{ + Raw: []byte(fmt.Sprintf(`{"watchNamespace": "%s"}`, watchNamespace)), + }, + } + Expect(k8sClient.Create(ctx, ce)).To(Succeed(), "failed to create ClusterExtension %q", ceName) + DeferCleanup(func() { + By(fmt.Sprintf("cleanup: deleting ClusterExtension %s", ce.Name)) + _ = k8sClient.Delete(context.Background(), ce, client.PropagationPolicy(metav1.DeletePropagationForeground)) + }) + + By(fmt.Sprintf("waiting for the ClusterExtension %s to be installed for %s scenario", ceName, sc.label)) + helpers.ExpectClusterExtensionToBeInstalled(ctx, ceName) + + By(fmt.Sprintf("verifying the operator deployment watch scope annotation for %s scenario", sc.label)) + Eventually(func(g Gomega) { + deployments := &appsv1.DeploymentList{} + err := k8sClient.List(ctx, deployments, client.InNamespace(installNamespace)) + g.Expect(err).ToNot(HaveOccurred(), "failed to list deployments in namespace %s", installNamespace) + g.Expect(deployments.Items).ToNot(BeEmpty(), "expected at least one deployment in namespace %s", installNamespace) + + found := false + for i := range deployments.Items { + annotations := deployments.Items[i].Spec.Template.Annotations + if annotations == nil { + continue + } + if val, ok := annotations["olm.targetNamespaces"]; ok { + g.Expect(val).To(Equal(watchNamespace), "unexpected watch scope annotation value") + found = true + break + } + } + g.Expect(found).To(BeTrue(), "failed to find deployment with olm.targetNamespaces annotation") + }).WithTimeout(5 * time.Minute).WithPolling(3 * time.Second).Should(Succeed()) + + By(fmt.Sprintf("cleaning up resources created for %s scenario to allow next scenario", sc.label)) + deletePolicy := metav1.DeletePropagationForeground + Expect(k8sClient.Delete(ctx, ce, client.PropagationPolicy(deletePolicy))).To(Succeed(), "failed to delete ClusterExtension %q", ceName) + helpers.EnsureCleanupClusterExtension(context.Background(), "quay-operator", "quayregistries.quay.redhat.com") + + Expect(k8sClient.Delete(ctx, crb, client.PropagationPolicy(deletePolicy))).To(Succeed(), "failed to delete ClusterRoleBinding %q", crbName) + Expect(k8sClient.Delete(ctx, sa, client.PropagationPolicy(deletePolicy))).To(Succeed(), "failed to delete ServiceAccount %q", saName) + + if watchNSObj != nil { + Expect(k8sClient.Delete(ctx, watchNSObj, client.PropagationPolicy(deletePolicy))).To(Succeed(), "failed to delete watch namespace %q", watchNamespace) + } + Expect(k8sClient.Delete(ctx, installNS, client.PropagationPolicy(deletePolicy))).To(Succeed(), "failed to delete install namespace %q", installNamespace) + } + }) +}) + var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLMOwnSingleNamespace][Skipped:Disconnected][Serial] OLMv1 operator installation support for ownNamespace watch mode with an operator that does not support ownNamespace installation mode", Ordered, Serial, func() { var ( k8sClient client.Client @@ -311,3 +480,109 @@ var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLMOwnSingleNamespace][Skipped:D }).WithTimeout(5 * time.Minute).WithPolling(1 * time.Second).Should(Succeed()) }) }) + +var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLMOwnSingleNamespace][Skipped:Disconnected][Serial] OLMv1 operator installation should reject invalid watch namespace configuration and update the status conditions accordingly", Ordered, Serial, func() { + var ( + k8sClient client.Client + namespace string + testPrefix = "invalidwatch" + ) + + var unique, saName, crbName, ceName string + BeforeEach(func() { + By("checking if OpenShift is available for tests") + if !env.Get().IsOpenShift { + Skip("Requires OpenShift for the tests") + } + helpers.RequireOLMv1CapabilityOnOpenshift() + k8sClient = env.Get().K8sClient + unique = rand.String(4) + namespace = fmt.Sprintf("olmv1-%s-ns-%s", testPrefix, unique) + saName = fmt.Sprintf("install-%s-sa-%s", testPrefix, unique) + crbName = fmt.Sprintf("install-%s-crb-%s", testPrefix, unique) + ceName = fmt.Sprintf("install-%s-ce-%s", testPrefix, unique) + + By("ensuring no lingering ClusterExtensions or CRDs for quay-operator") + helpers.EnsureCleanupClusterExtension(context.Background(), "quay-operator", "quayregistries.quay.redhat.com") + + By(fmt.Sprintf("creating namespace %s for invalid watch namespace tests", namespace)) + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + } + Expect(k8sClient.Create(context.Background(), ns)).To(Succeed(), "failed to create test namespace %q", namespace) + DeferCleanup(func() { + By(fmt.Sprintf("cleaning up namespace %s", namespace)) + _ = k8sClient.Delete(context.Background(), ns, client.PropagationPolicy(metav1.DeletePropagationForeground)) + }) + }) + + AfterEach(func(ctx SpecContext) { + if CurrentSpecReport().Failed() { + By("dumping for debugging") + helpers.DescribeAllClusterExtensions(ctx, namespace) + } + }) + + // The controller validates the inline watchNamespace using the same DNS-1123 rules that gate namespace names. + // Setting a trailing '-' produces an invalid identifier that cannot exist in the cluster, so the install should + // fail fast and surface a failure through the Installed condition. + It("should fail to install the ClusterExtension when watch namespace is invalid", + func(ctx SpecContext) { + By("creating ServiceAccount") + sa := helpers.NewServiceAccount(saName, namespace) + Expect(k8sClient.Create(ctx, sa)).To(Succeed(), "failed to create ServiceAccount %q", saName) + By("ensuring ServiceAccount is available before proceeding") + helpers.ExpectServiceAccountExists(ctx, saName, namespace) + DeferCleanup(func() { + By(fmt.Sprintf("cleanup: deleting ServiceAccount %s in namespace %s", sa.Name, sa.Namespace)) + _ = k8sClient.Delete(context.Background(), sa, client.PropagationPolicy(metav1.DeletePropagationForeground)) + }) + + By("creating ClusterRoleBinding") + crb := helpers.NewClusterRoleBinding(crbName, "cluster-admin", saName, namespace) + Expect(k8sClient.Create(ctx, crb)).To(Succeed(), "failed to create ClusterRoleBinding %q", crbName) + By("ensuring ClusterRoleBinding is available before proceeding") + helpers.ExpectClusterRoleBindingExists(ctx, crbName) + DeferCleanup(func() { + By(fmt.Sprintf("cleanup: deleting ClusterRoleBinding %s", crb.Name)) + _ = k8sClient.Delete(context.Background(), crb, client.PropagationPolicy(metav1.DeletePropagationForeground)) + }) + + invalidWatchNamespace := fmt.Sprintf("%s-", namespace) + + By("creating ClusterExtension with an invalid watch namespace configured") + ce := helpers.NewClusterExtensionObject("quay-operator", "3.14.2", ceName, saName, namespace) + ce.Spec.Config = &olmv1.ClusterExtensionConfig{ + ConfigType: olmv1.ClusterExtensionConfigTypeInline, + Inline: &apiextensionsv1.JSON{ + Raw: []byte(fmt.Sprintf(`{"watchNamespace": "%s"}`, invalidWatchNamespace)), + }, + } + Expect(k8sClient.Create(ctx, ce)).To(Succeed(), "failed to create ClusterExtension %q", ceName) + DeferCleanup(func() { + By(fmt.Sprintf("cleanup: deleting ClusterExtension %s", ce.Name)) + _ = k8sClient.Delete(context.Background(), ce, client.PropagationPolicy(metav1.DeletePropagationForeground)) + + By("ensuring ClusterExtension is deleted") + helpers.EnsureCleanupClusterExtension(context.Background(), ceName, namespace) + }) + + By("waiting for the ClusterExtension installation to fail due to invalid watch namespace") + Eventually(func(g Gomega) { + var ext olmv1.ClusterExtension + err := k8sClient.Get(ctx, client.ObjectKey{Name: ceName}, &ext) + g.Expect(err).ToNot(HaveOccurred(), "failed to get ClusterExtension %q", ceName) + + conditions := ext.Status.Conditions + g.Expect(conditions).ToNot(BeEmpty(), "ClusterExtension %q has empty status.conditions", ceName) + + installed := meta.FindStatusCondition(conditions, olmv1.TypeInstalled) + g.Expect(installed).ToNot(BeNil(), "Installed condition not found") + g.Expect(installed.Status).To(Equal(metav1.ConditionFalse), "Installed should be False") + g.Expect(installed.Reason).To(Equal(olmv1.ReasonFailed)) + g.Expect(installed.Message).ToNot(BeEmpty()) + }).WithTimeout(5 * time.Minute).WithPolling(3 * time.Second).Should(Succeed()) + }) +})