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

     1  package redis
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"net"
     8  	"regexp"
     9  	"strconv"
    10  	"strings"
    11  
    12  	rediscli "github.com/go-redis/redis/v8"
    13  	"github.com/spotahome/redis-operator/log"
    14  	"github.com/spotahome/redis-operator/metrics"
    15  )
    16  
    17  // Client defines the functions neccesary to connect to redis and sentinel to get or set what we nned
    18  type Client interface {
    19  	GetNumberSentinelsInMemory(ip string) (int32, error)
    20  	GetNumberSentinelSlavesInMemory(ip string) (int32, error)
    21  	ResetSentinel(ip string) error
    22  	GetSlaveOf(ip, port, password string) (string, error)
    23  	IsMaster(ip, port, password string) (bool, error)
    24  	MonitorRedis(ip, monitor, quorum, password string) error
    25  	MonitorRedisWithPort(ip, monitor, port, quorum, password string) error
    26  	MakeMaster(ip, port, password string) error
    27  	MakeSlaveOf(ip, masterIP, password string) error
    28  	MakeSlaveOfWithPort(ip, masterIP, masterPort, password string) error
    29  	GetSentinelMonitor(ip string) (string, string, error)
    30  	SetCustomSentinelConfig(ip string, configs []string) error
    31  	SetCustomRedisConfig(ip string, port string, configs []string, password string) error
    32  	SlaveIsReady(ip, port, password string) (bool, error)
    33  	SentinelCheckQuorum(ip string) error
    34  }
    35  
    36  type client struct {
    37  	metricsRecorder metrics.Recorder
    38  }
    39  
    40  // New returns a redis client
    41  func New(metricsRecorder metrics.Recorder) Client {
    42  	return &client{
    43  		metricsRecorder: metricsRecorder,
    44  	}
    45  }
    46  
    47  const (
    48  	sentinelsNumberREString = "sentinels=([0-9]+)"
    49  	slaveNumberREString     = "slaves=([0-9]+)"
    50  	sentinelStatusREString  = "status=([a-z]+)"
    51  	redisMasterHostREString = "master_host:([0-9.]+)"
    52  	redisRoleMaster         = "role:master"
    53  	redisSyncing            = "master_sync_in_progress:1"
    54  	redisMasterSillPending  = "master_host:127.0.0.1"
    55  	redisLinkUp             = "master_link_status:up"
    56  	redisPort               = "6379"
    57  	sentinelPort            = "26379"
    58  	masterName              = "mymaster"
    59  )
    60  
    61  var (
    62  	sentinelNumberRE  = regexp.MustCompile(sentinelsNumberREString)
    63  	sentinelStatusRE  = regexp.MustCompile(sentinelStatusREString)
    64  	slaveNumberRE     = regexp.MustCompile(slaveNumberREString)
    65  	redisMasterHostRE = regexp.MustCompile(redisMasterHostREString)
    66  )
    67  
    68  // GetNumberSentinelsInMemory return the number of sentinels that the requested sentinel has
    69  func (c *client) GetNumberSentinelsInMemory(ip string) (int32, error) {
    70  	options := &rediscli.Options{
    71  		Addr:     net.JoinHostPort(ip, sentinelPort),
    72  		Password: "",
    73  		DB:       0,
    74  	}
    75  	rClient := rediscli.NewClient(options)
    76  	defer rClient.Close()
    77  	info, err := rClient.Info(context.TODO(), "sentinel").Result()
    78  	if err != nil {
    79  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, ip, metrics.GET_NUM_SENTINELS_IN_MEM, metrics.FAIL, getRedisError(err))
    80  		return 0, err
    81  	}
    82  	if err2 := isSentinelReady(info); err2 != nil {
    83  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, ip, metrics.GET_NUM_SENTINELS_IN_MEM, metrics.FAIL, metrics.SENTINEL_NOT_READY)
    84  		return 0, err2
    85  	}
    86  	match := sentinelNumberRE.FindStringSubmatch(info)
    87  	if len(match) == 0 {
    88  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, ip, metrics.GET_NUM_SENTINELS_IN_MEM, metrics.FAIL, metrics.REGEX_NOT_FOUND)
    89  		return 0, errors.New("seninel regex not found")
    90  	}
    91  	nSentinels, err := strconv.Atoi(match[1])
    92  	if err != nil {
    93  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, ip, metrics.GET_NUM_SENTINELS_IN_MEM, metrics.FAIL, metrics.MISC)
    94  		return 0, err
    95  	}
    96  	c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, ip, metrics.GET_NUM_SENTINELS_IN_MEM, metrics.SUCCESS, metrics.NOT_APPLICABLE)
    97  	return int32(nSentinels), nil
    98  }
    99  
   100  // GetNumberSentinelsInMemory return the number of sentinels that the requested sentinel has
   101  func (c *client) GetNumberSentinelSlavesInMemory(ip string) (int32, error) {
   102  	options := &rediscli.Options{
   103  		Addr:     net.JoinHostPort(ip, sentinelPort),
   104  		Password: "",
   105  		DB:       0,
   106  	}
   107  	rClient := rediscli.NewClient(options)
   108  	defer rClient.Close()
   109  	info, err := rClient.Info(context.TODO(), "sentinel").Result()
   110  	if err != nil {
   111  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, ip, metrics.GET_NUM_REDIS_SLAVES_IN_MEM, metrics.FAIL, getRedisError(err))
   112  		return 0, err
   113  	}
   114  	if err2 := isSentinelReady(info); err2 != nil {
   115  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, ip, metrics.GET_NUM_REDIS_SLAVES_IN_MEM, metrics.FAIL, metrics.SENTINEL_NOT_READY)
   116  		return 0, err2
   117  	}
   118  	match := slaveNumberRE.FindStringSubmatch(info)
   119  	if len(match) == 0 {
   120  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, ip, metrics.GET_NUM_REDIS_SLAVES_IN_MEM, metrics.FAIL, metrics.REGEX_NOT_FOUND)
   121  		return 0, errors.New("slaves regex not found")
   122  	}
   123  	nSlaves, err := strconv.Atoi(match[1])
   124  	if err != nil {
   125  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, ip, metrics.GET_NUM_REDIS_SLAVES_IN_MEM, metrics.FAIL, metrics.MISC)
   126  		return 0, err
   127  	}
   128  	c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, ip, metrics.GET_NUM_REDIS_SLAVES_IN_MEM, metrics.SUCCESS, metrics.NOT_APPLICABLE)
   129  	return int32(nSlaves), nil
   130  }
   131  
   132  func isSentinelReady(info string) error {
   133  	matchStatus := sentinelStatusRE.FindStringSubmatch(info)
   134  	if len(matchStatus) == 0 || matchStatus[1] != "ok" {
   135  		return errors.New("sentinels not ready")
   136  	}
   137  	return nil
   138  }
   139  
   140  // ResetSentinel sends a sentinel reset * for the given sentinel
   141  func (c *client) ResetSentinel(ip string) error {
   142  	options := &rediscli.Options{
   143  		Addr:     net.JoinHostPort(ip, sentinelPort),
   144  		Password: "",
   145  		DB:       0,
   146  	}
   147  	rClient := rediscli.NewClient(options)
   148  	defer rClient.Close()
   149  	cmd := rediscli.NewIntCmd(context.TODO(), "SENTINEL", "reset", "*")
   150  	err := rClient.Process(context.TODO(), cmd)
   151  	if err != nil {
   152  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, ip, metrics.RESET_SENTINEL, metrics.FAIL, getRedisError(err))
   153  		return err
   154  	}
   155  	_, err = cmd.Result()
   156  	if err != nil {
   157  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, ip, metrics.RESET_SENTINEL, metrics.FAIL, getRedisError(err))
   158  		return err
   159  	}
   160  	c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, ip, metrics.RESET_SENTINEL, metrics.SUCCESS, metrics.NOT_APPLICABLE)
   161  	return nil
   162  }
   163  
   164  // GetSlaveOf returns the master of the given redis, or nil if it's master
   165  func (c *client) GetSlaveOf(ip, port, password string) (string, error) {
   166  
   167  	options := &rediscli.Options{
   168  		Addr:     net.JoinHostPort(ip, port),
   169  		Password: password,
   170  		DB:       0,
   171  	}
   172  	rClient := rediscli.NewClient(options)
   173  	defer rClient.Close()
   174  	info, err := rClient.Info(context.TODO(), "replication").Result()
   175  	if err != nil {
   176  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_REDIS, ip, metrics.GET_SLAVE_OF, metrics.FAIL, getRedisError(err))
   177  		log.Errorf("error while getting masterIP : Failed to get info replication while querying redis instance %v", ip)
   178  		return "", err
   179  	}
   180  	match := redisMasterHostRE.FindStringSubmatch(info)
   181  	if len(match) == 0 {
   182  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_REDIS, ip, metrics.GET_SLAVE_OF, metrics.SUCCESS, metrics.NOT_APPLICABLE)
   183  		return "", nil
   184  	}
   185  	c.metricsRecorder.RecordRedisOperation(metrics.KIND_REDIS, ip, metrics.GET_SLAVE_OF, metrics.SUCCESS, metrics.NOT_APPLICABLE)
   186  	return match[1], nil
   187  }
   188  
   189  func (c *client) IsMaster(ip, port, password string) (bool, error) {
   190  	options := &rediscli.Options{
   191  		Addr:     net.JoinHostPort(ip, port),
   192  		Password: password,
   193  		DB:       0,
   194  	}
   195  	rClient := rediscli.NewClient(options)
   196  	defer rClient.Close()
   197  	info, err := rClient.Info(context.TODO(), "replication").Result()
   198  	if err != nil {
   199  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_REDIS, ip, metrics.IS_MASTER, metrics.FAIL, getRedisError(err))
   200  		return false, err
   201  	}
   202  	c.metricsRecorder.RecordRedisOperation(metrics.KIND_REDIS, ip, metrics.IS_MASTER, metrics.SUCCESS, metrics.NOT_APPLICABLE)
   203  	return strings.Contains(info, redisRoleMaster), nil
   204  }
   205  
   206  func (c *client) MonitorRedis(ip, monitor, quorum, password string) error {
   207  	return c.MonitorRedisWithPort(ip, monitor, redisPort, quorum, password)
   208  }
   209  
   210  func (c *client) MonitorRedisWithPort(ip, monitor, port, quorum, password string) error {
   211  	options := &rediscli.Options{
   212  		Addr:     net.JoinHostPort(ip, sentinelPort),
   213  		Password: "",
   214  		DB:       0,
   215  	}
   216  	rClient := rediscli.NewClient(options)
   217  	defer rClient.Close()
   218  	cmd := rediscli.NewBoolCmd(context.TODO(), "SENTINEL", "REMOVE", masterName)
   219  	_ = rClient.Process(context.TODO(), cmd)
   220  	// We'll continue even if it fails, the priority is to have the redises monitored
   221  	cmd = rediscli.NewBoolCmd(context.TODO(), "SENTINEL", "MONITOR", masterName, monitor, port, quorum)
   222  	err := rClient.Process(context.TODO(), cmd)
   223  	if err != nil {
   224  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_REDIS, ip, metrics.MONITOR_REDIS_WITH_PORT, metrics.FAIL, getRedisError(err))
   225  		return err
   226  	}
   227  	_, err = cmd.Result()
   228  	if err != nil {
   229  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_REDIS, ip, metrics.MONITOR_REDIS_WITH_PORT, metrics.FAIL, getRedisError(err))
   230  		return err
   231  	}
   232  
   233  	if password != "" {
   234  		cmd = rediscli.NewBoolCmd(context.TODO(), "SENTINEL", "SET", masterName, "auth-pass", password)
   235  		err := rClient.Process(context.TODO(), cmd)
   236  		if err != nil {
   237  			c.metricsRecorder.RecordRedisOperation(metrics.KIND_REDIS, ip, metrics.MONITOR_REDIS_WITH_PORT, metrics.FAIL, getRedisError(err))
   238  			return err
   239  		}
   240  		_, err = cmd.Result()
   241  		if err != nil {
   242  			c.metricsRecorder.RecordRedisOperation(metrics.KIND_REDIS, ip, metrics.MONITOR_REDIS_WITH_PORT, metrics.FAIL, getRedisError(err))
   243  			return err
   244  		}
   245  	}
   246  	c.metricsRecorder.RecordRedisOperation(metrics.KIND_REDIS, ip, metrics.MONITOR_REDIS_WITH_PORT, metrics.SUCCESS, metrics.NOT_APPLICABLE)
   247  	return nil
   248  }
   249  
   250  func (c *client) MakeMaster(ip string, port string, password string) error {
   251  	options := &rediscli.Options{
   252  		Addr:     net.JoinHostPort(ip, port),
   253  		Password: password,
   254  		DB:       0,
   255  	}
   256  	rClient := rediscli.NewClient(options)
   257  	defer rClient.Close()
   258  	if res := rClient.SlaveOf(context.TODO(), "NO", "ONE"); res.Err() != nil {
   259  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_REDIS, ip, metrics.MAKE_MASTER, metrics.FAIL, getRedisError(res.Err()))
   260  		return res.Err()
   261  	}
   262  	c.metricsRecorder.RecordRedisOperation(metrics.KIND_REDIS, ip, metrics.MAKE_MASTER, metrics.SUCCESS, metrics.NOT_APPLICABLE)
   263  	return nil
   264  }
   265  
   266  func (c *client) MakeSlaveOf(ip, masterIP, password string) error {
   267  	return c.MakeSlaveOfWithPort(ip, masterIP, redisPort, password)
   268  }
   269  
   270  func (c *client) MakeSlaveOfWithPort(ip, masterIP, masterPort, password string) error {
   271  	options := &rediscli.Options{
   272  		Addr:     net.JoinHostPort(ip, masterPort), // this is IP and Port for the RedisFailover redis
   273  		Password: password,
   274  		DB:       0,
   275  	}
   276  	rClient := rediscli.NewClient(options)
   277  	defer rClient.Close()
   278  	if res := rClient.SlaveOf(context.TODO(), masterIP, masterPort); res.Err() != nil {
   279  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_REDIS, ip, metrics.MAKE_SLAVE_OF, metrics.FAIL, getRedisError(res.Err()))
   280  		return res.Err()
   281  	}
   282  	c.metricsRecorder.RecordRedisOperation(metrics.KIND_REDIS, ip, metrics.MAKE_SLAVE_OF, metrics.SUCCESS, metrics.NOT_APPLICABLE)
   283  	return nil
   284  }
   285  
   286  func (c *client) GetSentinelMonitor(ip string) (string, string, error) {
   287  	options := &rediscli.Options{
   288  		Addr:     net.JoinHostPort(ip, sentinelPort),
   289  		Password: "",
   290  		DB:       0,
   291  	}
   292  	rClient := rediscli.NewClient(options)
   293  	defer rClient.Close()
   294  	cmd := rediscli.NewSliceCmd(context.TODO(), "SENTINEL", "master", masterName)
   295  	err := rClient.Process(context.TODO(), cmd)
   296  	if err != nil {
   297  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, ip, metrics.GET_SENTINEL_MONITOR, metrics.FAIL, getRedisError(err))
   298  		return "", "", err
   299  	}
   300  	res, err := cmd.Result()
   301  	if err != nil {
   302  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, ip, metrics.GET_SENTINEL_MONITOR, metrics.FAIL, getRedisError(err))
   303  		return "", "", err
   304  	}
   305  	masterIP := res[3].(string)
   306  	masterPort := res[5].(string)
   307  	c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, ip, metrics.GET_SENTINEL_MONITOR, metrics.SUCCESS, metrics.NOT_APPLICABLE)
   308  	return masterIP, masterPort, nil
   309  }
   310  
   311  func (c *client) SetCustomSentinelConfig(ip string, configs []string) error {
   312  	options := &rediscli.Options{
   313  		Addr:     net.JoinHostPort(ip, sentinelPort),
   314  		Password: "",
   315  		DB:       0,
   316  	}
   317  	rClient := rediscli.NewClient(options)
   318  	defer rClient.Close()
   319  
   320  	for _, config := range configs {
   321  		param, value, err := c.getConfigParameters(config)
   322  		if err != nil {
   323  			return err
   324  		}
   325  		if err := c.applySentinelConfig(param, value, rClient); err != nil {
   326  			return err
   327  		}
   328  	}
   329  	return nil
   330  }
   331  
   332  func (c *client) SentinelCheckQuorum(ip string) error {
   333  
   334  	options := &rediscli.Options{
   335  		Addr:     net.JoinHostPort(ip, sentinelPort),
   336  		Password: "",
   337  		DB:       0,
   338  	}
   339  	rClient := rediscli.NewSentinelClient(options)
   340  	defer rClient.Close()
   341  	cmd := rClient.CkQuorum(context.TODO(), masterName)
   342  	res, err := cmd.Result()
   343  
   344  	if err != nil {
   345  		log.Warnf("Unable to get result for CKQUORUM comand")
   346  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, ip, metrics.CHECK_SENTINEL_QUORUM, metrics.FAIL, getRedisError(err))
   347  		return err
   348  	}
   349  	log.Debugf("SentinelCheckQuorum cmd result: %s", res)
   350  	s := strings.Split(res, " ")
   351  	status := s[0]
   352  	quorum := s[1]
   353  
   354  	if status == "" {
   355  		log.Errorf("quorum command result unexpected output")
   356  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, ip, metrics.CHECK_SENTINEL_QUORUM, metrics.FAIL, "quorum command result unexpected output")
   357  		return fmt.Errorf("quorum command result unexpected output")
   358  	}
   359  	if status == "(error)" && quorum == "NOQUORUM" {
   360  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, ip, metrics.CHECK_SENTINEL_QUORUM, metrics.SUCCESS, "NOQUORUM")
   361  		return fmt.Errorf("quorum Not available")
   362  
   363  	} else if status == "OK" {
   364  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, ip, metrics.CHECK_SENTINEL_QUORUM, metrics.SUCCESS, "QUORUM")
   365  		return nil
   366  	} else {
   367  		log.Errorf("quorum command status unexpected !!!")
   368  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, ip, metrics.CHECK_SENTINEL_QUORUM, metrics.FAIL, "quorum command status unexpected output")
   369  		return fmt.Errorf("quorum status unexpected %s", status)
   370  	}
   371  
   372  }
   373  func (c *client) SetCustomRedisConfig(ip string, port string, configs []string, password string) error {
   374  	options := &rediscli.Options{
   375  		Addr:     net.JoinHostPort(ip, port),
   376  		Password: password,
   377  		DB:       0,
   378  	}
   379  	rClient := rediscli.NewClient(options)
   380  	defer rClient.Close()
   381  
   382  	for _, config := range configs {
   383  		param, value, err := c.getConfigParameters(config)
   384  		if err != nil {
   385  			return err
   386  		}
   387  		// If the configuration is an empty line , it will result in an incorrect configSet, which will not run properly down the line.
   388  		// `config set save ""` should support
   389  		if strings.TrimSpace(param) == "" {
   390  			continue
   391  		}
   392  		if err := c.applyRedisConfig(param, value, rClient); err != nil {
   393  			return err
   394  		}
   395  	}
   396  	return nil
   397  }
   398  
   399  func (c *client) applyRedisConfig(parameter string, value string, rClient *rediscli.Client) error {
   400  	result := rClient.ConfigSet(context.TODO(), parameter, value)
   401  	if nil != result.Err() {
   402  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_REDIS, strings.Split(rClient.Options().Addr, ":")[0], metrics.APPLY_REDIS_CONFIG, metrics.FAIL, getRedisError(result.Err()))
   403  		return result.Err()
   404  	}
   405  	c.metricsRecorder.RecordRedisOperation(metrics.KIND_REDIS, strings.Split(rClient.Options().Addr, ":")[0], metrics.APPLY_REDIS_CONFIG, metrics.SUCCESS, metrics.NOT_APPLICABLE)
   406  	return result.Err()
   407  }
   408  
   409  func (c *client) applySentinelConfig(parameter string, value string, rClient *rediscli.Client) error {
   410  	cmd := rediscli.NewStatusCmd(context.TODO(), "SENTINEL", "set", masterName, parameter, value)
   411  	err := rClient.Process(context.TODO(), cmd)
   412  	if err != nil {
   413  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, strings.Split(rClient.Options().Addr, ":")[0], metrics.APPLY_SENTINEL_CONFIG, metrics.FAIL, getRedisError(err))
   414  		return err
   415  	}
   416  	c.metricsRecorder.RecordRedisOperation(metrics.KIND_SENTINEL, strings.Split(rClient.Options().Addr, ":")[0], metrics.APPLY_SENTINEL_CONFIG, metrics.SUCCESS, metrics.NOT_APPLICABLE)
   417  	return cmd.Err()
   418  }
   419  
   420  func (c *client) getConfigParameters(config string) (parameter string, value string, err error) {
   421  	s := strings.Split(config, " ")
   422  	if len(s) < 2 {
   423  		return "", "", fmt.Errorf("configuration '%s' malformed", config)
   424  	}
   425  	if len(s) == 2 && s[1] == `""` {
   426  		return s[0], "", nil
   427  	}
   428  	return s[0], strings.Join(s[1:], " "), nil
   429  }
   430  
   431  func (c *client) SlaveIsReady(ip, port, password string) (bool, error) {
   432  	options := &rediscli.Options{
   433  		Addr:     net.JoinHostPort(ip, port),
   434  		Password: password,
   435  		DB:       0,
   436  	}
   437  	rClient := rediscli.NewClient(options)
   438  	defer rClient.Close()
   439  	info, err := rClient.Info(context.TODO(), "replication").Result()
   440  	if err != nil {
   441  		c.metricsRecorder.RecordRedisOperation(metrics.KIND_REDIS, strings.Split(rClient.Options().Addr, ":")[0], metrics.SLAVE_IS_READY, metrics.FAIL, getRedisError(err))
   442  		return false, err
   443  	}
   444  
   445  	ok := !strings.Contains(info, redisSyncing) &&
   446  		!strings.Contains(info, redisMasterSillPending) &&
   447  		strings.Contains(info, redisLinkUp)
   448  	c.metricsRecorder.RecordRedisOperation(metrics.KIND_REDIS, strings.Split(rClient.Options().Addr, ":")[0], metrics.SLAVE_IS_READY, metrics.SUCCESS, metrics.NOT_APPLICABLE)
   449  	return ok, nil
   450  }
   451  
   452  func getRedisError(err error) string {
   453  	if strings.Contains(err.Error(), "NOAUTH") {
   454  		return metrics.NOAUTH
   455  	} else if strings.Contains(err.Error(), "WRONGPASS") {
   456  		return metrics.WRONG_PASSWORD_USED
   457  	} else if strings.Contains(err.Error(), "NOPERM") {
   458  		return metrics.NOPERM
   459  	} else if strings.Contains(err.Error(), "i/o timeout") {
   460  		return metrics.IO_TIMEOUT
   461  	} else if strings.Contains(err.Error(), "connection refused") {
   462  		return metrics.CONNECTION_REFUSED
   463  	} else {
   464  		return "MISC"
   465  	}
   466  }