From a5fafe5bef129f327b3e30005fca1a0571171e57 Mon Sep 17 00:00:00 2001 From: Shubham Gupta Date: Wed, 4 Oct 2023 21:53:45 +0530 Subject: [PATCH] redis_test - unit test Signed-off-by: Shubham Gupta --- go.mod | 3 +- k8sutils/services_test.go | 230 ++++++++++++++++++++ k8sutils/statefulset_test.go | 392 +++++++++++++++++++++++++++++++++++ 3 files changed, 624 insertions(+), 1 deletion(-) create mode 100644 k8sutils/services_test.go create mode 100644 k8sutils/statefulset_test.go diff --git a/go.mod b/go.mod index 32084681a..161af7150 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.27.10 github.com/pkg/errors v0.9.1 + github.com/stretchr/testify v1.8.4 k8s.io/api v0.28.2 k8s.io/apimachinery v0.28.2 k8s.io/client-go v0.28.2 @@ -46,12 +47,12 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nxadm/tail v1.4.8 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.16.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/testify v1.8.4 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.25.0 // indirect golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect diff --git a/k8sutils/services_test.go b/k8sutils/services_test.go new file mode 100644 index 000000000..035583779 --- /dev/null +++ b/k8sutils/services_test.go @@ -0,0 +1,230 @@ +package k8sutils + +import ( + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" +) + +func TestGenerateServiceDef(t *testing.T) { + tests := []struct { + name string + serviceMeta metav1.ObjectMeta + enableMetrics bool + headless bool + serviceType string + expected *corev1.Service + }{ + { + name: "Test sentinel with ClusterIP service type", + serviceMeta: metav1.ObjectMeta{ + Name: "test-service", + Labels: map[string]string{ + "role": "sentinel", + }, + }, + enableMetrics: false, + headless: false, + serviceType: "ClusterIP", + expected: &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-service", + Labels: map[string]string{ + "role": "sentinel", + }, + OwnerReferences: []metav1.OwnerReference{ + {}, + }, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "sentinel-client", + Port: sentinelPort, + TargetPort: intstr.FromInt(int(sentinelPort)), + Protocol: corev1.ProtocolTCP, + }, + }, + Selector: map[string]string{"role": "sentinel"}, + ClusterIP: "", + Type: corev1.ServiceTypeClusterIP, + }, + }, + }, + { + name: "Test sentinel with headless service", + serviceMeta: metav1.ObjectMeta{ + Name: "test-service", + Labels: map[string]string{ + "role": "sentinel", + }, + }, + enableMetrics: false, + headless: true, + serviceType: "ClusterIP", + expected: &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-service", + Labels: map[string]string{ + "role": "sentinel", + }, + OwnerReferences: []metav1.OwnerReference{ + {}, + }, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "sentinel-client", + Port: sentinelPort, + TargetPort: intstr.FromInt(int(sentinelPort)), + Protocol: corev1.ProtocolTCP, + }, + }, + Selector: map[string]string{"role": "sentinel"}, + ClusterIP: "None", + Type: corev1.ServiceTypeClusterIP, + }, + }, + }, + { + name: "Test redis with ClusterIP service type", + serviceMeta: metav1.ObjectMeta{ + Name: "test-redis-service", + Labels: map[string]string{ + "role": "redis", + }, + }, + enableMetrics: false, + headless: false, + serviceType: "ClusterIP", + expected: &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-redis-service", + Labels: map[string]string{ + "role": "redis", + }, + OwnerReferences: []metav1.OwnerReference{ + {}, + }, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "redis-client", + Port: redisPort, + TargetPort: intstr.FromInt(int(redisPort)), + Protocol: corev1.ProtocolTCP, + }, + }, + Selector: map[string]string{"role": "redis"}, + ClusterIP: "", + Type: corev1.ServiceTypeClusterIP, + }, + }, + }, + { + name: "Test redis with headless service", + serviceMeta: metav1.ObjectMeta{ + Name: "test-redis-headless-service", + Labels: map[string]string{ + "role": "redis", + }, + }, + enableMetrics: false, + headless: true, + serviceType: "ClusterIP", + expected: &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-redis-headless-service", + Labels: map[string]string{ + "role": "redis", + }, + OwnerReferences: []metav1.OwnerReference{ + {}, + }, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "redis-client", + Port: redisPort, + TargetPort: intstr.FromInt(int(redisPort)), + Protocol: corev1.ProtocolTCP, + }, + }, + Selector: map[string]string{"role": "redis"}, + ClusterIP: "None", + Type: corev1.ServiceTypeClusterIP, + }, + }, + }, + { + name: "Test redis with ClusterIP service type and metrics enabled", + serviceMeta: metav1.ObjectMeta{ + Name: "test-redis-metrics-service", + Labels: map[string]string{ + "role": "redis", + }, + }, + enableMetrics: true, + headless: false, + serviceType: "ClusterIP", + expected: &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-redis-metrics-service", + Labels: map[string]string{ + "role": "redis", + }, + OwnerReferences: []metav1.OwnerReference{ + {}, + }, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "redis-client", + Port: redisPort, + TargetPort: intstr.FromInt(int(redisPort)), + Protocol: corev1.ProtocolTCP, + }, + *enableMetricsPort(), + }, + Selector: map[string]string{"role": "redis"}, + ClusterIP: "", + Type: corev1.ServiceTypeClusterIP, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := generateServiceDef(tt.serviceMeta, tt.enableMetrics, metav1.OwnerReference{}, tt.headless, tt.serviceType) + assert.Equal(t, tt.expected, actual) + }) + } +} diff --git a/k8sutils/statefulset_test.go b/k8sutils/statefulset_test.go new file mode 100644 index 000000000..1bb954166 --- /dev/null +++ b/k8sutils/statefulset_test.go @@ -0,0 +1,392 @@ +package k8sutils + +import ( + "path" + "testing" + + common "github.com/OT-CONTAINER-KIT/redis-operator/api" + redisv1beta2 "github.com/OT-CONTAINER-KIT/redis-operator/api/v1beta2" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + "k8s.io/utils/pointer" +) + +func TestGetVolumeMount(t *testing.T) { + tests := []struct { + name string + persistenceEnabled *bool + clusterMode bool + nodeConfVolume bool + externalConfig *string + mountpath []corev1.VolumeMount + tlsConfig *redisv1beta2.TLSConfig + aclConfig *redisv1beta2.ACLConfig + expectedMounts []corev1.VolumeMount + }{ + { + name: "1. All false or nil", + persistenceEnabled: nil, + clusterMode: false, + nodeConfVolume: false, + externalConfig: nil, + mountpath: []corev1.VolumeMount{}, + tlsConfig: nil, + aclConfig: nil, + expectedMounts: []corev1.VolumeMount{}, + }, + { + name: "2. Persistence enabled with cluster mode and node conf", + persistenceEnabled: pointer.Bool(true), + clusterMode: true, + nodeConfVolume: true, + externalConfig: nil, + mountpath: []corev1.VolumeMount{}, + tlsConfig: nil, + aclConfig: nil, + expectedMounts: []corev1.VolumeMount{ + { + Name: "persistent-volume", + MountPath: "/data", + }, + { + Name: "node-conf", + MountPath: "/node-conf", + }, + }, + }, + { + name: "3. Persistence enabled with cluster mode and external config", + persistenceEnabled: pointer.Bool(true), + clusterMode: true, + nodeConfVolume: false, + externalConfig: pointer.String("some-config"), + mountpath: []corev1.VolumeMount{}, + tlsConfig: nil, + aclConfig: nil, + expectedMounts: []corev1.VolumeMount{ + { + Name: "persistent-volume", + MountPath: "/data", + }, + { + Name: "external-config", + MountPath: "/etc/redis/external.conf.d", + }, + }, + }, + { + name: "4. Persistence enabled, cluster mode false, node conf true, no tls/acl, with mountpath", + persistenceEnabled: pointer.Bool(true), + clusterMode: false, + nodeConfVolume: true, + externalConfig: nil, + mountpath: []corev1.VolumeMount{ + { + Name: "additional-mount", + MountPath: "/additional", + }, + }, + tlsConfig: nil, + aclConfig: nil, + expectedMounts: []corev1.VolumeMount{{Name: "persistent-volume", MountPath: "/data"}, {Name: "additional-mount", MountPath: "/additional"}}, + }, + { + name: "5. Only tls enabled", + persistenceEnabled: nil, + clusterMode: false, + nodeConfVolume: false, + externalConfig: nil, + mountpath: []corev1.VolumeMount{}, + tlsConfig: &redisv1beta2.TLSConfig{}, + aclConfig: nil, + expectedMounts: []corev1.VolumeMount{{Name: "tls-certs", MountPath: "/tls", ReadOnly: true}}, + }, + { + name: "6. Only acl enabled", + persistenceEnabled: nil, + clusterMode: false, + nodeConfVolume: false, + externalConfig: nil, + mountpath: []corev1.VolumeMount{}, + tlsConfig: nil, + aclConfig: &redisv1beta2.ACLConfig{}, + expectedMounts: []corev1.VolumeMount{{Name: "acl-secret", MountPath: "/etc/redis/user.acl", SubPath: "user.acl"}}, + }, + { + name: "7. Everything enabled except externalConfig", + persistenceEnabled: pointer.Bool(true), + clusterMode: true, + nodeConfVolume: true, + externalConfig: nil, + mountpath: []corev1.VolumeMount{ + { + Name: "additional-mount", + MountPath: "/additional", + }, + }, + tlsConfig: &redisv1beta2.TLSConfig{}, + aclConfig: &redisv1beta2.ACLConfig{}, + expectedMounts: []corev1.VolumeMount{ + {Name: "persistent-volume", MountPath: "/data"}, + {Name: "node-conf", MountPath: "/node-conf"}, + {Name: "tls-certs", MountPath: "/tls", ReadOnly: true}, + {Name: "acl-secret", MountPath: "/etc/redis/user.acl", SubPath: "user.acl"}, + {Name: "additional-mount", MountPath: "/additional"}, + }, + }, + { + name: "8. Only externalConfig enabled", + persistenceEnabled: nil, + clusterMode: false, + nodeConfVolume: false, + externalConfig: pointer.String("some-config"), + mountpath: []corev1.VolumeMount{}, + tlsConfig: nil, + aclConfig: nil, + expectedMounts: []corev1.VolumeMount{{Name: "external-config", MountPath: "/etc/redis/external.conf.d"}}, + }, + { + name: "9. Persistence enabled, cluster mode true, node conf true, only acl enabled", + persistenceEnabled: pointer.Bool(true), + clusterMode: true, + nodeConfVolume: true, + externalConfig: nil, + mountpath: []corev1.VolumeMount{}, + tlsConfig: nil, + aclConfig: &redisv1beta2.ACLConfig{}, + expectedMounts: []corev1.VolumeMount{ + {Name: "persistent-volume", MountPath: "/data"}, + {Name: "node-conf", MountPath: "/node-conf"}, + {Name: "acl-secret", MountPath: "/etc/redis/user.acl", SubPath: "user.acl"}, + }, + }, + { + name: "10. Persistence enabled, cluster mode false, node conf false, only tls enabled with mountpath", + persistenceEnabled: pointer.Bool(true), + clusterMode: false, + nodeConfVolume: false, + externalConfig: nil, + mountpath: []corev1.VolumeMount{ + { + Name: "additional-mount", + MountPath: "/additional", + }, + }, + tlsConfig: &redisv1beta2.TLSConfig{}, + aclConfig: nil, + expectedMounts: []corev1.VolumeMount{{Name: "persistent-volume", MountPath: "/data"}, {Name: "tls-certs", MountPath: "/tls", ReadOnly: true}, {Name: "additional-mount", MountPath: "/additional"}}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getVolumeMount("persistent-volume", tt.persistenceEnabled, tt.clusterMode, tt.nodeConfVolume, tt.externalConfig, tt.mountpath, tt.tlsConfig, tt.aclConfig) + assert.ElementsMatch(t, tt.expectedMounts, got) + }) + } +} + +func TestGenerateTLSEnvironmentVariables(t *testing.T) { + tlsConfig := &redisv1beta2.TLSConfig{ + TLSConfig: common.TLSConfig{ + CaKeyFile: "test_ca.crt", + CertKeyFile: "test_tls.crt", + KeyFile: "test_tls.key", + }, + } + + envVars := GenerateTLSEnvironmentVariables(tlsConfig) + + expectedEnvVars := []corev1.EnvVar{ + { + Name: "TLS_MODE", + Value: "true", + }, + { + Name: "REDIS_TLS_CA_KEY", + Value: path.Join("/tls/", "test_ca.crt"), + }, + { + Name: "REDIS_TLS_CERT", + Value: path.Join("/tls/", "test_tls.crt"), + }, + { + Name: "REDIS_TLS_CERT_KEY", + Value: path.Join("/tls/", "test_tls.key"), + }, + } + assert.ElementsMatch(t, envVars, expectedEnvVars, "EnvVars generated for TLS config are not as expected") +} + +func TestGetEnvironmentVariables(t *testing.T) { + tests := []struct { + name string + role string + enabledMetric bool + enabledPassword *bool + secretName *string + secretKey *string + persistenceEnabled *bool + exporterEnvVar *[]corev1.EnvVar + tlsConfig *redisv1beta2.TLSConfig + aclConfig *redisv1beta2.ACLConfig + envVar *[]corev1.EnvVar + expectedEnvironment []corev1.EnvVar + }{ + { + name: "Test with role sentinel, metrics true, password true, persistence true, exporter env, tls enabled, acl enabled and env var", + role: "sentinel", + enabledMetric: true, + enabledPassword: pointer.Bool(true), + secretName: pointer.String("test-secret"), + secretKey: pointer.String("test-key"), + persistenceEnabled: pointer.Bool(true), + exporterEnvVar: &[]corev1.EnvVar{ + {Name: "TEST_EXPORTER_ENV", Value: "exporter-value"}, + }, + tlsConfig: &redisv1beta2.TLSConfig{ + TLSConfig: common.TLSConfig{ + CaKeyFile: "test_ca.crt", + CertKeyFile: "test_tls.crt", + KeyFile: "test_tls.key", + Secret: corev1.SecretVolumeSource{ + SecretName: "tls-secret", + }, + }, + }, + aclConfig: &redisv1beta2.ACLConfig{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "acl-secret", + }, + }, + envVar: &[]corev1.EnvVar{ + {Name: "TEST_ENV", Value: "test-value"}, + }, + expectedEnvironment: []corev1.EnvVar{ + {Name: "ACL_MODE", Value: "true"}, + {Name: "PERSISTENCE_ENABLED", Value: "true"}, + {Name: "REDIS_ADDR", Value: "redis://localhost:26379"}, + {Name: "REDIS_EXPORTER_SKIP_TLS_VERIFICATION", Value: "true"}, + {Name: "REDIS_EXPORTER_TLS_CA_CERT_FILE", Value: "/tls/ca.crt"}, + {Name: "REDIS_EXPORTER_TLS_CLIENT_CERT_FILE", Value: "/tls/tls.crt"}, + {Name: "REDIS_EXPORTER_TLS_CLIENT_KEY_FILE", Value: "/tls/tls.key"}, + {Name: "TLS_MODE", Value: "true"}, + {Name: "REDIS_TLS_CA_KEY", Value: path.Join("/tls/", "test_ca.crt")}, + {Name: "REDIS_TLS_CERT", Value: path.Join("/tls/", "test_tls.crt")}, + {Name: "REDIS_TLS_CERT_KEY", Value: path.Join("/tls/", "test_tls.key")}, + {Name: "REDIS_PASSWORD", ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "test-secret", + }, + Key: "test-key", + }, + }}, + {Name: "SERVER_MODE", Value: "sentinel"}, + {Name: "SETUP_MODE", Value: "sentinel"}, + {Name: "TEST_ENV", Value: "test-value"}, + {Name: "TEST_EXPORTER_ENV", Value: "exporter-value"}, + }, + }, + { + name: "Test with role redis, metrics false, password nil, persistence nil, exporter nil, tls nil, acl nil and nil env var", + role: "redis", + enabledMetric: false, + enabledPassword: nil, + secretName: nil, + secretKey: nil, + persistenceEnabled: nil, + exporterEnvVar: nil, + tlsConfig: nil, + aclConfig: nil, + envVar: nil, + expectedEnvironment: []corev1.EnvVar{ + {Name: "REDIS_ADDR", Value: "redis://localhost:6379"}, + {Name: "SERVER_MODE", Value: "redis"}, + {Name: "SETUP_MODE", Value: "redis"}, + }, + }, + { + name: "Test with role redis, metrics false, password nil, persistence false, exporter nil, tls nil, acl nil and nil env var", + role: "sentinel", + enabledMetric: false, + enabledPassword: nil, + secretName: nil, + secretKey: nil, + persistenceEnabled: pointer.Bool(false), + exporterEnvVar: nil, + tlsConfig: nil, + aclConfig: nil, + envVar: nil, + expectedEnvironment: []corev1.EnvVar{ + {Name: "REDIS_ADDR", Value: "redis://localhost:26379"}, + {Name: "SERVER_MODE", Value: "sentinel"}, + {Name: "SETUP_MODE", Value: "sentinel"}, + }, + }, + { + name: "Test with role cluster, metrics true, password true, persistence true, exporter env, tls nil, acl enabled and env var", + role: "cluster", + enabledMetric: true, + enabledPassword: pointer.Bool(true), + secretName: pointer.String("test-secret"), + secretKey: pointer.String("test-key"), + persistenceEnabled: pointer.Bool(true), + exporterEnvVar: &[]corev1.EnvVar{ + {Name: "TEST_EXPORTER_ENV", Value: "exporter-value"}, + }, + tlsConfig: nil, + aclConfig: &redisv1beta2.ACLConfig{}, + envVar: &[]corev1.EnvVar{ + {Name: "TEST_ENV", Value: "test-value"}, + }, + expectedEnvironment: []corev1.EnvVar{ + {Name: "ACL_MODE", Value: "true"}, + {Name: "PERSISTENCE_ENABLED", Value: "true"}, + {Name: "REDIS_ADDR", Value: "redis://localhost:6379"}, + {Name: "REDIS_PASSWORD", ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "test-secret", + }, + Key: "test-key", + }, + }}, + {Name: "SERVER_MODE", Value: "cluster"}, + {Name: "SETUP_MODE", Value: "cluster"}, + {Name: "TEST_ENV", Value: "test-value"}, + {Name: "TEST_EXPORTER_ENV", Value: "exporter-value"}, + }, + }, + { + name: "Test with cluster role and only metrics enabled", + role: "cluster", + enabledMetric: true, + enabledPassword: nil, + secretName: nil, + secretKey: nil, + persistenceEnabled: nil, + exporterEnvVar: &[]corev1.EnvVar{ + {Name: "TEST_EXPORTER_ENV", Value: "exporter-value"}, + }, + tlsConfig: nil, + aclConfig: nil, + envVar: nil, + expectedEnvironment: []corev1.EnvVar{ + {Name: "REDIS_ADDR", Value: "redis://localhost:6379"}, + {Name: "SERVER_MODE", Value: "cluster"}, + {Name: "SETUP_MODE", Value: "cluster"}, + {Name: "TEST_EXPORTER_ENV", Value: "exporter-value"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actualEnvironment := getEnvironmentVariables(tt.role, tt.enabledMetric, tt.enabledPassword, tt.secretName, + tt.secretKey, tt.persistenceEnabled, tt.exporterEnvVar, tt.tlsConfig, tt.aclConfig, tt.envVar) + + assert.ElementsMatch(t, tt.expectedEnvironment, actualEnvironment) + }) + } +}