github.com/spotahome/redis-operator@v1.2.4/operator/redisfailover/service/heal.go (about) 1 package service 2 3 import ( 4 "errors" 5 "sort" 6 "strconv" 7 8 redisfailoverv1 "github.com/spotahome/redis-operator/api/redisfailover/v1" 9 "github.com/spotahome/redis-operator/log" 10 "github.com/spotahome/redis-operator/service/k8s" 11 "github.com/spotahome/redis-operator/service/redis" 12 v1 "k8s.io/api/core/v1" 13 ) 14 15 // RedisFailoverHeal defines the interface able to fix the problems on the redis failovers 16 type RedisFailoverHeal interface { 17 MakeMaster(ip string, rFailover *redisfailoverv1.RedisFailover) error 18 SetOldestAsMaster(rFailover *redisfailoverv1.RedisFailover) error 19 SetMasterOnAll(masterIP string, rFailover *redisfailoverv1.RedisFailover) error 20 SetExternalMasterOnAll(masterIP string, masterPort string, rFailover *redisfailoverv1.RedisFailover) error 21 NewSentinelMonitor(ip string, monitor string, rFailover *redisfailoverv1.RedisFailover) error 22 NewSentinelMonitorWithPort(ip string, monitor string, port string, rFailover *redisfailoverv1.RedisFailover) error 23 RestoreSentinel(ip string) error 24 SetSentinelCustomConfig(ip string, rFailover *redisfailoverv1.RedisFailover) error 25 SetRedisCustomConfig(ip string, rFailover *redisfailoverv1.RedisFailover) error 26 DeletePod(podName string, rFailover *redisfailoverv1.RedisFailover) error 27 } 28 29 // RedisFailoverHealer is our implementation of RedisFailoverCheck interface 30 type RedisFailoverHealer struct { 31 k8sService k8s.Services 32 redisClient redis.Client 33 logger log.Logger 34 } 35 36 // NewRedisFailoverHealer creates an object of the RedisFailoverChecker struct 37 func NewRedisFailoverHealer(k8sService k8s.Services, redisClient redis.Client, logger log.Logger) *RedisFailoverHealer { 38 logger = logger.With("service", "redis.healer") 39 return &RedisFailoverHealer{ 40 k8sService: k8sService, 41 redisClient: redisClient, 42 logger: logger, 43 } 44 } 45 46 func (r *RedisFailoverHealer) setMasterLabelIfNecessary(namespace string, pod v1.Pod) error { 47 for labelKey, labelValue := range pod.ObjectMeta.Labels { 48 if labelKey == redisRoleLabelKey && labelValue == redisRoleLabelMaster { 49 return nil 50 } 51 } 52 return r.k8sService.UpdatePodLabels(namespace, pod.ObjectMeta.Name, generateRedisMasterRoleLabel()) 53 } 54 55 func (r *RedisFailoverHealer) setSlaveLabelIfNecessary(namespace string, pod v1.Pod) error { 56 for labelKey, labelValue := range pod.ObjectMeta.Labels { 57 if labelKey == redisRoleLabelKey && labelValue == redisRoleLabelSlave { 58 return nil 59 } 60 } 61 return r.k8sService.UpdatePodLabels(namespace, pod.ObjectMeta.Name, generateRedisSlaveRoleLabel()) 62 } 63 64 func (r *RedisFailoverHealer) MakeMaster(ip string, rf *redisfailoverv1.RedisFailover) error { 65 password, err := k8s.GetRedisPassword(r.k8sService, rf) 66 if err != nil { 67 return err 68 } 69 70 port := getRedisPort(rf.Spec.Redis.Port) 71 err = r.redisClient.MakeMaster(ip, port, password) 72 if err != nil { 73 return err 74 } 75 76 rps, err := r.k8sService.GetStatefulSetPods(rf.Namespace, GetRedisName(rf)) 77 if err != nil { 78 return err 79 } 80 for _, rp := range rps.Items { 81 if rp.Status.PodIP == ip { 82 return r.setMasterLabelIfNecessary(rf.Namespace, rp) 83 } 84 } 85 return nil 86 } 87 88 // SetOldestAsMaster puts all redis to the same master, choosen by order of appearance 89 func (r *RedisFailoverHealer) SetOldestAsMaster(rf *redisfailoverv1.RedisFailover) error { 90 ssp, err := r.k8sService.GetStatefulSetPods(rf.Namespace, GetRedisName(rf)) 91 if err != nil { 92 return err 93 } 94 if len(ssp.Items) < 1 { 95 return errors.New("number of redis pods are 0") 96 } 97 98 // Order the pods so we start by the oldest one 99 sort.Slice(ssp.Items, func(i, j int) bool { 100 return ssp.Items[i].CreationTimestamp.Before(&ssp.Items[j].CreationTimestamp) 101 }) 102 103 password, err := k8s.GetRedisPassword(r.k8sService, rf) 104 if err != nil { 105 return err 106 } 107 108 port := getRedisPort(rf.Spec.Redis.Port) 109 newMasterIP := "" 110 for _, pod := range ssp.Items { 111 if newMasterIP == "" { 112 newMasterIP = pod.Status.PodIP 113 r.logger.WithField("redisfailover", rf.ObjectMeta.Name).WithField("namespace", rf.ObjectMeta.Namespace).Infof("New master is %s with ip %s", pod.Name, newMasterIP) 114 if err := r.redisClient.MakeMaster(newMasterIP, port, password); err != nil { 115 newMasterIP = "" 116 r.logger.WithField("redisfailover", rf.ObjectMeta.Name).WithField("namespace", rf.ObjectMeta.Namespace).Errorf("Make new master failed, master ip: %s, error: %v", pod.Status.PodIP, err) 117 continue 118 } 119 120 err = r.setMasterLabelIfNecessary(rf.Namespace, pod) 121 if err != nil { 122 return err 123 } 124 125 newMasterIP = pod.Status.PodIP 126 } else { 127 r.logger.Infof("Making pod %s slave of %s", pod.Name, newMasterIP) 128 if err := r.redisClient.MakeSlaveOfWithPort(pod.Status.PodIP, newMasterIP, port, password); err != nil { 129 r.logger.WithField("redisfailover", rf.ObjectMeta.Name).WithField("namespace", rf.ObjectMeta.Namespace).Errorf("Make slave failed, slave pod ip: %s, master ip: %s, error: %v", pod.Status.PodIP, newMasterIP, err) 130 } 131 132 err = r.setSlaveLabelIfNecessary(rf.Namespace, pod) 133 if err != nil { 134 return err 135 } 136 } 137 } 138 if newMasterIP == "" { 139 return errors.New("SetOldestAsMaster- unable to set master") 140 } else { 141 return nil 142 } 143 } 144 145 // SetMasterOnAll puts all redis nodes as a slave of a given master 146 func (r *RedisFailoverHealer) SetMasterOnAll(masterIP string, rf *redisfailoverv1.RedisFailover) error { 147 ssp, err := r.k8sService.GetStatefulSetPods(rf.Namespace, GetRedisName(rf)) 148 if err != nil { 149 return err 150 } 151 152 password, err := k8s.GetRedisPassword(r.k8sService, rf) 153 if err != nil { 154 return err 155 } 156 157 port := getRedisPort(rf.Spec.Redis.Port) 158 for _, pod := range ssp.Items { 159 //During this configuration process if there is a new master selected , bailout 160 isMaster, err := r.redisClient.IsMaster(masterIP, port, password) 161 if err != nil || !isMaster { 162 r.logger.WithField("redisfailover", rf.ObjectMeta.Name).WithField("namespace", rf.ObjectMeta.Namespace).Errorf("check master failed maybe this node is not ready(ip changed), or sentinel made a switch: %s", masterIP) 163 return err 164 } else { 165 if pod.Status.PodIP == masterIP { 166 continue 167 } 168 r.logger.WithField("redisfailover", rf.ObjectMeta.Name).WithField("namespace", rf.ObjectMeta.Namespace).Infof("Making pod %s slave of %s", pod.Name, masterIP) 169 if err := r.redisClient.MakeSlaveOfWithPort(pod.Status.PodIP, masterIP, port, password); err != nil { 170 r.logger.WithField("redisfailover", rf.ObjectMeta.Name).WithField("namespace", rf.ObjectMeta.Namespace).Errorf("Make slave failed, slave ip: %s, master ip: %s, error: %v", pod.Status.PodIP, masterIP, err) 171 return err 172 } 173 174 err = r.setSlaveLabelIfNecessary(rf.Namespace, pod) 175 if err != nil { 176 return err 177 } 178 } 179 } 180 return nil 181 } 182 183 // SetExternalMasterOnAll puts all redis nodes as a slave of a given master outside of 184 // the current RedisFailover instance 185 func (r *RedisFailoverHealer) SetExternalMasterOnAll(masterIP, masterPort string, rf *redisfailoverv1.RedisFailover) error { 186 ssp, err := r.k8sService.GetStatefulSetPods(rf.Namespace, GetRedisName(rf)) 187 if err != nil { 188 return err 189 } 190 191 password, err := k8s.GetRedisPassword(r.k8sService, rf) 192 if err != nil { 193 return err 194 } 195 196 for _, pod := range ssp.Items { 197 r.logger.WithField("redisfailover", rf.ObjectMeta.Name).WithField("namespace", rf.ObjectMeta.Namespace).Infof("Making pod %s slave of %s:%s", pod.Name, masterIP, masterPort) 198 if err := r.redisClient.MakeSlaveOfWithPort(pod.Status.PodIP, masterIP, masterPort, password); err != nil { 199 return err 200 } 201 202 } 203 return nil 204 } 205 206 // NewSentinelMonitor changes the master that Sentinel has to monitor 207 func (r *RedisFailoverHealer) NewSentinelMonitor(ip string, monitor string, rf *redisfailoverv1.RedisFailover) error { 208 quorum := strconv.Itoa(int(getQuorum(rf))) 209 210 password, err := k8s.GetRedisPassword(r.k8sService, rf) 211 if err != nil { 212 return err 213 } 214 215 port := getRedisPort(rf.Spec.Redis.Port) 216 return r.redisClient.MonitorRedisWithPort(ip, monitor, port, quorum, password) 217 } 218 219 // NewSentinelMonitorWithPort changes the master that Sentinel has to monitor by the provided IP and Port 220 func (r *RedisFailoverHealer) NewSentinelMonitorWithPort(ip string, monitor string, monitorPort string, rf *redisfailoverv1.RedisFailover) error { 221 quorum := strconv.Itoa(int(getQuorum(rf))) 222 223 password, err := k8s.GetRedisPassword(r.k8sService, rf) 224 if err != nil { 225 return err 226 } 227 228 return r.redisClient.MonitorRedisWithPort(ip, monitor, monitorPort, quorum, password) 229 } 230 231 // RestoreSentinel clear the number of sentinels on memory 232 func (r *RedisFailoverHealer) RestoreSentinel(ip string) error { 233 r.logger.Debugf("Restoring sentinel %s", ip) 234 return r.redisClient.ResetSentinel(ip) 235 } 236 237 // SetSentinelCustomConfig will call sentinel to set the configuration given in config 238 func (r *RedisFailoverHealer) SetSentinelCustomConfig(ip string, rf *redisfailoverv1.RedisFailover) error { 239 r.logger.WithField("redisfailover", rf.ObjectMeta.Name).WithField("namespace", rf.ObjectMeta.Namespace).Debugf("Setting the custom config on sentinel %s...", ip) 240 return r.redisClient.SetCustomSentinelConfig(ip, rf.Spec.Sentinel.CustomConfig) 241 } 242 243 // SetRedisCustomConfig will call redis to set the configuration given in config 244 func (r *RedisFailoverHealer) SetRedisCustomConfig(ip string, rf *redisfailoverv1.RedisFailover) error { 245 r.logger.WithField("redisfailover", rf.ObjectMeta.Name).WithField("namespace", rf.ObjectMeta.Namespace).Debugf("Setting the custom config on redis %s...", ip) 246 247 password, err := k8s.GetRedisPassword(r.k8sService, rf) 248 if err != nil { 249 return err 250 } 251 252 port := getRedisPort(rf.Spec.Redis.Port) 253 return r.redisClient.SetCustomRedisConfig(ip, port, rf.Spec.Redis.CustomConfig, password) 254 } 255 256 // DeletePod delete a failing pod so kubernetes relaunch it again 257 func (r *RedisFailoverHealer) DeletePod(podName string, rFailover *redisfailoverv1.RedisFailover) error { 258 r.logger.WithField("redisfailover", rFailover.ObjectMeta.Name).WithField("namespace", rFailover.ObjectMeta.Namespace).Infof("Deleting pods %s...", podName) 259 return r.k8sService.DeletePod(rFailover.Namespace, podName) 260 }