Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
164 changes: 79 additions & 85 deletions test/e2e/e2e_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,118 +6,112 @@ 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"
corev1 "k8s.io/api/core/v1"
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.
},
)
2 changes: 0 additions & 2 deletions test/e2e/rediscluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down