diff --git a/k8sutils/redis-cluster.go b/k8sutils/redis-cluster.go index f12c5bd84..d3a523cc2 100644 --- a/k8sutils/redis-cluster.go +++ b/k8sutils/redis-cluster.go @@ -27,7 +27,6 @@ type RedisClusterService struct { // generateRedisClusterParams generates Redis cluster information func generateRedisClusterParams(cr *redisv1beta2.RedisCluster, replicas int32, externalConfig *string, params RedisClusterSTS) statefulSetParameters { res := statefulSetParameters{ - Metadata: cr.ObjectMeta, Replicas: &replicas, ClusterMode: true, NodeConfVolume: cr.Spec.Storage.NodeConfVolume, diff --git a/k8sutils/redis-cluster_test.go b/k8sutils/redis-cluster_test.go new file mode 100644 index 000000000..2e1d1ddb3 --- /dev/null +++ b/k8sutils/redis-cluster_test.go @@ -0,0 +1,514 @@ +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_generateRedisClusterParams(t *testing.T) { + path := filepath.Join("..", "tests", "testdata", "redis-cluster.yaml") + + expectedLeaderSTS := statefulSetParameters{ + Replicas: pointer.Int32(3), + ClusterMode: true, + NodeConfVolume: true, + 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{"redisLeader"}, + }, + }, + }, + }, + }, + }, + }, + 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"), + }, + }, + }, + }, + NodeConfPersistentVolumeClaim: 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-leader"), + ServiceAccountName: pointer.String("redis-sa"), + IgnoreAnnotations: []string{"opstreelabs.in/ignore"}, + } + expectedFollowerSTS := statefulSetParameters{ + Replicas: pointer.Int32(3), + ClusterMode: true, + NodeConfVolume: true, + 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{"redisFollower"}, + }, + }, + }, + }, + }, + }, + }, + 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"), + }, + }, + }, + }, + NodeConfPersistentVolumeClaim: 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-follower"), + ServiceAccountName: pointer.String("redis-sa"), + 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.RedisCluster{} + err = yaml.UnmarshalStrict(data, input) + if err != nil { + t.Fatalf("Failed to unmarshal file %s: %v", path, err) + } + + actualLeaderSTS := generateRedisClusterParams(input, *input.Spec.Size, input.Spec.RedisLeader.RedisConfig.AdditionalRedisConfig, RedisClusterSTS{ + RedisStateFulType: "leader", + ExternalConfig: input.Spec.RedisLeader.RedisConfig.AdditionalRedisConfig, + SecurityContext: input.Spec.RedisLeader.SecurityContext, + Affinity: input.Spec.RedisLeader.Affinity, + TerminationGracePeriodSeconds: input.Spec.RedisLeader.TerminationGracePeriodSeconds, + ReadinessProbe: input.Spec.RedisLeader.ReadinessProbe, + LivenessProbe: input.Spec.RedisLeader.LivenessProbe, + NodeSelector: input.Spec.RedisLeader.NodeSelector, + Tolerations: input.Spec.RedisLeader.Tolerations, + }) + assert.EqualValues(t, expectedLeaderSTS, actualLeaderSTS, "Expected %+v, got %+v", expectedLeaderSTS, actualLeaderSTS) + + actualFollowerSTS := generateRedisClusterParams(input, *input.Spec.Size, input.Spec.RedisFollower.RedisConfig.AdditionalRedisConfig, RedisClusterSTS{ + RedisStateFulType: "follower", + ExternalConfig: input.Spec.RedisFollower.RedisConfig.AdditionalRedisConfig, + SecurityContext: input.Spec.RedisFollower.SecurityContext, + Affinity: input.Spec.RedisFollower.Affinity, + TerminationGracePeriodSeconds: input.Spec.RedisFollower.TerminationGracePeriodSeconds, + ReadinessProbe: input.Spec.RedisFollower.ReadinessProbe, + LivenessProbe: input.Spec.RedisFollower.LivenessProbe, + NodeSelector: input.Spec.RedisFollower.NodeSelector, + Tolerations: input.Spec.RedisFollower.Tolerations, + }) + assert.EqualValues(t, expectedFollowerSTS, actualFollowerSTS, "Expected %+v, got %+v", expectedFollowerSTS, actualFollowerSTS) +} + +func Test_generateRedisClusterContainerParams(t *testing.T) { + path := filepath.Join("..", "tests", "testdata", "redis-cluster.yaml") + expectedLeaderContainer := 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: "cluster", + 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", + }, + }, + } + + expectedFollowerContainer := 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: "cluster", + 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.RedisCluster{} + err = yaml.UnmarshalStrict(data, input) + if err != nil { + t.Fatalf("Failed to unmarshal file %s: %v", path, err) + } + + actualLeaderContainer := generateRedisClusterContainerParams(input, input.Spec.RedisLeader.SecurityContext, input.Spec.RedisLeader.ReadinessProbe, input.Spec.RedisLeader.LivenessProbe) + assert.EqualValues(t, expectedLeaderContainer, actualLeaderContainer, "Expected %+v, got %+v", expectedLeaderContainer, actualLeaderContainer) + + actualFollowerContainer := generateRedisClusterContainerParams(input, input.Spec.RedisFollower.SecurityContext, input.Spec.RedisFollower.ReadinessProbe, input.Spec.RedisFollower.LivenessProbe) + assert.EqualValues(t, expectedFollowerContainer, actualFollowerContainer, "Expected %+v, got %+v", expectedFollowerContainer, actualFollowerContainer) +} + +func Test_generateRedisClusterInitContainerParams(t *testing.T) { + path := filepath.Join("..", "tests", "testdata", "redis-cluster.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: "cluster", + Command: []string{"/bin/bash", "-c", "/app/restore.bash"}, + Arguments: []string{"--restore-from", "redis-cluster-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.RedisCluster{} + err = yaml.UnmarshalStrict(data, input) + if err != nil { + t.Fatalf("Failed to unmarshal file %s: %v", path, err) + } + + actual := generateRedisClusterInitContainerParams(input) + assert.EqualValues(t, expected, actual, "Expected %+v, got %+v", expected, actual) +} diff --git a/k8sutils/redis-replication_test.go b/k8sutils/redis-replication_test.go new file mode 100644 index 000000000..8b4a2e7f9 --- /dev/null +++ b/k8sutils/redis-replication_test.go @@ -0,0 +1,300 @@ +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_generateRedisReplicationParams(t *testing.T) { + path := filepath.Join("..", "tests", "testdata", "redis-replication.yaml") + expected := statefulSetParameters{ + Replicas: pointer.Int32(3), + ClusterMode: false, + NodeConfVolume: false, + 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.RedisReplication{} + err = yaml.UnmarshalStrict(data, input) + if err != nil { + t.Fatalf("Failed to unmarshal file %s: %v", path, err) + } + + actual := generateRedisReplicationParams(input) + assert.EqualValues(t, expected, actual, "Expected %+v, got %+v", expected, actual) +} + +func Test_generateRedisReplicationContainerParams(t *testing.T) { + path := filepath.Join("..", "tests", "testdata", "redis-replication.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: "replication", + 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.RedisReplication{} + err = yaml.UnmarshalStrict(data, input) + if err != nil { + t.Fatalf("Failed to unmarshal file %s: %v", path, err) + } + + actual := generateRedisReplicationContainerParams(input) + assert.EqualValues(t, expected, actual, "Expected %+v, got %+v", expected, actual) +} + +func Test_generateRedisReplicationInitContainerParams(t *testing.T) { + path := filepath.Join("..", "tests", "testdata", "redis-replication.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: "replication", + Command: []string{"/bin/bash", "-c", "/app/restore.bash"}, + Arguments: []string{"--restore-from", "redis-replication-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.RedisReplication{} + err = yaml.UnmarshalStrict(data, input) + if err != nil { + t.Fatalf("Failed to unmarshal file %s: %v", path, err) + } + + actual := generateRedisReplicationInitContainerParams(input) + assert.EqualValues(t, expected, actual, "Expected %+v, got %+v", expected, actual) +} diff --git a/k8sutils/redis-sentinel.go b/k8sutils/redis-sentinel.go index a4b7260ec..da39bd284 100644 --- a/k8sutils/redis-sentinel.go +++ b/k8sutils/redis-sentinel.go @@ -87,7 +87,6 @@ func (service RedisSentinelSTS) CreateRedisSentinelSetup(ctx context.Context, cr func generateRedisSentinelParams(cr *redisv1beta2.RedisSentinel, replicas int32, externalConfig *string, affinity *corev1.Affinity) statefulSetParameters { res := statefulSetParameters{ - Metadata: cr.ObjectMeta, Replicas: &replicas, ClusterMode: false, NodeConfVolume: false, diff --git a/k8sutils/redis-sentinel_test.go b/k8sutils/redis-sentinel_test.go new file mode 100644 index 000000000..883bc78d5 --- /dev/null +++ b/k8sutils/redis-sentinel_test.go @@ -0,0 +1,247 @@ +package k8sutils + +import ( + "context" + "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_generateRedisSentinelParams(t *testing.T) { + path := filepath.Join("..", "tests", "testdata", "redis-sentinel.yaml") + expected := statefulSetParameters{ + Replicas: pointer.Int32(3), + ClusterMode: false, + NodeConfVolume: false, + 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, + }, + }, + EnableMetrics: true, + ImagePullSecrets: &[]corev1.LocalObjectReference{{Name: "mysecret"}}, + 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.RedisSentinel{} + err = yaml.UnmarshalStrict(data, input) + if err != nil { + t.Fatalf("Failed to unmarshal file %s: %v", path, err) + } + + actual := generateRedisSentinelParams(input, *input.Spec.Size, nil, input.Spec.Affinity) + assert.EqualValues(t, expected, actual, "Expected %+v, got %+v", expected, actual) +} + +func Test_generateRedisSentinelContainerParams(t *testing.T) { + path := filepath.Join("..", "tests", "testdata", "redis-sentinel.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: "sentinel", + EnabledPassword: pointer.Bool(true), + SecretName: pointer.String("redis-secret"), + SecretKey: pointer.String("password"), + TLSConfig: &redisv1beta2.TLSConfig{ + TLSConfig: common.TLSConfig{ + CaKeyFile: "ca.key", + CertKeyFile: "tls.crt", + KeyFile: "tls.key", + Secret: corev1.SecretVolumeSource{ + SecretName: "redis-tls-cert", + }, + }, + }, + AdditionalEnvVariable: &[]corev1.EnvVar{}, + EnvVars: &[]corev1.EnvVar{ + { + Name: "CUSTOM_ENV_VAR_1", + Value: "custom_value_1", + }, + { + Name: "CUSTOM_ENV_VAR_2", + Value: "custom_value_2", + }, + }, + } + + data, err := os.ReadFile(path) + if err != nil { + t.Fatalf("Failed to read file %s: %v", path, err) + } + + input := &redisv1beta2.RedisSentinel{} + err = yaml.UnmarshalStrict(data, input) + if err != nil { + t.Fatalf("Failed to unmarshal file %s: %v", path, err) + } + + actual := generateRedisSentinelContainerParams(context.TODO(), input, nil, nil) + assert.EqualValues(t, expected, actual, "Expected %+v, got %+v", expected, actual) +} + +func Test_generateRedisSentinelInitContainerParams(t *testing.T) { + path := filepath.Join("..", "tests", "testdata", "redis-sentinel.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: "sentinel", + Command: []string{"/bin/bash", "-c", "/app/restore.bash"}, + Arguments: []string{"--restore-from", "redis-sentinel-restore"}, + 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", + }, + }, + }, + }, + } + + data, err := os.ReadFile(path) + if err != nil { + t.Fatalf("Failed to read file %s: %v", path, err) + } + + input := &redisv1beta2.RedisSentinel{} + err = yaml.UnmarshalStrict(data, input) + if err != nil { + t.Fatalf("Failed to unmarshal file %s: %v", path, err) + } + + actual := generateRedisSentinelInitContainerParams(input) + assert.EqualValues(t, expected, actual, "Expected %+v, got %+v", expected, actual) +} diff --git a/k8sutils/statefulset.go b/k8sutils/statefulset.go index 4145a7406..16e76395c 100644 --- a/k8sutils/statefulset.go +++ b/k8sutils/statefulset.go @@ -30,7 +30,6 @@ type statefulSetParameters struct { Replicas *int32 ClusterMode bool NodeConfVolume bool - Metadata metav1.ObjectMeta NodeSelector map[string]string PodSecurityContext *corev1.PodSecurityContext PriorityClassName string diff --git a/tests/testdata/redis-cluster.yaml b/tests/testdata/redis-cluster.yaml new file mode 100644 index 000000000..b107d6508 --- /dev/null +++ b/tests/testdata/redis-cluster.yaml @@ -0,0 +1,177 @@ +--- +apiVersion: redis.redis.opstreelabs.in/v1beta2 +kind: Redis +metadata: + name: redis-cluster + namespace: redis + labels: + app: redis-cluster + annotations: + opstreelabs.in/redis: "true" +spec: + clusterSize: 3 + 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" + podSecurityContext: + runAsUser: 1000 + fsGroup: 1000 + redisLeader: + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + runAsNonRoot: true + readOnlyRootFilesystem: true + capabilities: + drop: [ "ALL" ] + add: [ "NET_BIND_SERVICE" ] + replicas: 3 + redisConfig: + additionalRedisConfig: redis-external-config-leader + tolerations: + - key: "node-role.kubernetes.io/infra" + operator: "Exists" + effect: "NoSchedule" + - key: "node-role.kubernetes.io/infra" + operator: "Exists" + effect: "NoExecute" + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/infra + operator: In + values: + - redisLeader + redisFollower: + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + runAsNonRoot: true + readOnlyRootFilesystem: true + capabilities: + drop: [ "ALL" ] + add: [ "NET_BIND_SERVICE" ] + replicas: 3 + redisConfig: + additionalRedisConfig: redis-external-config-follower + tolerations: + - key: "node-role.kubernetes.io/infra" + operator: "Exists" + effect: "NoSchedule" + - key: "node-role.kubernetes.io/infra" + operator: "Exists" + effect: "NoExecute" + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/infra + operator: In + values: + - redisFollower + 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 + persistenceEnabled: true + storage: + nodeConfVolume: true + nodeConfVolumeClaimTemplate: + spec: + storageClassName: standard + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 1Gi + 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 + priorityClassName: high-priority + serviceAccountName: redis-sa + 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-cluster-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 \ No newline at end of file diff --git a/tests/testdata/redis-replication.yaml b/tests/testdata/redis-replication.yaml new file mode 100644 index 000000000..2f695bfe1 --- /dev/null +++ b/tests/testdata/redis-replication.yaml @@ -0,0 +1,141 @@ +--- +apiVersion: redis.redis.opstreelabs.in/v1beta2 +kind: Redis +metadata: + name: redis-replication + namespace: redis + labels: + app: redis-replication + annotations: + opstreelabs.in/redis: "true" +spec: + clusterSize: 3 + 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-replication-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 diff --git a/tests/testdata/redis-sentinel.yaml b/tests/testdata/redis-sentinel.yaml new file mode 100644 index 000000000..40bf80154 --- /dev/null +++ b/tests/testdata/redis-sentinel.yaml @@ -0,0 +1,120 @@ +--- +apiVersion: redis.redis.opstreelabs.in/v1beta2 +kind: Redis +metadata: + name: redis-sentinel + namespace: redis + labels: + app: redis-sentinel + annotations: + opstreelabs.in/redis: "true" +spec: + clusterSize: 3 + 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 + 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 + 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-sentinel-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