github.com/spotahome/redis-operator@v1.2.4/operator/redisfailover/service/generator.go (about)

     1  package service
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"strings"
     7  	"text/template"
     8  
     9  	appsv1 "k8s.io/api/apps/v1"
    10  	corev1 "k8s.io/api/core/v1"
    11  	policyv1 "k8s.io/api/policy/v1"
    12  	"k8s.io/apimachinery/pkg/api/resource"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	"k8s.io/apimachinery/pkg/util/intstr"
    15  
    16  	redisfailoverv1 "github.com/spotahome/redis-operator/api/redisfailover/v1"
    17  	"github.com/spotahome/redis-operator/operator/redisfailover/util"
    18  )
    19  
    20  const (
    21  	redisConfigurationVolumeName = "redis-config"
    22  	// Template used to build the Redis configuration
    23  	redisConfigTemplate = `slaveof 127.0.0.1 {{.Spec.Redis.Port}}
    24  port {{.Spec.Redis.Port}}
    25  tcp-keepalive 60
    26  save 900 1
    27  save 300 10
    28  user pinger -@all +ping on >pingpass
    29  {{- range .Spec.Redis.CustomCommandRenames}}
    30  rename-command "{{.From}}" "{{.To}}"
    31  {{- end}}
    32  `
    33  
    34  	sentinelConfigTemplate = `sentinel monitor mymaster 127.0.0.1 {{.Spec.Redis.Port}} 2
    35  sentinel down-after-milliseconds mymaster 1000
    36  sentinel failover-timeout mymaster 3000
    37  sentinel parallel-syncs mymaster 2`
    38  
    39  	redisShutdownConfigurationVolumeName   = "redis-shutdown-config"
    40  	redisStartupConfigurationVolumeName    = "redis-startup-config"
    41  	redisReadinessVolumeName               = "redis-readiness-config"
    42  	redisStorageVolumeName                 = "redis-data"
    43  	sentinelStartupConfigurationVolumeName = "sentinel-startup-config"
    44  
    45  	graceTime = 30
    46  )
    47  
    48  func generateSentinelService(rf *redisfailoverv1.RedisFailover, labels map[string]string, ownerRefs []metav1.OwnerReference) *corev1.Service {
    49  	name := GetSentinelName(rf)
    50  	namespace := rf.Namespace
    51  
    52  	sentinelTargetPort := intstr.FromInt(26379)
    53  	selectorLabels := generateSelectorLabels(sentinelRoleName, rf.Name)
    54  	labels = util.MergeLabels(labels, selectorLabels)
    55  
    56  	return &corev1.Service{
    57  		ObjectMeta: metav1.ObjectMeta{
    58  			Name:            name,
    59  			Namespace:       namespace,
    60  			Labels:          labels,
    61  			OwnerReferences: ownerRefs,
    62  			Annotations:     rf.Spec.Sentinel.ServiceAnnotations,
    63  		},
    64  		Spec: corev1.ServiceSpec{
    65  			Selector: selectorLabels,
    66  			Ports: []corev1.ServicePort{
    67  				{
    68  					Name:       "sentinel",
    69  					Port:       26379,
    70  					TargetPort: sentinelTargetPort,
    71  					Protocol:   "TCP",
    72  				},
    73  			},
    74  		},
    75  	}
    76  }
    77  
    78  func generateRedisService(rf *redisfailoverv1.RedisFailover, labels map[string]string, ownerRefs []metav1.OwnerReference) *corev1.Service {
    79  	name := GetRedisName(rf)
    80  	namespace := rf.Namespace
    81  
    82  	selectorLabels := generateSelectorLabels(redisRoleName, rf.Name)
    83  	labels = util.MergeLabels(labels, selectorLabels)
    84  	defaultAnnotations := map[string]string{
    85  		"prometheus.io/scrape": "true",
    86  		"prometheus.io/port":   "http",
    87  		"prometheus.io/path":   "/metrics",
    88  	}
    89  	annotations := util.MergeLabels(defaultAnnotations, rf.Spec.Redis.ServiceAnnotations)
    90  
    91  	return &corev1.Service{
    92  		ObjectMeta: metav1.ObjectMeta{
    93  			Name:            name,
    94  			Namespace:       namespace,
    95  			Labels:          labels,
    96  			OwnerReferences: ownerRefs,
    97  			Annotations:     annotations,
    98  		},
    99  		Spec: corev1.ServiceSpec{
   100  			Type:      corev1.ServiceTypeClusterIP,
   101  			ClusterIP: corev1.ClusterIPNone,
   102  			Ports: []corev1.ServicePort{
   103  				{
   104  					Port:     exporterPort,
   105  					Protocol: corev1.ProtocolTCP,
   106  					Name:     exporterPortName,
   107  				},
   108  			},
   109  			Selector: selectorLabels,
   110  		},
   111  	}
   112  }
   113  
   114  func generateSentinelConfigMap(rf *redisfailoverv1.RedisFailover, labels map[string]string, ownerRefs []metav1.OwnerReference) *corev1.ConfigMap {
   115  	name := GetSentinelName(rf)
   116  	namespace := rf.Namespace
   117  
   118  	labels = util.MergeLabels(labels, generateSelectorLabels(sentinelRoleName, rf.Name))
   119  
   120  	tmpl, err := template.New("sentinel").Parse(sentinelConfigTemplate)
   121  	if err != nil {
   122  		panic(err)
   123  	}
   124  
   125  	var tplOutput bytes.Buffer
   126  	if err := tmpl.Execute(&tplOutput, rf); err != nil {
   127  		panic(err)
   128  	}
   129  
   130  	sentinelConfigFileContent := tplOutput.String()
   131  
   132  	return &corev1.ConfigMap{
   133  		ObjectMeta: metav1.ObjectMeta{
   134  			Name:            name,
   135  			Namespace:       namespace,
   136  			Labels:          labels,
   137  			OwnerReferences: ownerRefs,
   138  		},
   139  		Data: map[string]string{
   140  			sentinelConfigFileName: sentinelConfigFileContent,
   141  		},
   142  	}
   143  }
   144  
   145  func generateRedisConfigMap(rf *redisfailoverv1.RedisFailover, labels map[string]string, ownerRefs []metav1.OwnerReference, password string) *corev1.ConfigMap {
   146  	name := GetRedisName(rf)
   147  	labels = util.MergeLabels(labels, generateSelectorLabels(redisRoleName, rf.Name))
   148  
   149  	tmpl, err := template.New("redis").Parse(redisConfigTemplate)
   150  	if err != nil {
   151  		panic(err)
   152  	}
   153  
   154  	var tplOutput bytes.Buffer
   155  	if err := tmpl.Execute(&tplOutput, rf); err != nil {
   156  		panic(err)
   157  	}
   158  
   159  	redisConfigFileContent := tplOutput.String()
   160  
   161  	if password != "" {
   162  		redisConfigFileContent = fmt.Sprintf("%s\nmasterauth %s\nrequirepass %s", redisConfigFileContent, password, password)
   163  	}
   164  
   165  	return &corev1.ConfigMap{
   166  		ObjectMeta: metav1.ObjectMeta{
   167  			Name:            name,
   168  			Namespace:       rf.Namespace,
   169  			Labels:          labels,
   170  			OwnerReferences: ownerRefs,
   171  		},
   172  		Data: map[string]string{
   173  			redisConfigFileName: redisConfigFileContent,
   174  		},
   175  	}
   176  }
   177  
   178  func generateRedisShutdownConfigMap(rf *redisfailoverv1.RedisFailover, labels map[string]string, ownerRefs []metav1.OwnerReference) *corev1.ConfigMap {
   179  	name := GetRedisShutdownConfigMapName(rf)
   180  	port := rf.Spec.Redis.Port
   181  	namespace := rf.Namespace
   182  	rfName := strings.Replace(strings.ToUpper(rf.Name), "-", "_", -1)
   183  
   184  	labels = util.MergeLabels(labels, generateSelectorLabels(redisRoleName, rf.Name))
   185  	shutdownContent := fmt.Sprintf(`master=$(redis-cli -h ${RFS_%[1]v_SERVICE_HOST} -p ${RFS_%[1]v_SERVICE_PORT_SENTINEL} --csv SENTINEL get-master-addr-by-name mymaster | tr ',' ' ' | tr -d '\"' |cut -d' ' -f1)
   186  if [ "$master" = "$(hostname -i)" ]; then
   187    redis-cli -h ${RFS_%[1]v_SERVICE_HOST} -p ${RFS_%[1]v_SERVICE_PORT_SENTINEL} SENTINEL failover mymaster
   188    sleep 31
   189  fi
   190  cmd="redis-cli -p %[2]v"
   191  if [ ! -z "${REDIS_PASSWORD}" ]; then
   192  	export REDISCLI_AUTH=${REDIS_PASSWORD}
   193  fi
   194  save_command="${cmd} save"
   195  eval $save_command`, rfName, port)
   196  
   197  	return &corev1.ConfigMap{
   198  		ObjectMeta: metav1.ObjectMeta{
   199  			Name:            name,
   200  			Namespace:       namespace,
   201  			Labels:          labels,
   202  			OwnerReferences: ownerRefs,
   203  		},
   204  		Data: map[string]string{
   205  			"shutdown.sh": shutdownContent,
   206  		},
   207  	}
   208  }
   209  
   210  func generateRedisReadinessConfigMap(rf *redisfailoverv1.RedisFailover, labels map[string]string, ownerRefs []metav1.OwnerReference) *corev1.ConfigMap {
   211  	name := GetRedisReadinessName(rf)
   212  	port := rf.Spec.Redis.Port
   213  	namespace := rf.Namespace
   214  
   215  	labels = util.MergeLabels(labels, generateSelectorLabels(redisRoleName, rf.Name))
   216  	readinessContent := fmt.Sprintf(`ROLE="role"
   217  ROLE_MASTER="role:master"
   218  ROLE_SLAVE="role:slave"
   219  IN_SYNC="master_sync_in_progress:1"
   220  NO_MASTER="master_host:127.0.0.1"
   221  
   222  cmd="redis-cli -p %[1]v"
   223  if [ ! -z "${REDIS_PASSWORD}" ]; then
   224  	export REDISCLI_AUTH=${REDIS_PASSWORD}
   225  fi
   226  
   227  cmd="${cmd} info replication"
   228  
   229  check_master(){
   230  		exit 0
   231  }
   232  
   233  check_slave(){
   234  		in_sync=$(echo "${cmd} | grep ${IN_SYNC} | tr -d \"\\r\" | tr -d \"\\n\"" | xargs -0 sh -c)
   235  		no_master=$(echo "${cmd} | grep ${NO_MASTER} | tr -d \"\\r\" | tr -d \"\\n\"" |  xargs -0 sh -c)
   236  
   237  		if [ -z "$in_sync" ] && [ -z "$no_master" ]; then
   238  				exit 0
   239  		fi
   240  
   241  		exit 1
   242  }
   243  
   244  role=$(echo "${cmd} | grep $ROLE | tr -d \"\\r\" | tr -d \"\\n\"" | xargs -0 sh -c)
   245  case $role in
   246  		$ROLE_MASTER)
   247  				check_master
   248  				;;
   249  		$ROLE_SLAVE)
   250  				check_slave
   251  				;;
   252  		*)
   253  				echo "unespected"
   254  				exit 1
   255  esac`, port)
   256  
   257  	return &corev1.ConfigMap{
   258  		ObjectMeta: metav1.ObjectMeta{
   259  			Name:            name,
   260  			Namespace:       namespace,
   261  			Labels:          labels,
   262  			OwnerReferences: ownerRefs,
   263  		},
   264  		Data: map[string]string{
   265  			"ready.sh": readinessContent,
   266  		},
   267  	}
   268  }
   269  
   270  func generateRedisStatefulSet(rf *redisfailoverv1.RedisFailover, labels map[string]string, ownerRefs []metav1.OwnerReference) *appsv1.StatefulSet {
   271  	name := GetRedisName(rf)
   272  	namespace := rf.Namespace
   273  
   274  	redisCommand := getRedisCommand(rf)
   275  	selectorLabels := generateSelectorLabels(redisRoleName, rf.Name)
   276  	labels = util.MergeLabels(labels, selectorLabels)
   277  	labels = util.MergeLabels(labels, generateRedisDefaultRoleLabel())
   278  
   279  	volumeMounts := getRedisVolumeMounts(rf)
   280  	volumes := getRedisVolumes(rf)
   281  	terminationGracePeriodSeconds := getTerminationGracePeriodSeconds(rf)
   282  
   283  	ss := &appsv1.StatefulSet{
   284  		ObjectMeta: metav1.ObjectMeta{
   285  			Annotations:     rf.Annotations,
   286  			Name:            name,
   287  			Namespace:       namespace,
   288  			Labels:          labels,
   289  			OwnerReferences: ownerRefs,
   290  		},
   291  		Spec: appsv1.StatefulSetSpec{
   292  			ServiceName: name,
   293  			Replicas:    &rf.Spec.Redis.Replicas,
   294  			UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
   295  				Type: appsv1.OnDeleteStatefulSetStrategyType,
   296  			},
   297  			PodManagementPolicy: appsv1.ParallelPodManagement,
   298  			Selector: &metav1.LabelSelector{
   299  				MatchLabels: selectorLabels,
   300  			},
   301  			Template: corev1.PodTemplateSpec{
   302  				ObjectMeta: metav1.ObjectMeta{
   303  					Labels:      labels,
   304  					Annotations: rf.Spec.Redis.PodAnnotations,
   305  				},
   306  				Spec: corev1.PodSpec{
   307  					Affinity:                      getAffinity(rf.Spec.Redis.Affinity, labels),
   308  					Tolerations:                   rf.Spec.Redis.Tolerations,
   309  					TopologySpreadConstraints:     rf.Spec.Redis.TopologySpreadConstraints,
   310  					NodeSelector:                  rf.Spec.Redis.NodeSelector,
   311  					SecurityContext:               getSecurityContext(rf.Spec.Redis.SecurityContext),
   312  					HostNetwork:                   rf.Spec.Redis.HostNetwork,
   313  					DNSPolicy:                     getDnsPolicy(rf.Spec.Redis.DNSPolicy),
   314  					ImagePullSecrets:              rf.Spec.Redis.ImagePullSecrets,
   315  					PriorityClassName:             rf.Spec.Redis.PriorityClassName,
   316  					ServiceAccountName:            rf.Spec.Redis.ServiceAccountName,
   317  					TerminationGracePeriodSeconds: &terminationGracePeriodSeconds,
   318  					Containers: []corev1.Container{
   319  						{
   320  							Name:            "redis",
   321  							Image:           rf.Spec.Redis.Image,
   322  							ImagePullPolicy: pullPolicy(rf.Spec.Redis.ImagePullPolicy),
   323  							SecurityContext: getContainerSecurityContext(rf.Spec.Redis.ContainerSecurityContext),
   324  							Ports: []corev1.ContainerPort{
   325  								{
   326  									Name:          "redis",
   327  									ContainerPort: rf.Spec.Redis.Port,
   328  									Protocol:      corev1.ProtocolTCP,
   329  								},
   330  							},
   331  							VolumeMounts: volumeMounts,
   332  							Command:      redisCommand,
   333  							ReadinessProbe: &corev1.Probe{
   334  								InitialDelaySeconds: graceTime,
   335  								TimeoutSeconds:      5,
   336  								ProbeHandler: corev1.ProbeHandler{
   337  									Exec: &corev1.ExecAction{
   338  										Command: []string{"/bin/sh", "/redis-readiness/ready.sh"},
   339  									},
   340  								},
   341  							},
   342  							LivenessProbe: &corev1.Probe{
   343  								InitialDelaySeconds: graceTime,
   344  								TimeoutSeconds:      5,
   345  								FailureThreshold:    6,
   346  								PeriodSeconds:       15,
   347  								ProbeHandler: corev1.ProbeHandler{
   348  									Exec: &corev1.ExecAction{
   349  										Command: []string{
   350  											"sh",
   351  											"-c",
   352  											fmt.Sprintf("redis-cli -h $(hostname) -p %[1]v ping --user pinger --pass pingpass --no-auth-warning", rf.Spec.Redis.Port),
   353  										},
   354  									},
   355  								},
   356  							},
   357  							Resources: rf.Spec.Redis.Resources,
   358  							Lifecycle: &corev1.Lifecycle{
   359  								PreStop: &corev1.LifecycleHandler{
   360  									Exec: &corev1.ExecAction{
   361  										Command: []string{"/bin/sh", "/redis-shutdown/shutdown.sh"},
   362  									},
   363  								},
   364  							},
   365  						},
   366  					},
   367  					Volumes: volumes,
   368  				},
   369  			},
   370  		},
   371  	}
   372  
   373  	if rf.Spec.Redis.Storage.PersistentVolumeClaim != nil {
   374  		pvc := corev1.PersistentVolumeClaim{
   375  			TypeMeta: metav1.TypeMeta{
   376  				APIVersion: "v1",
   377  				Kind:       "PersistentVolumeClaim",
   378  			},
   379  			ObjectMeta: metav1.ObjectMeta{
   380  				Name:              rf.Spec.Redis.Storage.PersistentVolumeClaim.EmbeddedObjectMetadata.Name,
   381  				Labels:            rf.Spec.Redis.Storage.PersistentVolumeClaim.EmbeddedObjectMetadata.Labels,
   382  				Annotations:       rf.Spec.Redis.Storage.PersistentVolumeClaim.EmbeddedObjectMetadata.Annotations,
   383  				CreationTimestamp: metav1.Time{},
   384  			},
   385  			Spec:   rf.Spec.Redis.Storage.PersistentVolumeClaim.Spec,
   386  			Status: rf.Spec.Redis.Storage.PersistentVolumeClaim.Status,
   387  		}
   388  		if !rf.Spec.Redis.Storage.KeepAfterDeletion {
   389  			// Set an owner reference so the persistent volumes are deleted when the RF is
   390  			pvc.OwnerReferences = ownerRefs
   391  		}
   392  		ss.Spec.VolumeClaimTemplates = []corev1.PersistentVolumeClaim{
   393  			pvc,
   394  		}
   395  	}
   396  
   397  	if rf.Spec.Redis.StartupConfigMap != "" {
   398  		ss.Spec.Template.Spec.Containers[0].StartupProbe = &corev1.Probe{
   399  			InitialDelaySeconds: graceTime,
   400  			TimeoutSeconds:      5,
   401  			FailureThreshold:    6,
   402  			PeriodSeconds:       15,
   403  			ProbeHandler: corev1.ProbeHandler{
   404  				Exec: &corev1.ExecAction{
   405  					Command: []string{"/bin/sh", "/redis-startup/startup.sh"},
   406  				},
   407  			},
   408  		}
   409  	}
   410  
   411  	if rf.Spec.Redis.Exporter.Enabled {
   412  		exporter := createRedisExporterContainer(rf)
   413  		ss.Spec.Template.Spec.Containers = append(ss.Spec.Template.Spec.Containers, exporter)
   414  	}
   415  
   416  	if rf.Spec.Redis.InitContainers != nil {
   417  		initContainers := getInitContainersWithRedisEnv(rf)
   418  		ss.Spec.Template.Spec.InitContainers = append(ss.Spec.Template.Spec.InitContainers, initContainers...)
   419  	}
   420  
   421  	if rf.Spec.Redis.ExtraContainers != nil {
   422  		extraContainers := getExtraContainersWithRedisEnv(rf)
   423  		ss.Spec.Template.Spec.Containers = append(ss.Spec.Template.Spec.Containers, extraContainers...)
   424  	}
   425  
   426  	redisEnv := getRedisEnv(rf)
   427  	ss.Spec.Template.Spec.Containers[0].Env = append(ss.Spec.Template.Spec.Containers[0].Env, redisEnv...)
   428  
   429  	return ss
   430  }
   431  
   432  func generateSentinelDeployment(rf *redisfailoverv1.RedisFailover, labels map[string]string, ownerRefs []metav1.OwnerReference) *appsv1.Deployment {
   433  	name := GetSentinelName(rf)
   434  	configMapName := GetSentinelName(rf)
   435  	namespace := rf.Namespace
   436  
   437  	sentinelCommand := getSentinelCommand(rf)
   438  	selectorLabels := generateSelectorLabels(sentinelRoleName, rf.Name)
   439  	labels = util.MergeLabels(labels, selectorLabels)
   440  
   441  	volumeMounts := getSentinelVolumeMounts(rf)
   442  	volumes := getSentinelVolumes(rf, configMapName)
   443  
   444  	sd := &appsv1.Deployment{
   445  		ObjectMeta: metav1.ObjectMeta{
   446  			Name:            name,
   447  			Namespace:       namespace,
   448  			Labels:          labels,
   449  			OwnerReferences: ownerRefs,
   450  		},
   451  		Spec: appsv1.DeploymentSpec{
   452  			Replicas: &rf.Spec.Sentinel.Replicas,
   453  			Selector: &metav1.LabelSelector{
   454  				MatchLabels: selectorLabels,
   455  			},
   456  			Template: corev1.PodTemplateSpec{
   457  				ObjectMeta: metav1.ObjectMeta{
   458  					Labels:      labels,
   459  					Annotations: rf.Spec.Sentinel.PodAnnotations,
   460  				},
   461  				Spec: corev1.PodSpec{
   462  					Affinity:                  getAffinity(rf.Spec.Sentinel.Affinity, labels),
   463  					Tolerations:               rf.Spec.Sentinel.Tolerations,
   464  					TopologySpreadConstraints: rf.Spec.Sentinel.TopologySpreadConstraints,
   465  					NodeSelector:              rf.Spec.Sentinel.NodeSelector,
   466  					SecurityContext:           getSecurityContext(rf.Spec.Sentinel.SecurityContext),
   467  					HostNetwork:               rf.Spec.Sentinel.HostNetwork,
   468  					DNSPolicy:                 getDnsPolicy(rf.Spec.Sentinel.DNSPolicy),
   469  					ImagePullSecrets:          rf.Spec.Sentinel.ImagePullSecrets,
   470  					PriorityClassName:         rf.Spec.Sentinel.PriorityClassName,
   471  					ServiceAccountName:        rf.Spec.Sentinel.ServiceAccountName,
   472  					InitContainers: []corev1.Container{
   473  						{
   474  							Name:            "sentinel-config-copy",
   475  							Image:           rf.Spec.Sentinel.Image,
   476  							ImagePullPolicy: pullPolicy(rf.Spec.Sentinel.ImagePullPolicy),
   477  							SecurityContext: getContainerSecurityContext(rf.Spec.Sentinel.ConfigCopy.ContainerSecurityContext),
   478  							VolumeMounts: []corev1.VolumeMount{
   479  								{
   480  									Name:      "sentinel-config",
   481  									MountPath: "/redis",
   482  								},
   483  								{
   484  									Name:      "sentinel-config-writable",
   485  									MountPath: "/redis-writable",
   486  								},
   487  							},
   488  							Command: []string{
   489  								"cp",
   490  								fmt.Sprintf("/redis/%s", sentinelConfigFileName),
   491  								fmt.Sprintf("/redis-writable/%s", sentinelConfigFileName),
   492  							},
   493  							Resources: corev1.ResourceRequirements{
   494  								Limits: corev1.ResourceList{
   495  									corev1.ResourceCPU:    resource.MustParse("10m"),
   496  									corev1.ResourceMemory: resource.MustParse("32Mi"),
   497  								},
   498  								Requests: corev1.ResourceList{
   499  									corev1.ResourceCPU:    resource.MustParse("10m"),
   500  									corev1.ResourceMemory: resource.MustParse("32Mi"),
   501  								},
   502  							},
   503  						},
   504  					},
   505  					Containers: []corev1.Container{
   506  						{
   507  							Name:            "sentinel",
   508  							Image:           rf.Spec.Sentinel.Image,
   509  							ImagePullPolicy: pullPolicy(rf.Spec.Sentinel.ImagePullPolicy),
   510  							SecurityContext: getContainerSecurityContext(rf.Spec.Sentinel.ContainerSecurityContext),
   511  							Ports: []corev1.ContainerPort{
   512  								{
   513  									Name:          "sentinel",
   514  									ContainerPort: 26379,
   515  									Protocol:      corev1.ProtocolTCP,
   516  								},
   517  							},
   518  							VolumeMounts: volumeMounts,
   519  							Command:      sentinelCommand,
   520  							ReadinessProbe: &corev1.Probe{
   521  								InitialDelaySeconds: graceTime,
   522  								TimeoutSeconds:      5,
   523  								ProbeHandler: corev1.ProbeHandler{
   524  									Exec: &corev1.ExecAction{
   525  										Command: []string{
   526  											"sh",
   527  											"-c",
   528  											"redis-cli -h $(hostname) -p 26379 ping",
   529  										},
   530  									},
   531  								},
   532  							},
   533  							LivenessProbe: &corev1.Probe{
   534  								InitialDelaySeconds: graceTime,
   535  								TimeoutSeconds:      5,
   536  								ProbeHandler: corev1.ProbeHandler{
   537  									Exec: &corev1.ExecAction{
   538  										Command: []string{
   539  											"sh",
   540  											"-c",
   541  											"redis-cli -h $(hostname) -p 26379 ping",
   542  										},
   543  									},
   544  								},
   545  							},
   546  							Resources: rf.Spec.Sentinel.Resources,
   547  						},
   548  					},
   549  					Volumes: volumes,
   550  				},
   551  			},
   552  		},
   553  	}
   554  
   555  	if rf.Spec.Sentinel.StartupConfigMap != "" {
   556  		sd.Spec.Template.Spec.Containers[0].StartupProbe = &corev1.Probe{
   557  			InitialDelaySeconds: graceTime,
   558  			TimeoutSeconds:      5,
   559  			FailureThreshold:    6,
   560  			PeriodSeconds:       15,
   561  			ProbeHandler: corev1.ProbeHandler{
   562  				Exec: &corev1.ExecAction{
   563  					Command: []string{"/bin/sh", "/sentinel-startup/startup.sh"},
   564  				},
   565  			},
   566  		}
   567  	}
   568  
   569  	if rf.Spec.Sentinel.Exporter.Enabled {
   570  		exporter := createSentinelExporterContainer(rf)
   571  		sd.Spec.Template.Spec.Containers = append(sd.Spec.Template.Spec.Containers, exporter)
   572  	}
   573  	if rf.Spec.Sentinel.InitContainers != nil {
   574  		sd.Spec.Template.Spec.InitContainers = append(sd.Spec.Template.Spec.InitContainers, rf.Spec.Sentinel.InitContainers...)
   575  	}
   576  
   577  	if rf.Spec.Sentinel.ExtraContainers != nil {
   578  		sd.Spec.Template.Spec.Containers = append(sd.Spec.Template.Spec.Containers, rf.Spec.Sentinel.ExtraContainers...)
   579  	}
   580  
   581  	return sd
   582  }
   583  
   584  func generatePodDisruptionBudget(name string, namespace string, labels map[string]string, ownerRefs []metav1.OwnerReference, minAvailable intstr.IntOrString) *policyv1.PodDisruptionBudget {
   585  	return &policyv1.PodDisruptionBudget{
   586  		ObjectMeta: metav1.ObjectMeta{
   587  			Name:            name,
   588  			Namespace:       namespace,
   589  			Labels:          labels,
   590  			OwnerReferences: ownerRefs,
   591  		},
   592  		Spec: policyv1.PodDisruptionBudgetSpec{
   593  			MinAvailable: &minAvailable,
   594  			Selector: &metav1.LabelSelector{
   595  				MatchLabels: labels,
   596  			},
   597  		},
   598  	}
   599  }
   600  
   601  var exporterDefaultResourceRequirements = corev1.ResourceRequirements{
   602  	Limits: corev1.ResourceList{
   603  		corev1.ResourceCPU:    resource.MustParse(exporterDefaultLimitCPU),
   604  		corev1.ResourceMemory: resource.MustParse(exporterDefaultLimitMemory),
   605  	},
   606  	Requests: corev1.ResourceList{
   607  		corev1.ResourceCPU:    resource.MustParse(exporterDefaultRequestCPU),
   608  		corev1.ResourceMemory: resource.MustParse(exporterDefaultRequestMemory),
   609  	},
   610  }
   611  
   612  func createRedisExporterContainer(rf *redisfailoverv1.RedisFailover) corev1.Container {
   613  	resources := exporterDefaultResourceRequirements
   614  	if rf.Spec.Redis.Exporter.Resources != nil {
   615  		resources = *rf.Spec.Redis.Exporter.Resources
   616  	}
   617  	container := corev1.Container{
   618  		Name:            exporterContainerName,
   619  		Image:           rf.Spec.Redis.Exporter.Image,
   620  		ImagePullPolicy: pullPolicy(rf.Spec.Redis.Exporter.ImagePullPolicy),
   621  		SecurityContext: getContainerSecurityContext(rf.Spec.Redis.Exporter.ContainerSecurityContext),
   622  		Args:            rf.Spec.Redis.Exporter.Args,
   623  		Env: append(rf.Spec.Redis.Exporter.Env, corev1.EnvVar{
   624  			Name: "REDIS_ALIAS",
   625  			ValueFrom: &corev1.EnvVarSource{
   626  				FieldRef: &corev1.ObjectFieldSelector{
   627  					FieldPath: "metadata.name",
   628  				},
   629  			},
   630  		},
   631  		),
   632  		Ports: []corev1.ContainerPort{
   633  			{
   634  				Name:          "metrics",
   635  				ContainerPort: exporterPort,
   636  				Protocol:      corev1.ProtocolTCP,
   637  			},
   638  		},
   639  		Resources: resources,
   640  	}
   641  
   642  	redisEnv := getRedisEnv(rf)
   643  	container.Env = append(container.Env, redisEnv...)
   644  
   645  	return container
   646  }
   647  
   648  func createSentinelExporterContainer(rf *redisfailoverv1.RedisFailover) corev1.Container {
   649  	resources := exporterDefaultResourceRequirements
   650  	if rf.Spec.Sentinel.Exporter.Resources != nil {
   651  		resources = *rf.Spec.Sentinel.Exporter.Resources
   652  	}
   653  	container := corev1.Container{
   654  		Name:            sentinelExporterContainerName,
   655  		Image:           rf.Spec.Sentinel.Exporter.Image,
   656  		ImagePullPolicy: pullPolicy(rf.Spec.Sentinel.Exporter.ImagePullPolicy),
   657  		SecurityContext: getContainerSecurityContext(rf.Spec.Sentinel.Exporter.ContainerSecurityContext),
   658  		Args:            rf.Spec.Sentinel.Exporter.Args,
   659  		Env: append(rf.Spec.Sentinel.Exporter.Env, corev1.EnvVar{
   660  			Name: "REDIS_ALIAS",
   661  			ValueFrom: &corev1.EnvVarSource{
   662  				FieldRef: &corev1.ObjectFieldSelector{
   663  					FieldPath: "metadata.name",
   664  				},
   665  			},
   666  		}, corev1.EnvVar{
   667  			Name:  "REDIS_EXPORTER_WEB_LISTEN_ADDRESS",
   668  			Value: fmt.Sprintf("0.0.0.0:%[1]v", sentinelExporterPort),
   669  		}, corev1.EnvVar{
   670  			Name:  "REDIS_ADDR",
   671  			Value: "redis://127.0.0.1:26379",
   672  		},
   673  		),
   674  		Ports: []corev1.ContainerPort{
   675  			{
   676  				Name:          "metrics",
   677  				ContainerPort: sentinelExporterPort,
   678  				Protocol:      corev1.ProtocolTCP,
   679  			},
   680  		},
   681  		Resources: resources,
   682  	}
   683  
   684  	return container
   685  }
   686  
   687  func getAffinity(affinity *corev1.Affinity, labels map[string]string) *corev1.Affinity {
   688  	if affinity != nil {
   689  		return affinity
   690  	}
   691  
   692  	// Return a SOFT anti-affinity
   693  	return &corev1.Affinity{
   694  		PodAntiAffinity: &corev1.PodAntiAffinity{
   695  			PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{
   696  				{
   697  					Weight: 100,
   698  					PodAffinityTerm: corev1.PodAffinityTerm{
   699  						TopologyKey: hostnameTopologyKey,
   700  						LabelSelector: &metav1.LabelSelector{
   701  							MatchLabels: labels,
   702  						},
   703  					},
   704  				},
   705  			},
   706  		},
   707  	}
   708  }
   709  
   710  func getSecurityContext(secctx *corev1.PodSecurityContext) *corev1.PodSecurityContext {
   711  	if secctx != nil {
   712  		return secctx
   713  	}
   714  
   715  	defaultUserAndGroup := int64(1000)
   716  	runAsNonRoot := true
   717  
   718  	return &corev1.PodSecurityContext{
   719  		RunAsUser:    &defaultUserAndGroup,
   720  		RunAsGroup:   &defaultUserAndGroup,
   721  		RunAsNonRoot: &runAsNonRoot,
   722  		FSGroup:      &defaultUserAndGroup,
   723  	}
   724  }
   725  
   726  func getContainerSecurityContext(secctx *corev1.SecurityContext) *corev1.SecurityContext {
   727  	if secctx != nil {
   728  		return secctx
   729  	}
   730  
   731  	capabilities := &corev1.Capabilities{
   732  		Add: []corev1.Capability{},
   733  		Drop: []corev1.Capability{
   734  			"ALL",
   735  		},
   736  	}
   737  	privileged := false
   738  	defaultUserAndGroup := int64(1000)
   739  	runAsNonRoot := true
   740  	allowPrivilegeEscalation := false
   741  	readOnlyRootFilesystem := true
   742  
   743  	return &corev1.SecurityContext{
   744  		Capabilities:             capabilities,
   745  		Privileged:               &privileged,
   746  		RunAsUser:                &defaultUserAndGroup,
   747  		RunAsGroup:               &defaultUserAndGroup,
   748  		RunAsNonRoot:             &runAsNonRoot,
   749  		ReadOnlyRootFilesystem:   &readOnlyRootFilesystem,
   750  		AllowPrivilegeEscalation: &allowPrivilegeEscalation,
   751  	}
   752  }
   753  
   754  func getDnsPolicy(dnspolicy corev1.DNSPolicy) corev1.DNSPolicy {
   755  	if dnspolicy == "" {
   756  		return corev1.DNSClusterFirst
   757  	}
   758  	return dnspolicy
   759  }
   760  
   761  func getQuorum(rf *redisfailoverv1.RedisFailover) int32 {
   762  	return rf.Spec.Sentinel.Replicas/2 + 1
   763  }
   764  
   765  func getRedisVolumeMounts(rf *redisfailoverv1.RedisFailover) []corev1.VolumeMount {
   766  	volumeMounts := []corev1.VolumeMount{
   767  		{
   768  			Name:      redisConfigurationVolumeName,
   769  			MountPath: "/redis",
   770  		},
   771  		{
   772  			Name:      redisShutdownConfigurationVolumeName,
   773  			MountPath: "/redis-shutdown",
   774  		},
   775  		{
   776  			Name:      redisReadinessVolumeName,
   777  			MountPath: "/redis-readiness",
   778  		},
   779  		{
   780  			Name:      getRedisDataVolumeName(rf),
   781  			MountPath: "/data",
   782  		},
   783  	}
   784  
   785  	if rf.Spec.Redis.StartupConfigMap != "" {
   786  		startupVolumeMount := corev1.VolumeMount{
   787  			Name:      redisStartupConfigurationVolumeName,
   788  			MountPath: "/redis-startup",
   789  		}
   790  
   791  		volumeMounts = append(volumeMounts, startupVolumeMount)
   792  	}
   793  
   794  	if rf.Spec.Redis.ExtraVolumeMounts != nil {
   795  		volumeMounts = append(volumeMounts, rf.Spec.Redis.ExtraVolumeMounts...)
   796  	}
   797  
   798  	return volumeMounts
   799  }
   800  
   801  func getSentinelVolumeMounts(rf *redisfailoverv1.RedisFailover) []corev1.VolumeMount {
   802  	volumeMounts := []corev1.VolumeMount{
   803  		{
   804  			Name:      "sentinel-config-writable",
   805  			MountPath: "/redis",
   806  		},
   807  	}
   808  
   809  	if rf.Spec.Sentinel.StartupConfigMap != "" {
   810  		startupVolumeMount := corev1.VolumeMount{
   811  			Name:      "sentinel-startup-config",
   812  			MountPath: "/sentinel-startup",
   813  		}
   814  		volumeMounts = append(volumeMounts, startupVolumeMount)
   815  	}
   816  	if rf.Spec.Sentinel.ExtraVolumeMounts != nil {
   817  		volumeMounts = append(volumeMounts, rf.Spec.Sentinel.ExtraVolumeMounts...)
   818  	}
   819  
   820  	return volumeMounts
   821  }
   822  
   823  func getRedisVolumes(rf *redisfailoverv1.RedisFailover) []corev1.Volume {
   824  	configMapName := GetRedisName(rf)
   825  	shutdownConfigMapName := GetRedisShutdownConfigMapName(rf)
   826  	readinessConfigMapName := GetRedisReadinessName(rf)
   827  
   828  	executeMode := int32(0744)
   829  	volumes := []corev1.Volume{
   830  		{
   831  			Name: redisConfigurationVolumeName,
   832  			VolumeSource: corev1.VolumeSource{
   833  				ConfigMap: &corev1.ConfigMapVolumeSource{
   834  					LocalObjectReference: corev1.LocalObjectReference{
   835  						Name: configMapName,
   836  					},
   837  				},
   838  			},
   839  		},
   840  		{
   841  			Name: redisShutdownConfigurationVolumeName,
   842  			VolumeSource: corev1.VolumeSource{
   843  				ConfigMap: &corev1.ConfigMapVolumeSource{
   844  					LocalObjectReference: corev1.LocalObjectReference{
   845  						Name: shutdownConfigMapName,
   846  					},
   847  					DefaultMode: &executeMode,
   848  				},
   849  			},
   850  		},
   851  		{
   852  			Name: redisReadinessVolumeName,
   853  			VolumeSource: corev1.VolumeSource{
   854  				ConfigMap: &corev1.ConfigMapVolumeSource{
   855  					LocalObjectReference: corev1.LocalObjectReference{
   856  						Name: readinessConfigMapName,
   857  					},
   858  					DefaultMode: &executeMode,
   859  				},
   860  			},
   861  		},
   862  	}
   863  
   864  	if rf.Spec.Redis.StartupConfigMap != "" {
   865  		startupVolumeName := rf.Spec.Redis.StartupConfigMap
   866  		startupVolume := corev1.Volume{
   867  			Name: redisStartupConfigurationVolumeName,
   868  			VolumeSource: corev1.VolumeSource{
   869  				ConfigMap: &corev1.ConfigMapVolumeSource{
   870  					LocalObjectReference: corev1.LocalObjectReference{
   871  						Name: startupVolumeName,
   872  					},
   873  					DefaultMode: &executeMode,
   874  				},
   875  			},
   876  		}
   877  		volumes = append(volumes, startupVolume)
   878  	}
   879  
   880  	if rf.Spec.Redis.ExtraVolumes != nil {
   881  		volumes = append(volumes, rf.Spec.Redis.ExtraVolumes...)
   882  	}
   883  
   884  	dataVolume := getRedisDataVolume(rf)
   885  	if dataVolume != nil {
   886  		volumes = append(volumes, *dataVolume)
   887  	}
   888  
   889  	return volumes
   890  }
   891  
   892  func getSentinelVolumes(rf *redisfailoverv1.RedisFailover, configMapName string) []corev1.Volume {
   893  	executeMode := int32(0744)
   894  
   895  	volumes := []corev1.Volume{
   896  		{
   897  			Name: "sentinel-config",
   898  			VolumeSource: corev1.VolumeSource{
   899  				ConfigMap: &corev1.ConfigMapVolumeSource{
   900  					LocalObjectReference: corev1.LocalObjectReference{
   901  						Name: configMapName,
   902  					},
   903  				},
   904  			},
   905  		},
   906  		{
   907  			Name: "sentinel-config-writable",
   908  			VolumeSource: corev1.VolumeSource{
   909  				EmptyDir: &corev1.EmptyDirVolumeSource{},
   910  			},
   911  		},
   912  	}
   913  
   914  	if rf.Spec.Sentinel.StartupConfigMap != "" {
   915  		startupVolumeName := rf.Spec.Sentinel.StartupConfigMap
   916  		startupVolume := corev1.Volume{
   917  			Name: sentinelStartupConfigurationVolumeName,
   918  			VolumeSource: corev1.VolumeSource{
   919  				ConfigMap: &corev1.ConfigMapVolumeSource{
   920  					LocalObjectReference: corev1.LocalObjectReference{
   921  						Name: startupVolumeName,
   922  					},
   923  					DefaultMode: &executeMode,
   924  				},
   925  			},
   926  		}
   927  		volumes = append(volumes, startupVolume)
   928  	}
   929  
   930  	if rf.Spec.Sentinel.ExtraVolumes != nil {
   931  		volumes = append(volumes, rf.Spec.Sentinel.ExtraVolumes...)
   932  	}
   933  
   934  	return volumes
   935  }
   936  
   937  func getRedisDataVolume(rf *redisfailoverv1.RedisFailover) *corev1.Volume {
   938  	// This will find the volumed desired by the user. If no volume defined
   939  	// an EmptyDir will be used by default
   940  	switch {
   941  	case rf.Spec.Redis.Storage.PersistentVolumeClaim != nil:
   942  		return nil
   943  	case rf.Spec.Redis.Storage.EmptyDir != nil:
   944  		return &corev1.Volume{
   945  			Name: redisStorageVolumeName,
   946  			VolumeSource: corev1.VolumeSource{
   947  				EmptyDir: rf.Spec.Redis.Storage.EmptyDir,
   948  			},
   949  		}
   950  	default:
   951  		return &corev1.Volume{
   952  			Name: redisStorageVolumeName,
   953  			VolumeSource: corev1.VolumeSource{
   954  				EmptyDir: &corev1.EmptyDirVolumeSource{},
   955  			},
   956  		}
   957  	}
   958  }
   959  
   960  func getRedisDataVolumeName(rf *redisfailoverv1.RedisFailover) string {
   961  	switch {
   962  	case rf.Spec.Redis.Storage.PersistentVolumeClaim != nil:
   963  		return rf.Spec.Redis.Storage.PersistentVolumeClaim.Name
   964  	case rf.Spec.Redis.Storage.EmptyDir != nil:
   965  		return redisStorageVolumeName
   966  	default:
   967  		return redisStorageVolumeName
   968  	}
   969  }
   970  
   971  func getRedisCommand(rf *redisfailoverv1.RedisFailover) []string {
   972  	if len(rf.Spec.Redis.Command) > 0 {
   973  		return rf.Spec.Redis.Command
   974  	}
   975  	return []string{
   976  		"redis-server",
   977  		fmt.Sprintf("/redis/%s", redisConfigFileName),
   978  	}
   979  }
   980  
   981  func getSentinelCommand(rf *redisfailoverv1.RedisFailover) []string {
   982  	if len(rf.Spec.Sentinel.Command) > 0 {
   983  		return rf.Spec.Sentinel.Command
   984  	}
   985  	return []string{
   986  		"redis-server",
   987  		fmt.Sprintf("/redis/%s", sentinelConfigFileName),
   988  		"--sentinel",
   989  	}
   990  }
   991  
   992  func pullPolicy(specPolicy corev1.PullPolicy) corev1.PullPolicy {
   993  	if specPolicy == "" {
   994  		return corev1.PullAlways
   995  	}
   996  	return specPolicy
   997  }
   998  
   999  func getTerminationGracePeriodSeconds(rf *redisfailoverv1.RedisFailover) int64 {
  1000  	if rf.Spec.Redis.TerminationGracePeriodSeconds > 0 {
  1001  		return rf.Spec.Redis.TerminationGracePeriodSeconds
  1002  	}
  1003  	return 30
  1004  }
  1005  
  1006  func getExtraContainersWithRedisEnv(rf *redisfailoverv1.RedisFailover) []corev1.Container {
  1007  	env := getRedisEnv(rf)
  1008  	extraContainers := getContainersWithRedisEnv(rf.Spec.Redis.ExtraContainers, env)
  1009  
  1010  	return extraContainers
  1011  }
  1012  
  1013  func getInitContainersWithRedisEnv(rf *redisfailoverv1.RedisFailover) []corev1.Container {
  1014  	env := getRedisEnv(rf)
  1015  	initContainers := getContainersWithRedisEnv(rf.Spec.Redis.InitContainers, env)
  1016  
  1017  	return initContainers
  1018  }
  1019  
  1020  func getContainersWithRedisEnv(cs []corev1.Container, e []corev1.EnvVar) []corev1.Container {
  1021  	var containers []corev1.Container
  1022  	for _, c := range cs {
  1023  		c.Env = append(c.Env, e...)
  1024  		containers = append(containers, c)
  1025  	}
  1026  
  1027  	return containers
  1028  }
  1029  
  1030  func getRedisEnv(rf *redisfailoverv1.RedisFailover) []corev1.EnvVar {
  1031  	var env []corev1.EnvVar
  1032  
  1033  	env = append(env, corev1.EnvVar{
  1034  		Name:  "REDIS_ADDR",
  1035  		Value: fmt.Sprintf("redis://127.0.0.1:%[1]v", rf.Spec.Redis.Port),
  1036  	})
  1037  
  1038  	env = append(env, corev1.EnvVar{
  1039  		Name:  "REDIS_PORT",
  1040  		Value: fmt.Sprintf("%[1]v", rf.Spec.Redis.Port),
  1041  	})
  1042  
  1043  	env = append(env, corev1.EnvVar{
  1044  		Name:  "REDIS_USER",
  1045  		Value: "default",
  1046  	})
  1047  
  1048  	if rf.Spec.Auth.SecretPath != "" {
  1049  		env = append(env, corev1.EnvVar{
  1050  			Name: "REDIS_PASSWORD",
  1051  			ValueFrom: &corev1.EnvVarSource{
  1052  				SecretKeyRef: &corev1.SecretKeySelector{
  1053  					LocalObjectReference: corev1.LocalObjectReference{
  1054  						Name: rf.Spec.Auth.SecretPath,
  1055  					},
  1056  					Key: "password",
  1057  				},
  1058  			},
  1059  		})
  1060  	}
  1061  
  1062  	return env
  1063  }