Skip to content

Commit

Permalink
test: enhance Redis controllers' test cases with testdata and validat…
Browse files Browse the repository at this point in the history
…ion checks (#1181)

* test: enhance Redis controllers' test cases with testdata and validation checks

- Updated Redis, RedisCluster, RedisReplication, and RedisSentinel controller tests to utilize YAML testdata files for improved clarity and maintainability.
- Added validation checks for resource creation, owner references, and specifications in the respective test cases.
- Introduced new testdata files for Redis and RedisCluster to streamline test setup and ensure consistent testing across different configurations.
- Enhanced error handling and logging for better debugging during test execution.

This update improves the robustness and reliability of the Redis operator's testing framework.

Signed-off-by: drivebyer <[email protected]>

* fix lint

Signed-off-by: yangw <[email protected]>

---------

Signed-off-by: drivebyer <[email protected]>
Signed-off-by: yangw <[email protected]>
  • Loading branch information
drivebyer authored Dec 21, 2024
1 parent 1db22ae commit c6df0b3
Show file tree
Hide file tree
Showing 8 changed files with 486 additions and 238 deletions.
124 changes: 72 additions & 52 deletions pkg/controllers/redis/redis_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
})
})
})
37 changes: 37 additions & 0 deletions pkg/controllers/redis/testdata/full.yaml
Original file line number Diff line number Diff line change
@@ -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
166 changes: 94 additions & 72 deletions pkg/controllers/rediscluster/rediscluster_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
})
})
})
Loading

0 comments on commit c6df0b3

Please sign in to comment.