diff --git a/k8sutils/redis-standalone_test.go b/k8sutils/redis-standalone_test.go new file mode 100644 index 000000000..394f03845 --- /dev/null +++ b/k8sutils/redis-standalone_test.go @@ -0,0 +1,308 @@ +package k8sutils + +import ( + "os" + "path/filepath" + "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/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/utils/pointer" +) + +func Test_generateRedisStandaloneParams(t *testing.T) { + path := filepath.Join("..", "tests", "testdata", "redis-standalone.yaml") + expected := statefulSetParameters{ + Replicas: pointer.Int32(1), + ClusterMode: false, + NodeConfVolume: false, + // Metadata: metav1.ObjectMeta{ + // Name: "redis-standalone", + // Namespace: "redis", + // Labels: map[string]string{ + // "app": "redis-standalone"}, + // Annotations: map[string]string{ + // "opstreelabs.in.redis": "true"}, + // }, + NodeSelector: map[string]string{ + "node-role.kubernetes.io/infra": "worker"}, + PodSecurityContext: &corev1.PodSecurityContext{ + RunAsUser: pointer.Int64(1000), + FSGroup: pointer.Int64(1000), + }, + PriorityClassName: "high-priority", + Affinity: &corev1.Affinity{ + NodeAffinity: &corev1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ + NodeSelectorTerms: []corev1.NodeSelectorTerm{ + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "node-role.kubernetes.io/infra", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"worker"}, + }, + }, + }, + }, + }, + }, + }, + Tolerations: &[]corev1.Toleration{ + { + Key: "node-role.kubernetes.io/infra", + Operator: corev1.TolerationOpExists, + Effect: corev1.TaintEffectNoSchedule, + }, + { + Key: "node-role.kubernetes.io/infra", + Operator: corev1.TolerationOpExists, + Effect: corev1.TaintEffectNoExecute, + }, + }, + PersistentVolumeClaim: corev1.PersistentVolumeClaim{ + Spec: corev1.PersistentVolumeClaimSpec{ + StorageClassName: pointer.String("standard"), + AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: resource.MustParse("1Gi"), + }, + }, + }, + }, + EnableMetrics: true, + ImagePullSecrets: &[]corev1.LocalObjectReference{{Name: "mysecret"}}, + ExternalConfig: pointer.String("redis-external-config"), + ServiceAccountName: pointer.String("redis-sa"), + TerminationGracePeriodSeconds: pointer.Int64(30), + IgnoreAnnotations: []string{"opstreelabs.in/ignore"}, + } + + data, err := os.ReadFile(path) + if err != nil { + t.Fatalf("Failed to read file %s: %v", path, err) + } + + input := &redisv1beta2.Redis{} + err = yaml.UnmarshalStrict(data, input) + if err != nil { + t.Fatalf("Failed to unmarshal file %s: %v", path, err) + } + + actual := generateRedisStandaloneParams(input) + assert.EqualValues(t, expected, actual, "Expected %+v, got %+v", expected, actual) +} + +func Test_generateRedisStandaloneContainerParams(t *testing.T) { + path := filepath.Join("..", "tests", "testdata", "redis-standalone.yaml") + expected := containerParameters{ + Image: "quay.io/opstree/redis:v7.0.12", + ImagePullPolicy: corev1.PullPolicy("IfNotPresent"), + Resources: &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("101m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("101m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + }, + SecurityContext: &corev1.SecurityContext{ + RunAsUser: pointer.Int64(1000), + RunAsGroup: pointer.Int64(1000), + RunAsNonRoot: pointer.Bool(true), + ReadOnlyRootFilesystem: pointer.Bool(true), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + Add: []corev1.Capability{"NET_BIND_SERVICE"}, + }, + }, + RedisExporterImage: "quay.io/opstree/redis-exporter:v1.44.0", + RedisExporterImagePullPolicy: corev1.PullPolicy("Always"), + RedisExporterResources: &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + }, + RedisExporterEnv: &[]corev1.EnvVar{ + { + Name: "REDIS_EXPORTER_INCL_SYSTEM_METRICS", + Value: "true", + }, + { + Name: "UI_PROPERTIES_FILE_NAME", + ValueFrom: &corev1.EnvVarSource{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "game-demo", + }, + Key: "ui_properties_file_name", + }, + }, + }, + { + Name: "SECRET_USERNAME", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "mysecret", + }, + Key: "username", + }, + }, + }, + }, + Role: "standalone", + EnabledPassword: pointer.Bool(true), + SecretName: pointer.String("redis-secret"), + SecretKey: pointer.String("password"), + PersistenceEnabled: pointer.Bool(true), + TLSConfig: &redisv1beta2.TLSConfig{ + TLSConfig: common.TLSConfig{ + CaKeyFile: "ca.key", + CertKeyFile: "tls.crt", + KeyFile: "tls.key", + Secret: corev1.SecretVolumeSource{ + SecretName: "redis-tls-cert", + }, + }, + }, + ACLConfig: &redisv1beta2.ACLConfig{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "acl-secret", + }, + }, + EnvVars: &[]corev1.EnvVar{ + { + Name: "CUSTOM_ENV_VAR_1", + Value: "custom_value_1", + }, + { + Name: "CUSTOM_ENV_VAR_2", + Value: "custom_value_2", + }, + }, + AdditionalVolume: []corev1.Volume{ + { + Name: "example-config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "example-configmap", + }, + }, + }, + }, + }, + AdditionalMountPath: []corev1.VolumeMount{ + { + MountPath: "/config", + Name: "example-config", + }, + }, + } + + data, err := os.ReadFile(path) + if err != nil { + t.Fatalf("Failed to read file %s: %v", path, err) + } + + input := &redisv1beta2.Redis{} + err = yaml.UnmarshalStrict(data, input) + if err != nil { + t.Fatalf("Failed to unmarshal file %s: %v", path, err) + } + + actual := generateRedisStandaloneContainerParams(input) + assert.EqualValues(t, expected, actual, "Expected %+v, got %+v", expected, actual) +} + +func Test_generateRedisStandaloneInitContainerParams(t *testing.T) { + path := filepath.Join("..", "tests", "testdata", "redis-standalone.yaml") + expected := initContainerParameters{ + Enabled: pointer.Bool(true), + Image: "quay.io/opstree/redis-operator-restore:latest", + ImagePullPolicy: corev1.PullPolicy("Always"), + Resources: &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + }, + Role: "standalone", + Command: []string{"/bin/bash", "-c", "/app/restore.bash"}, + Arguments: []string{"--restore-from", "redis-standalone-restore"}, + PersistenceEnabled: pointer.Bool(true), + AdditionalEnvVariable: &[]corev1.EnvVar{ + { + Name: "CLUSTER_NAME", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "env-secrets", + }, + Key: "CLUSTER_NAME", + }, + }, + }, + { + Name: "CLUSTER_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "env-secrets", + }, + Key: "CLUSTER_NAMESPACE", + }, + }, + }, + }, + AdditionalVolume: []corev1.Volume{ + { + Name: "example-config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "example-configmap", + }, + }, + }, + }, + }, + AdditionalMountPath: []corev1.VolumeMount{ + { + MountPath: "/config", + Name: "example-config", + }, + }, + } + + data, err := os.ReadFile(path) + if err != nil { + t.Fatalf("Failed to read file %s: %v", path, err) + } + + input := &redisv1beta2.Redis{} + err = yaml.UnmarshalStrict(data, input) + if err != nil { + t.Fatalf("Failed to unmarshal file %s: %v", path, err) + } + + actual := generateRedisStandaloneInitContainerParams(input) + assert.EqualValues(t, expected, actual, "Expected %+v, got %+v", expected, actual) +} diff --git a/k8sutils/statefulset.go b/k8sutils/statefulset.go index e63b333b6..4145a7406 100644 --- a/k8sutils/statefulset.go +++ b/k8sutils/statefulset.go @@ -44,7 +44,6 @@ type statefulSetParameters struct { ServiceAccountName *string UpdateStrategy appsv1.StatefulSetUpdateStrategy RecreateStatefulSet bool - InitContainers *[]redisv1beta2.InitContainer TerminationGracePeriodSeconds *int64 IgnoreAnnotations []string } diff --git a/tests/testdata/redis-standalone.yaml b/tests/testdata/redis-standalone.yaml new file mode 100644 index 000000000..a3b351259 --- /dev/null +++ b/tests/testdata/redis-standalone.yaml @@ -0,0 +1,140 @@ +--- +apiVersion: redis.redis.opstreelabs.in/v1beta2 +kind: Redis +metadata: + name: redis-standalone + namespace: redis + labels: + app: redis-standalone + annotations: + opstreelabs.in/redis: "true" +spec: + redisConfig: + additionalRedisConfig: redis-external-config + podSecurityContext: + runAsUser: 1000 + fsGroup: 1000 + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + runAsNonRoot: true + readOnlyRootFilesystem: true + capabilities: + drop: ["ALL"] + add: ["NET_BIND_SERVICE"] + kubernetesConfig: + image: quay.io/opstree/redis:v7.0.12 + imagePullPolicy: IfNotPresent + imagePullSecrets: + - name: mysecret + resources: + requests: + cpu: 101m + memory: 128Mi + limits: + cpu: 101m + memory: 128Mi + redisSecret: + name: redis-secret + key: password + ignoreAnnotations: + - "opstreelabs.in/ignore" + 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 + env: + - name: REDIS_EXPORTER_INCL_SYSTEM_METRICS + value: "true" + - name: UI_PROPERTIES_FILE_NAME + valueFrom: + configMapKeyRef: + name: game-demo + key: ui_properties_file_name + - name: SECRET_USERNAME + valueFrom: + secretKeyRef: + name: mysecret + key: username + storage: + volumeClaimTemplate: + spec: + storageClassName: standard + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 1Gi + volumeMount: + volume: + - name: example-config + configMap: + name: example-configmap + mountPath: + - mountPath: /config + name: example-config + nodeSelector: + node-role.kubernetes.io/infra: worker + priorityClassName: high-priority + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/infra + operator: In + values: + - worker + tolerations: + - key: "node-role.kubernetes.io/infra" + operator: "Exists" + effect: "NoSchedule" + - key: "node-role.kubernetes.io/infra" + operator: "Exists" + effect: "NoExecute" + serviceAccountName: redis-sa + terminationGracePeriodSeconds: 30 + acl: + secret: + secretName: acl-secret + TLS: + ca: ca.key + cert: tls.crt + key: tls.key + secret: + secretName: redis-tls-cert + env: + - name: CUSTOM_ENV_VAR_1 + value: "custom_value_1" + - name: CUSTOM_ENV_VAR_2 + value: "custom_value_2" + initContainer: + enabled: true + image: quay.io/opstree/redis-operator-restore:latest + imagePullPolicy: Always + command: ["/bin/bash", "-c", "/app/restore.bash"] + args: ["--restore-from", "redis-standalone-restore"] + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 100m + memory: 128Mi + env: + - name: CLUSTER_NAME + valueFrom: + secretKeyRef: + name: env-secrets + key: CLUSTER_NAME + - name: CLUSTER_NAMESPACE + valueFrom: + secretKeyRef: + name: env-secrets + key: CLUSTER_NAMESPACE