From 078d95706447656a2314ab282b85d2cdd5ece92c Mon Sep 17 00:00:00 2001 From: Daniel Dorado Date: Tue, 23 Dec 2025 09:12:22 +0100 Subject: [PATCH] feat: refactor test - Remove Controller because it is not needed in an E2E test. - Changing BeforeSuite and AfterSuite by their Synchronized pair, because they are sharing cluster. --- go.mod | 1 - go.sum | 2 - test/e2e/e2e_suite_test.go | 164 ++++++++++++++++------------------ test/e2e/rediscluster_test.go | 2 - 4 files changed, 79 insertions(+), 90 deletions(-) diff --git a/go.mod b/go.mod index 1717f51..e064bbd 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( k8s.io/apiextensions-apiserver v0.33.0 k8s.io/apimachinery v0.33.0 k8s.io/client-go v0.33.0 - k8s.io/kubectl v0.33.0 k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e sigs.k8s.io/controller-runtime v0.20.4 ) diff --git a/go.sum b/go.sum index eefe4ec..efe1835 100644 --- a/go.sum +++ b/go.sum @@ -184,8 +184,6 @@ k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/kubectl v0.33.0 h1:HiRb1yqibBSCqic4pRZP+viiOBAnIdwYDpzUFejs07g= -k8s.io/kubectl v0.33.0/go.mod h1:gAlGBuS1Jq1fYZ9AjGWbI/5Vk3M/VW2DK4g10Fpyn/0= k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro= k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU= diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index bdcb846..096c7fc 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -6,13 +6,11 @@ package e2e import ( "context" - "fmt" + "os" "path/filepath" "testing" "time" - "k8s.io/apimachinery/pkg/api/errors" - redkeyv1 "github.com/inditextech/redkeyoperator/api/v1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -20,104 +18,100 @@ import ( apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/client-go/rest" - "k8s.io/kubectl/pkg/scheme" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/cache" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/clientcmd" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - "sigs.k8s.io/controller-runtime/pkg/metrics/server" ) const ( - crdDirName = "deployment" - metricsAddr = ":8080" defaultPoll = 10 * time.Second defaultWait = 100 * time.Second ) var ( - testEnv *envtest.Environment - k8sClient client.Client - managerCancel context.CancelFunc - ctx context.Context + k8sClient client.Client + ctx context.Context + cancel context.CancelFunc ) -// NewCachingClientFunc returns a controller‑runtime NewClientFunc -// that enables Unstructured caching (i.e. watches on arbitrary CRDs). -// This is useful when you rely on types not registered in the built‑in scheme. -func NewCachingClientFunc() client.NewClientFunc { - return func(cfg *rest.Config, opts client.Options) (client.Client, error) { - // Turn on unstructured caching for CRDs / unknown types - opts.Cache.Unstructured = true - return client.New(cfg, opts) - } -} - func TestE2E(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Redkey Operator E2E Suite", Label("e2e")) } -var _ = BeforeSuite(func() { - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) - - By("bootstrapping test environment") - useCluster := true - testEnv = &envtest.Environment{ - UseExistingCluster: &useCluster, - AttachControlPlaneOutput: true, - CRDDirectoryPaths: []string{filepath.Join("..", "..", crdDirName)}, - ErrorIfCRDPathMissing: true, - } - - cfg, err := testEnv.Start() - if err != nil { - if errors.IsAlreadyExists(err) { - GinkgoWriter.Printf("warning: CRD already existed, continuing: %v\n", err) - err = nil - } else { - Fail(fmt.Sprintf("failed to start test environment: %v", err)) +// SynchronizedBeforeSuite ensures cluster-level setup runs once across all +// parallel Ginkgo processes. The first process (process 1) performs the +// one-time setup, and all processes then create their own Kubernetes client. +var _ = SynchronizedBeforeSuite( + // This function runs ONLY on process 1 (the "primary" process). + // It returns data that will be passed to all processes. + func() []byte { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("registering schemes (process 1)") + utilruntime.Must(redkeyv1.AddToScheme(scheme.Scheme)) + utilruntime.Must(corev1.AddToScheme(scheme.Scheme)) + utilruntime.Must(apiextensions.AddToScheme(scheme.Scheme)) + utilruntime.Must(metav1.AddMetaToScheme(scheme.Scheme)) + + // Verify CRD directory exists for documentation purposes only. + // The CRD should already be installed in the cluster. + crdDir := filepath.Join("..", "..", "deployment") + _, err := os.Stat(crdDir) + Expect(err).NotTo(HaveOccurred(), "CRD directory %q must exist", crdDir) + + // Return empty data; all processes will create their own client. + return nil + }, + // This function runs on ALL processes (including process 1). + // It receives the data returned by the first function. + func(_ []byte) { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("creating Kubernetes client") + + // Register schemes on this process (they're per-process state) + utilruntime.Must(redkeyv1.AddToScheme(scheme.Scheme)) + utilruntime.Must(corev1.AddToScheme(scheme.Scheme)) + utilruntime.Must(apiextensions.AddToScheme(scheme.Scheme)) + utilruntime.Must(metav1.AddMetaToScheme(scheme.Scheme)) + + // Load kubeconfig from standard locations + kubeconfig := os.Getenv("KUBECONFIG") + if kubeconfig == "" { + home, _ := os.UserHomeDir() + kubeconfig = filepath.Join(home, ".kube", "config") + } + + cfg, err := clientcmd.BuildConfigFromFlags("", kubeconfig) + Expect(err).NotTo(HaveOccurred(), "failed to load kubeconfig from %s", kubeconfig) + Expect(cfg).NotTo(BeNil()) + + // Create a simple controller-runtime client (no manager needed for E2E) + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred(), "failed to create Kubernetes client") + Expect(k8sClient).NotTo(BeNil()) + + // Create a cancellable context for all tests + ctx, cancel = context.WithCancel(context.Background()) + }, +) + +// SynchronizedAfterSuite ensures cleanup runs safely across all parallel processes. +// The second function runs only on process 1 after all other processes have finished. +var _ = SynchronizedAfterSuite( + // This function runs on ALL processes. + func() { + By("cleaning up test context") + if cancel != nil { + cancel() } - } - Expect(cfg).NotTo(BeNil()) - Expect(err).NotTo(HaveOccurred()) - - // register all schemes in one shot - utilruntime.Must(redkeyv1.AddToScheme(scheme.Scheme)) - utilruntime.Must(corev1.AddToScheme(scheme.Scheme)) - utilruntime.Must(apiextensions.AddToScheme(scheme.Scheme)) - utilruntime.Must(metav1.AddMetaToScheme(scheme.Scheme)) - - port := 8080 + GinkgoParallelProcess() - 1 - metricsAddr := fmt.Sprintf(":%d", port) - - By("creating controller manager") - mgr, err := ctrl.NewManager(cfg, ctrl.Options{ - NewClient: NewCachingClientFunc(), - Scheme: scheme.Scheme, - Cache: cache.Options{DefaultNamespaces: map[string]cache.Config{}}, - Metrics: server.Options{BindAddress: metricsAddr}, - }) - - Expect(err).NotTo(HaveOccurred()) - - k8sClient = mgr.GetClient() - Expect(k8sClient).NotTo(BeNil()) - - ctx, managerCancel = context.WithCancel(ctrl.SetupSignalHandler()) - go func() { - defer GinkgoRecover() - Expect(mgr.Start(ctx)).To(Succeed()) - }() -}) - -var _ = AfterSuite(func() { - By("shutting down test environment") - if managerCancel != nil { - managerCancel() - } - Expect(testEnv.Stop()).To(Succeed()) -}) + }, + // This function runs ONLY on process 1, after all other processes have exited. + func() { + By("final cleanup complete") + // No cluster-level teardown needed; we use an existing cluster. + }, +) diff --git a/test/e2e/rediscluster_test.go b/test/e2e/rediscluster_test.go index d510d98..fae10b2 100644 --- a/test/e2e/rediscluster_test.go +++ b/test/e2e/rediscluster_test.go @@ -105,8 +105,6 @@ var _ = Describe("Redkey Operator & RedkeyCluster E2E", Label("operator", "clust Expect(rc.Spec.Storage).To(Equal(storage)) Expect(*rc.Spec.PurgeKeysOnRebalance).To(Equal(purgeKeys)) Expect(rc.Spec.Ephemeral).To(Equal(ephemeral)) - Expect(rc.Kind).To(Equal("RedkeyCluster")) - Expect(rc.APIVersion).To(Equal("redis.inditex.dev/v1")) Expect(rc.Spec.Auth).To(Equal(redkeyv1.RedisAuth{})) Expect(rc.Spec.Image).To(Equal(framework.GetRedisImage())) Expect(rc.Spec.Pdb).To(Equal(pdb))