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  }