Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 2 commits into from
Dec 21, 2024
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
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
Loading