diff --git a/pkg/controllers/redis/redis_controller_test.go b/pkg/controllers/redis/redis_controller_test.go index 5ec6e8317..d9e07da08 100644 --- a/pkg/controllers/redis/redis_controller_test.go +++ b/pkg/controllers/redis/redis_controller_test.go @@ -2,80 +2,100 @@ package redis import ( "context" - "fmt" - "math/rand" + "os" + "path/filepath" redisv1beta2 "github.com/OT-CONTAINER-KIT/redis-operator/api/v1beta2" - factories "github.com/OT-CONTAINER-KIT/redis-operator/pkg/testutil/factories/redis" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/yaml" "sigs.k8s.io/controller-runtime/pkg/client" ) -var _ = Describe("Redis test", func() { - Describe("When creating a redis without custom fields", func() { +var _ = Describe("Redis Controller", func() { + Context("When deploying Redis from testdata", func() { var ( - redisCR *redisv1beta2.Redis - redisCRName string + redis *redisv1beta2.Redis + testFile string ) + BeforeEach(func() { - redisCRName = fmt.Sprintf("redis-%d", rand.Int31()) //nolint:gosec - redisCR = factories.New(redisCRName) - Expect(k8sClient.Create(context.TODO(), redisCR)).Should(Succeed()) + testFile = filepath.Join("testdata", "full.yaml") + redis = &redisv1beta2.Redis{} + + yamlFile, err := os.ReadFile(testFile) + Expect(err).NotTo(HaveOccurred()) + + err = yaml.Unmarshal(yamlFile, redis) + Expect(err).NotTo(HaveOccurred()) + + redis.Namespace = ns + + Expect(k8sClient.Create(context.Background(), redis)).Should(Succeed()) }) - DescribeTable("the reconciler", - func(nameFmt string, obj client.Object) { - key := types.NamespacedName{ - Name: fmt.Sprintf(nameFmt, redisCRName), + AfterEach(func() { + Expect(k8sClient.Delete(context.Background(), redis)).Should(Succeed()) + }) + + It("should create all required resources", func() { + By("verifying the StatefulSet is created") + sts := &appsv1.StatefulSet{} + Eventually(func() error { + return k8sClient.Get(context.Background(), types.NamespacedName{ + Name: redis.Name, Namespace: ns, - } + }, sts) + }, timeout, interval).Should(Succeed()) + + By("verifying the headless Service is created") + headlessSvc := &corev1.Service{} + Eventually(func() error { + return k8sClient.Get(context.Background(), types.NamespacedName{ + Name: redis.Name + "-headless", + Namespace: ns, + }, headlessSvc) + }, timeout, interval).Should(Succeed()) - By("creating the resource when the cluster is created") - Eventually(func() error { return k8sClient.Get(context.TODO(), key, obj) }, timeout).Should(Succeed()) + By("verifying the additional Service is created") + additionalSvc := &corev1.Service{} + Eventually(func() error { + return k8sClient.Get(context.Background(), types.NamespacedName{ + Name: redis.Name + "-additional", + Namespace: ns, + }, additionalSvc) + }, timeout, interval).Should(Succeed()) - By("setting the owner reference") + By("verifying owner references") + for _, obj := range []client.Object{sts, headlessSvc, additionalSvc} { ownerRefs := obj.GetOwnerReferences() Expect(ownerRefs).To(HaveLen(1)) - Expect(ownerRefs[0].Name).To(Equal(redisCRName)) - }, - Entry("reconciles the leader statefulset", "%s", &appsv1.StatefulSet{}), - Entry("reconciles the leader headless service", "%s-headless", &corev1.Service{}), - Entry("reconciles the leader additional service", "%s-additional", &corev1.Service{}), - ) - }) + Expect(ownerRefs[0].Name).To(Equal(redis.Name)) + } - Describe("When creating a redis, ignore annotations", func() { - var ( - redisCR *redisv1beta2.Redis - redisCRName string - ) - BeforeEach(func() { - redisCRName = fmt.Sprintf("redis-%d", rand.Int31()) //nolint:gosec - redisCR = factories.New( - redisCRName, - factories.WithAnnotations(map[string]string{ - "key1": "value1", - "key2": "value2", - }), - factories.WithIgnoredKeys([]string{"key1"}), - ) - Expect(k8sClient.Create(context.TODO(), redisCR)).Should(Succeed()) - }) - Describe("the reconciler", func() { - It("should ignore key in statefulset", func() { - stsLeader := &appsv1.StatefulSet{} - stsLeaderNN := types.NamespacedName{ - Name: redisCRName, - Namespace: ns, + By("verifying StatefulSet specifications") + Expect(sts.Spec.Template.Spec.SecurityContext).To(Equal(redis.Spec.PodSecurityContext)) + Expect(sts.Spec.Template.Spec.Containers[0].Image).To(Equal(redis.Spec.KubernetesConfig.Image)) + + By("verifying PVC specifications") + Expect(sts.Spec.VolumeClaimTemplates[0].Spec.Resources.Requests.Storage()).To(Equal( + redis.Spec.Storage.VolumeClaimTemplate.Spec.Resources.Requests.Storage())) + + By("verifying Redis Exporter configuration") + var exporterContainer *corev1.Container + for _, container := range sts.Spec.Template.Spec.Containers { + if container.Name == "redis-exporter" { + exporterContainer = &container //nolint:exportloopref + break } - Eventually(func() error { return k8sClient.Get(context.TODO(), stsLeaderNN, stsLeader) }, timeout, interval).Should(BeNil()) - Expect(stsLeader.Annotations).To(HaveKey("key2")) - Expect(stsLeader.Annotations).NotTo(HaveKey("key1")) - }) + } + Expect(exporterContainer).NotTo(BeNil(), "Redis Exporter container should exist") + Expect(exporterContainer.Image).To(Equal(redis.Spec.RedisExporter.Image)) + Expect(exporterContainer.ImagePullPolicy).To(Equal(redis.Spec.RedisExporter.ImagePullPolicy)) + Expect(exporterContainer.Resources).To(Equal(*redis.Spec.RedisExporter.Resources)) }) }) }) diff --git a/pkg/controllers/redis/testdata/full.yaml b/pkg/controllers/redis/testdata/full.yaml new file mode 100644 index 000000000..8f48e8734 --- /dev/null +++ b/pkg/controllers/redis/testdata/full.yaml @@ -0,0 +1,37 @@ +--- +apiVersion: redis.redis.opstreelabs.in/v1beta2 +kind: Redis +metadata: + name: redis-standalone +spec: + podSecurityContext: + runAsUser: 1000 + fsGroup: 1000 + kubernetesConfig: + image: quay.io/opstree/redis:v7.0.12 + imagePullPolicy: IfNotPresent + resources: + requests: + cpu: 101m + memory: 128Mi + limits: + cpu: 101m + memory: 128Mi + redisExporter: + enabled: true + image: quay.io/opstree/redis-exporter:v1.44.0 + imagePullPolicy: Always + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 100m + memory: 128Mi + storage: + volumeClaimTemplate: + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 1Gi diff --git a/pkg/controllers/rediscluster/rediscluster_controller_test.go b/pkg/controllers/rediscluster/rediscluster_controller_test.go index 82cd7f19d..ee683a32d 100644 --- a/pkg/controllers/rediscluster/rediscluster_controller_test.go +++ b/pkg/controllers/rediscluster/rediscluster_controller_test.go @@ -2,103 +2,125 @@ package rediscluster import ( "context" - "fmt" - "math/rand" + "os" + "path/filepath" redisv1beta2 "github.com/OT-CONTAINER-KIT/redis-operator/api/v1beta2" - factories "github.com/OT-CONTAINER-KIT/redis-operator/pkg/testutil/factories/rediscluster" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/yaml" "sigs.k8s.io/controller-runtime/pkg/client" ) -var _ = Describe("Redis cluster test", func() { - Describe("When creating a redis cluster without custom fields", func() { +var _ = Describe("Redis Cluster Controller", func() { + Context("When deploying Redis Cluster from testdata", func() { var ( - redisClusterCR *redisv1beta2.RedisCluster - redisClusterCRName string + redisCluster *redisv1beta2.RedisCluster + testFile string ) + BeforeEach(func() { - redisClusterCRName = fmt.Sprintf("redis-cluster-%d", rand.Int31()) //nolint:gosec - redisClusterCR = factories.New(redisClusterCRName) - Expect(k8sClient.Create(context.TODO(), redisClusterCR)).Should(Succeed()) + testFile = filepath.Join("testdata", "full.yaml") + redisCluster = &redisv1beta2.RedisCluster{} + + yamlFile, err := os.ReadFile(testFile) + Expect(err).NotTo(HaveOccurred()) + + err = yaml.Unmarshal(yamlFile, redisCluster) + Expect(err).NotTo(HaveOccurred()) + + redisCluster.Namespace = ns + + Expect(k8sClient.Create(context.Background(), redisCluster)).Should(Succeed()) + }) + + AfterEach(func() { + Expect(k8sClient.Delete(context.Background(), redisCluster)).Should(Succeed()) }) - DescribeTable("the reconciler", - func(nameFmt string, obj client.Object) { - key := types.NamespacedName{ - Name: fmt.Sprintf(nameFmt, redisClusterCRName), + It("should create all required resources", func() { + By("verifying the Redis Cluster Leader StatefulSet is created") + leaderSts := &appsv1.StatefulSet{} + Eventually(func() error { + return k8sClient.Get(context.Background(), types.NamespacedName{ + Name: redisCluster.Name + "-leader", Namespace: ns, - } + }, leaderSts) + }, timeout, interval).Should(Succeed()) - By("creating the resource when the cluster is created") - Eventually(func() error { return k8sClient.Get(context.TODO(), key, obj) }, timeout).Should(Succeed()) + By("verifying the Redis Cluster Leader Service is created") + leaderSvc := &corev1.Service{} + Eventually(func() error { + return k8sClient.Get(context.Background(), types.NamespacedName{ + Name: redisCluster.Name + "-leader", + Namespace: ns, + }, leaderSvc) + }, timeout, interval).Should(Succeed()) + + By("verifying the Redis Cluster headless Service is created") + headlessSvc := &corev1.Service{} + Eventually(func() error { + return k8sClient.Get(context.Background(), types.NamespacedName{ + Name: redisCluster.Name + "-leader-headless", + Namespace: ns, + }, headlessSvc) + }, timeout, interval).Should(Succeed()) - By("setting the owner reference") + By("verifying owner references") + for _, obj := range []client.Object{leaderSts, leaderSvc, headlessSvc} { ownerRefs := obj.GetOwnerReferences() Expect(ownerRefs).To(HaveLen(1)) - Expect(ownerRefs[0].Name).To(Equal(redisClusterCRName)) - }, - Entry("reconciles the leader statefulset", "%s-leader", &appsv1.StatefulSet{}), - Entry("reconciles the leader service", "%s-leader", &corev1.Service{}), - Entry("reconciles the leader headless service", "%s-leader-headless", &corev1.Service{}), - Entry("reconciles the leader additional service", "%s-leader-additional", &corev1.Service{}), - ) - }) + Expect(ownerRefs[0].Name).To(Equal(redisCluster.Name)) + } - Describe("When creating a redis cluster with DisablePersistence", func() { - var ( - redisClusterCR *redisv1beta2.RedisCluster - redisClusterCRName string - ) - BeforeEach(func() { - redisClusterCRName = fmt.Sprintf("redis-cluster-%d", rand.Int31()) //nolint:gosec - redisClusterCR = factories.New(redisClusterCRName, factories.DisablePersistence()) - Expect(k8sClient.Create(context.TODO(), redisClusterCR)).Should(Succeed()) - }) + By("verifying StatefulSet specifications") + Expect(leaderSts.Spec.Template.Spec.SecurityContext).To(Equal(redisCluster.Spec.PodSecurityContext)) + Expect(leaderSts.Spec.Template.Spec.Containers[0].Image).To(Equal(redisCluster.Spec.KubernetesConfig.Image)) + Expect(leaderSts.Spec.Template.Spec.Containers[0].ImagePullPolicy).To(Equal(redisCluster.Spec.KubernetesConfig.ImagePullPolicy)) - It("should create leader statefulset without persistence volume", func() { - stsLeader := &appsv1.StatefulSet{} - stsLeaderNN := types.NamespacedName{ - Name: redisClusterCRName + "-leader", - Namespace: ns, + By("verifying Service specifications") + expectedLabels := map[string]string{ + "app": redisCluster.Name + "-leader", + "redis_setup_type": "cluster", + "role": "leader", } - Eventually(func() error { return k8sClient.Get(context.TODO(), stsLeaderNN, stsLeader) }, timeout, interval).Should(BeNil()) - Expect(stsLeader.Spec.VolumeClaimTemplates).To(HaveLen(0)) - }) - }) + Expect(leaderSvc.Labels).To(Equal(expectedLabels)) - Describe("When creating a redis cluster, ignore annotations", func() { - var ( - redisClusterCR *redisv1beta2.RedisCluster - redisClusterCRName string - ) - BeforeEach(func() { - redisClusterCRName = fmt.Sprintf("redis-cluster-%d", rand.Int31()) //nolint:gosec - redisClusterCR = factories.New( - redisClusterCRName, - factories.WithAnnotations(map[string]string{ - "key1": "value1", - "key2": "value2", - }), - factories.WithIgnoredKeys([]string{"key1"}), - ) - Expect(k8sClient.Create(context.TODO(), redisClusterCR)).Should(Succeed()) - }) - Describe("the reconciler", func() { - It("should ignore key in leader statefulset", func() { - stsLeader := &appsv1.StatefulSet{} - stsLeaderNN := types.NamespacedName{ - Name: redisClusterCRName + "-leader", - Namespace: ns, + expectedHeadlessLabels := map[string]string{ + "app": redisCluster.Name + "-leader", + "redis_setup_type": "cluster", + "role": "leader", + } + Expect(headlessSvc.Labels).To(Equal(expectedHeadlessLabels)) + + By("verifying cluster configuration") + Expect(leaderSts.Spec.Replicas).NotTo(BeNil()) + expectedReplicas := int32(3) + Expect(*leaderSts.Spec.Replicas).To(Equal(expectedReplicas)) + + By("verifying Redis Cluster configuration") + Expect(leaderSts.Spec.ServiceName).To(Equal(redisCluster.Name + "-leader-headless")) + + By("verifying resource requirements") + container := leaderSts.Spec.Template.Spec.Containers[0] + Expect(container.Resources.Limits).To(Equal(redisCluster.Spec.KubernetesConfig.Resources.Limits)) + Expect(container.Resources.Requests).To(Equal(redisCluster.Spec.KubernetesConfig.Resources.Requests)) + + By("verifying Redis Exporter configuration") + var exporterContainer *corev1.Container + for _, c := range leaderSts.Spec.Template.Spec.Containers { + if c.Name == "redis-exporter" { + exporterContainer = &c //nolint:exportloopref + break } - Eventually(func() error { return k8sClient.Get(context.TODO(), stsLeaderNN, stsLeader) }, timeout, interval).Should(BeNil()) - Expect(stsLeader.Annotations).To(HaveKey("key2")) - Expect(stsLeader.Annotations).NotTo(HaveKey("key1")) - }) + } + Expect(exporterContainer).NotTo(BeNil(), "Redis Exporter container should exist") + Expect(exporterContainer.Image).To(Equal(redisCluster.Spec.RedisExporter.Image)) + Expect(exporterContainer.ImagePullPolicy).To(Equal(redisCluster.Spec.RedisExporter.ImagePullPolicy)) + Expect(exporterContainer.Resources).To(Equal(*redisCluster.Spec.RedisExporter.Resources)) }) }) }) diff --git a/pkg/controllers/rediscluster/testdata/full.yaml b/pkg/controllers/rediscluster/testdata/full.yaml new file mode 100644 index 000000000..cd888ab8f --- /dev/null +++ b/pkg/controllers/rediscluster/testdata/full.yaml @@ -0,0 +1,48 @@ +--- +apiVersion: redis.redis.opstreelabs.in/v1beta2 +kind: RedisCluster +metadata: + name: redis-cluster +spec: + clusterSize: 3 + clusterVersion: v7 + persistenceEnabled: true + podSecurityContext: + runAsUser: 1000 + fsGroup: 1000 + kubernetesConfig: + image: quay.io/opstree/redis:v7.0.12 + imagePullPolicy: IfNotPresent + resources: + requests: + cpu: 101m + memory: 128Mi + limits: + cpu: 101m + memory: 128Mi + redisExporter: + enabled: true + image: quay.io/opstree/redis-exporter:v1.44.0 + imagePullPolicy: Always + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 100m + memory: 128Mi + storage: + volumeClaimTemplate: + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 1Gi + nodeConfVolume: true + nodeConfVolumeClaimTemplate: + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 1Gi + diff --git a/pkg/controllers/redisreplication/redisreplication_controller_test.go b/pkg/controllers/redisreplication/redisreplication_controller_test.go index b957974f9..bc5e2789b 100644 --- a/pkg/controllers/redisreplication/redisreplication_controller_test.go +++ b/pkg/controllers/redisreplication/redisreplication_controller_test.go @@ -2,80 +2,106 @@ package redisreplication import ( "context" - "fmt" - "math/rand" + "os" + "path/filepath" redisv1beta2 "github.com/OT-CONTAINER-KIT/redis-operator/api/v1beta2" - factories "github.com/OT-CONTAINER-KIT/redis-operator/pkg/testutil/factories/redisreplication" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/yaml" "sigs.k8s.io/controller-runtime/pkg/client" ) -var _ = Describe("Redis test", func() { - Describe("When creating a redis without custom fields", func() { +var _ = Describe("Redis Replication Controller", func() { + Context("When deploying Redis Replication from testdata", func() { var ( - cr *redisv1beta2.RedisReplication - crName string + redisReplication *redisv1beta2.RedisReplication + testFile string ) + BeforeEach(func() { - crName = fmt.Sprintf("redis-%d", rand.Int31()) //nolint:gosec - cr = factories.New(crName) - Expect(k8sClient.Create(context.TODO(), cr)).Should(Succeed()) + testFile = filepath.Join("testdata", "full.yaml") + redisReplication = &redisv1beta2.RedisReplication{} + + yamlFile, err := os.ReadFile(testFile) + Expect(err).NotTo(HaveOccurred()) + + err = yaml.Unmarshal(yamlFile, redisReplication) + Expect(err).NotTo(HaveOccurred()) + + redisReplication.Namespace = ns + + Expect(k8sClient.Create(context.Background(), redisReplication)).Should(Succeed()) }) - DescribeTable("the reconciler", - func(nameFmt string, obj client.Object) { - key := types.NamespacedName{ - Name: fmt.Sprintf(nameFmt, crName), + AfterEach(func() { + Expect(k8sClient.Delete(context.Background(), redisReplication)).Should(Succeed()) + }) + + It("should create all required resources", func() { + By("verifying the StatefulSet is created") + sts := &appsv1.StatefulSet{} + Eventually(func() error { + return k8sClient.Get(context.Background(), types.NamespacedName{ + Name: redisReplication.Name, Namespace: ns, - } + }, sts) + }, timeout, interval).Should(Succeed()) - By("creating the resource when the cluster is created") - Eventually(func() error { return k8sClient.Get(context.TODO(), key, obj) }, timeout).Should(Succeed()) + By("verifying the headless Service is created") + headlessSvc := &corev1.Service{} + Eventually(func() error { + return k8sClient.Get(context.Background(), types.NamespacedName{ + Name: redisReplication.Name + "-headless", + Namespace: ns, + }, headlessSvc) + }, timeout, interval).Should(Succeed()) + + By("verifying the additional Service is created") + additionalSvc := &corev1.Service{} + Eventually(func() error { + return k8sClient.Get(context.Background(), types.NamespacedName{ + Name: redisReplication.Name + "-additional", + Namespace: ns, + }, additionalSvc) + }, timeout, interval).Should(Succeed()) - By("setting the owner reference") + By("verifying owner references") + for _, obj := range []client.Object{sts, headlessSvc, additionalSvc} { ownerRefs := obj.GetOwnerReferences() Expect(ownerRefs).To(HaveLen(1)) - Expect(ownerRefs[0].Name).To(Equal(crName)) - }, - Entry("reconciles the leader statefulset", "%s", &appsv1.StatefulSet{}), - Entry("reconciles the leader headless service", "%s-headless", &corev1.Service{}), - Entry("reconciles the leader additional service", "%s-additional", &corev1.Service{}), - ) - }) + Expect(ownerRefs[0].Name).To(Equal(redisReplication.Name)) + } - Describe("When creating a redis, ignore annotations", func() { - var ( - cr *redisv1beta2.RedisReplication - crName string - ) - BeforeEach(func() { - crName = fmt.Sprintf("redis-%d", rand.Int31()) //nolint:gosec - cr = factories.New( - crName, - factories.WithAnnotations(map[string]string{ - "key1": "value1", - "key2": "value2", - }), - factories.WithIgnoredKeys([]string{"key1"}), - ) - Expect(k8sClient.Create(context.TODO(), cr)).Should(Succeed()) - }) - Describe("the reconciler", func() { - It("should ignore key in statefulset", func() { - stsLeader := &appsv1.StatefulSet{} - stsLeaderNN := types.NamespacedName{ - Name: crName, - Namespace: ns, + By("verifying StatefulSet specifications") + Expect(sts.Spec.Template.Spec.SecurityContext).To(Equal(redisReplication.Spec.PodSecurityContext)) + Expect(sts.Spec.Template.Spec.Containers[0].Image).To(Equal(redisReplication.Spec.KubernetesConfig.Image)) + + By("verifying PVC specifications") + Expect(sts.Spec.VolumeClaimTemplates).To(HaveLen(1)) + Expect(sts.Spec.VolumeClaimTemplates[0].Spec.Resources.Requests.Storage()).To(Equal( + redisReplication.Spec.Storage.VolumeClaimTemplate.Spec.Resources.Requests.Storage())) + + By("verifying replication configuration") + Expect(sts.Spec.Replicas).NotTo(BeNil()) + expectedReplicas := int32(3) + Expect(*sts.Spec.Replicas).To(Equal(expectedReplicas)) + + By("verifying Redis Exporter configuration") + By("verifying Redis Exporter container") + var exporterContainer *corev1.Container + for i := range sts.Spec.Template.Spec.Containers { + if sts.Spec.Template.Spec.Containers[i].Name == "redis-exporter" { + exporterContainer = &sts.Spec.Template.Spec.Containers[i] + break } - Eventually(func() error { return k8sClient.Get(context.TODO(), stsLeaderNN, stsLeader) }, timeout, interval).Should(BeNil()) - Expect(stsLeader.Annotations).To(HaveKey("key2")) - Expect(stsLeader.Annotations).NotTo(HaveKey("key1")) - }) + } + Expect(exporterContainer).NotTo(BeNil()) + Expect(exporterContainer.Image).To(Equal(redisReplication.Spec.RedisExporter.Image)) + Expect(exporterContainer.ImagePullPolicy).To(Equal(redisReplication.Spec.RedisExporter.ImagePullPolicy)) }) }) }) diff --git a/pkg/controllers/redisreplication/testdata/full.yaml b/pkg/controllers/redisreplication/testdata/full.yaml new file mode 100644 index 000000000..372b31f47 --- /dev/null +++ b/pkg/controllers/redisreplication/testdata/full.yaml @@ -0,0 +1,38 @@ +--- +apiVersion: redis.redis.opstreelabs.in/v1beta2 +kind: RedisReplication +metadata: + name: redis-replication +spec: + clusterSize: 3 + podSecurityContext: + runAsUser: 1000 + fsGroup: 1000 + kubernetesConfig: + image: quay.io/opstree/redis:v7.0.12 + imagePullPolicy: IfNotPresent + resources: + requests: + cpu: 101m + memory: 128Mi + limits: + cpu: 101m + memory: 128Mi + redisExporter: + enabled: true + image: quay.io/opstree/redis-exporter:v1.44.0 + imagePullPolicy: Always + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 100m + memory: 128Mi + storage: + volumeClaimTemplate: + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 1Gi diff --git a/pkg/controllers/redissentinel/redissentinel_controller_test.go b/pkg/controllers/redissentinel/redissentinel_controller_test.go index b062b9a7f..15dfa7e77 100644 --- a/pkg/controllers/redissentinel/redissentinel_controller_test.go +++ b/pkg/controllers/redissentinel/redissentinel_controller_test.go @@ -2,97 +2,131 @@ package redissentinel import ( "context" - "fmt" - "math/rand" + "os" + "path/filepath" redisv1beta2 "github.com/OT-CONTAINER-KIT/redis-operator/api/v1beta2" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + policyv1 "k8s.io/api/policy/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/apimachinery/pkg/util/yaml" + "sigs.k8s.io/controller-runtime/pkg/client" ) -var _ = Describe("Redis sentinel test", func() { - var ( - redisSentinelCR redisv1beta2.RedisSentinel - redisSentinelCRName string - ) - - BeforeEach(func() { - redisSentinelCRName = fmt.Sprintf("redis-sentinel-%d", rand.Int31()) //nolint:gosec - size := int32(3) - redisSentinelCR = redisv1beta2.RedisSentinel{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "redis.redis.opstreelabs.in/v1beta2", - Kind: "RedisReplication", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: redisSentinelCRName, - Namespace: ns, - }, - Spec: redisv1beta2.RedisSentinelSpec{ - Size: &size, - }, - } - err := k8sClient.Create(context.TODO(), &redisSentinelCR) - Expect(err).Should(Succeed()) - }) +var _ = Describe("Redis Sentinel Controller", func() { + Context("When deploying Redis Sentinel from testdata", func() { + var ( + redisSentinel *redisv1beta2.RedisSentinel + testFile string + ) - Context("When creating a redis sentinel CR", func() { - It("should create a statefulset, service", func() { - sts := &appsv1.StatefulSet{} - svc := &corev1.Service{} + BeforeEach(func() { + testFile = filepath.Join("testdata", "full.yaml") + redisSentinel = &redisv1beta2.RedisSentinel{} + + yamlFile, err := os.ReadFile(testFile) + Expect(err).NotTo(HaveOccurred()) + err = yaml.Unmarshal(yamlFile, redisSentinel) + Expect(err).NotTo(HaveOccurred()) + + redisSentinel.Namespace = ns + + Expect(k8sClient.Create(context.Background(), redisSentinel)).Should(Succeed()) + }) + + AfterEach(func() { + // Clean up resources + Expect(k8sClient.Delete(context.Background(), redisSentinel)).Should(Succeed()) + }) + + It("should create all required resources", func() { + By("verifying the Sentinel StatefulSet is created") + sts := &appsv1.StatefulSet{} Eventually(func() error { - return k8sClient.Get(context.TODO(), types.NamespacedName{ - Name: redisSentinelCRName + "-sentinel", + return k8sClient.Get(context.Background(), types.NamespacedName{ + Name: redisSentinel.Name + "-sentinel", Namespace: ns, }, sts) - }, timeout, interval).Should(BeNil()) - - Expect(*sts.Spec.Replicas).To(BeEquivalentTo(3)) - Expect(sts.Spec.ServiceName).To(Equal(redisSentinelCRName + "-sentinel-headless")) + }, timeout, interval).Should(Succeed()) + By("verifying the Sentinel Service is created") + svc := &corev1.Service{} Eventually(func() error { - return k8sClient.Get(context.TODO(), types.NamespacedName{ - Name: redisSentinelCRName + "-sentinel", + return k8sClient.Get(context.Background(), types.NamespacedName{ + Name: redisSentinel.Name + "-sentinel", Namespace: ns, }, svc) - }, timeout, interval).Should(BeNil()) + }, timeout, interval).Should(Succeed()) - Expect(svc.Labels).To(Equal(map[string]string{ - "app": redisSentinelCRName + "-sentinel", - "redis_setup_type": "sentinel", - "role": "sentinel", - })) + By("verifying the Sentinel headless Service is created") + headlessSvc := &corev1.Service{} + Eventually(func() error { + return k8sClient.Get(context.Background(), types.NamespacedName{ + Name: redisSentinel.Name + "-sentinel-headless", + Namespace: ns, + }, headlessSvc) + }, timeout, interval).Should(Succeed()) + By("verifying the Sentinel additional Service is created") + additionalSvc := &corev1.Service{} Eventually(func() error { - return k8sClient.Get(context.TODO(), types.NamespacedName{ - Name: redisSentinelCRName + "-sentinel-headless", + return k8sClient.Get(context.Background(), types.NamespacedName{ + Name: redisSentinel.Name + "-sentinel-additional", Namespace: ns, - }, svc) - }, timeout, interval).Should(BeNil()) + }, additionalSvc) + }, timeout, interval).Should(Succeed()) + + By("verifying owner references") + for _, obj := range []client.Object{sts, svc, headlessSvc, additionalSvc} { + ownerRefs := obj.GetOwnerReferences() + Expect(ownerRefs).To(HaveLen(1)) + Expect(ownerRefs[0].Name).To(Equal(redisSentinel.Name)) + } + + By("verifying StatefulSet specifications") + Expect(sts.Spec.Template.Spec.SecurityContext).To(Equal(redisSentinel.Spec.PodSecurityContext)) + Expect(sts.Spec.Template.Spec.Containers[0].Image).To(Equal(redisSentinel.Spec.KubernetesConfig.Image)) + Expect(sts.Spec.Template.Spec.Containers[0].ImagePullPolicy).To(Equal(redisSentinel.Spec.KubernetesConfig.ImagePullPolicy)) - Expect(svc.Labels).To(Equal(map[string]string{ - "app": redisSentinelCRName + "-sentinel", + By("verifying Service specifications") + expectedLabels := map[string]string{ + "app": redisSentinel.Name + "-sentinel", "redis_setup_type": "sentinel", "role": "sentinel", - })) + } + Expect(svc.Labels).To(Equal(expectedLabels)) + Expect(headlessSvc.Labels).To(Equal(expectedLabels)) + Expect(additionalSvc.Labels).To(Equal(expectedLabels)) + By("verifying cluster configuration") + Expect(sts.Spec.Replicas).NotTo(BeNil()) + expectedReplicas := int32(3) + Expect(*sts.Spec.Replicas).To(Equal(expectedReplicas)) + + By("verifying Redis Sentinel configuration") + Expect(sts.Spec.ServiceName).To(Equal(redisSentinel.Name + "-sentinel-headless")) + + By("verifying resource requirements") + container := sts.Spec.Template.Spec.Containers[0] + Expect(container.Resources.Limits).To(Equal(redisSentinel.Spec.KubernetesConfig.Resources.Limits)) + Expect(container.Resources.Requests).To(Equal(redisSentinel.Spec.KubernetesConfig.Resources.Requests)) + + By("verifying PodDisruptionBudget configuration") + pdb := &policyv1.PodDisruptionBudget{} Eventually(func() error { - return k8sClient.Get(context.TODO(), types.NamespacedName{ - Name: redisSentinelCRName + "-sentinel-additional", + return k8sClient.Get(context.Background(), types.NamespacedName{ + Name: redisSentinel.Name + "-sentinel", Namespace: ns, - }, svc) - }, timeout, interval).Should(BeNil()) + }, pdb) + }, timeout, interval).Should(Succeed()) - Expect(svc.Labels).To(Equal(map[string]string{ - "app": redisSentinelCRName + "-sentinel", - "redis_setup_type": "sentinel", - "role": "sentinel", - })) + minAvailable := intstr.FromInt(1) + Expect(pdb.Spec.MinAvailable).To(Equal(&minAvailable)) }) }) }) diff --git a/pkg/controllers/redissentinel/testdata/full.yaml b/pkg/controllers/redissentinel/testdata/full.yaml new file mode 100644 index 000000000..f0e9410fe --- /dev/null +++ b/pkg/controllers/redissentinel/testdata/full.yaml @@ -0,0 +1,23 @@ +--- +apiVersion: redis.redis.opstreelabs.in/v1beta2 +kind: RedisSentinel +metadata: + name: redis-sentinel +spec: + clusterSize: 3 + podSecurityContext: + runAsUser: 1000 + fsGroup: 1000 + pdb: + enabled: true + minAvailable: 1 + kubernetesConfig: + image: quay.io/opstree/redis-sentinel:v7.0.12 + imagePullPolicy: IfNotPresent + resources: + requests: + cpu: 101m + memory: 128Mi + limits: + cpu: 101m + memory: 128Mi