func (cnx *AdminConnections) ValidateResp(resp *redis.Resp, addr, errMessage string) error {
	if resp == nil {
		cnx.log.Error(fmt.Errorf("%s: unable to connect to node %s", errMessage, addr), "")
		return fmt.Errorf("%s: unable to connect to node %s", errMessage, addr)
	}
	if resp.Err != nil {
		cnx.handleError(addr, resp.Err)
		cnx.log.Error(resp.Err, fmt.Sprintf("%s: unexpected error on node %s", errMessage, addr))
		return fmt.Errorf("%s: unexpected error on node %s: %v", errMessage, addr, resp.Err)
	}
	return nil
}
ValidateResp check the redis resp, eventually reconnect on connection error
in case of error, customize the error, log it and return it
package redisutil
import (
	"errors"
	"fmt"
	"math/rand"
	"net"
	"os"
	"time"
	"github.com/go-logr/logr"
	"github.com/mediocregopher/radix.v2/redis"
	"github.com/ucloud/redis-cluster-operator/pkg/utils"
)
const (
	defaultClientTimeout = 2 * time.Second
	defaultClientName    = ""
	ErrNotFound = "unable to find a node to connect"
)
type IAdminConnections interface {
	Add(addr string) error
	Remove(addr string)
	Get(addr string) (IClient, error)
	GetRandom() (IClient, error)
	GetDifferentFrom(addr string) (IClient, error)
	GetAll() map[string]IClient
	GetSelected(addrs []string) map[string]IClient
	Reconnect(addr string) error
	AddAll(addrs []string)
	ReplaceAll(addrs []string)
	ValidateResp(resp *redis.Resp, addr, errMessage string) error
	ValidatePipeResp(c IClient, addr, errMessage string) bool
	Reset()
	GetAUTH() (string, bool)
}
type AdminConnections struct {
	clients           map[string]IClient
	connectionTimeout time.Duration
	commandsMapping   map[string]string
	clientName        string
	password          string
	log               logr.Logger
}
func init() {
	rand.Seed(time.Now().UnixNano())
}
func NewAdminConnections(addrs []string, options *AdminOptions, log logr.Logger) IAdminConnections {
	cnx := &AdminConnections{
		clients:           make(map[string]IClient),
		connectionTimeout: defaultClientTimeout,
		commandsMapping:   make(map[string]string),
		clientName:        defaultClientName,
		log:               log,
	}
	if options != nil {
		if options.ConnectionTimeout != 0 {
			cnx.connectionTimeout = options.ConnectionTimeout
		}
		if _, err := os.Stat(options.RenameCommandsFile); err == nil {
			cnx.commandsMapping = utils.BuildCommandReplaceMapping(options.RenameCommandsFile, cnx.log)
		}
		cnx.clientName = options.ClientName
		cnx.password = options.Password
	}
	cnx.AddAll(addrs)
	return cnx
}
func (cnx *AdminConnections) GetAUTH() (string, bool) {
	if len(cnx.password) > 0 {
		return cnx.password, true
	}
	return "", false
}
func (cnx *AdminConnections) Reconnect(addr string) error {
	cnx.log.Info(fmt.Sprintf("reconnecting to %s", addr))
	cnx.Remove(addr)
	return cnx.Add(addr)
}
func (cnx *AdminConnections) AddAll(addrs []string) {
	for _, addr := range addrs {
		cnx.Add(addr)
	}
}
func (cnx *AdminConnections) ReplaceAll(addrs []string) {
	cnx.Reset()
	cnx.AddAll(addrs)
}
func (cnx *AdminConnections) Reset() {
	for _, c := range cnx.clients {
		c.Close()
	}
	cnx.clients = map[string]IClient{}
}
func (cnx *AdminConnections) GetAll() map[string]IClient {
	return cnx.clients
}
func (cnx *AdminConnections) GetSelected(addrs []string) map[string]IClient {
	clientsSelected := make(map[string]IClient)
	for _, addr := range addrs {
		if client, ok := cnx.clients[addr]; ok {
			clientsSelected[addr] = client
		}
	}
	return clientsSelected
}
func (cnx *AdminConnections) Close() {
	for _, c := range cnx.clients {
		c.Close()
	}
}
func (cnx *AdminConnections) Add(addr string) error {
	_, err := cnx.Update(addr)
	return err
}
func (cnx *AdminConnections) Remove(addr string) {
	if c, ok := cnx.clients[addr]; ok {
		c.Close()
		delete(cnx.clients, addr)
	}
}
func (cnx *AdminConnections) Update(addr string) (IClient, error) {
	if c, ok := cnx.clients[addr]; ok {
		c.Close()
	}
	c, err := cnx.connect(addr)
	if err == nil && c != nil {
		cnx.clients[addr] = c
	} else {
		cnx.log.Info(fmt.Sprintf("cannot connect to %s ", addr))
	}
	return c, err
}
func (cnx *AdminConnections) Get(addr string) (IClient, error) {
	if c, ok := cnx.clients[addr]; ok {
		return c, nil
	}
	c, err := cnx.connect(addr)
	if err == nil && c != nil {
		cnx.clients[addr] = c
	}
	return c, err
}
func (cnx *AdminConnections) GetRandom() (IClient, error) {
	_, c, err := cnx.getRandomKeyClient()
	return c, err
}
func (cnx *AdminConnections) GetDifferentFrom(addr string) (IClient, error) {
	if len(cnx.clients) == 1 {
		for a, c := range cnx.clients {
			if a != addr {
				return c, nil
			}
			return nil, errors.New(ErrNotFound)
		}
	}
	for {
		a, c, err := cnx.getRandomKeyClient()
		if err != nil {
			return nil, err
		}
		if a != addr {
			return c, nil
		}
	}
}
func (cnx *AdminConnections) getRandomKeyClient() (string, IClient, error) {
	nbClient := len(cnx.clients)
	if nbClient == 0 {
		return "", nil, errors.New(ErrNotFound)
	}
	randNumber := rand.Intn(nbClient)
	for k, c := range cnx.clients {
		if randNumber == 0 {
			return k, c, nil
		}
		randNumber--
	}
	return "", nil, errors.New(ErrNotFound)
}
func (cnx *AdminConnections) handleError(addr string, err error) bool {
	if err == nil {
		return false
	} else if netError, ok := err.(net.Error); ok && netError.Timeout() {
		cnx.Reconnect(addr)
		return true
	}
	switch err.(type) {
	case *net.OpError:
		cnx.Reconnect(addr)
		return true
	}
	return false
}
func (cnx *AdminConnections) connect(addr string) (IClient, error) {
	c, err := NewClient(addr, cnx.password, cnx.connectionTimeout, cnx.commandsMapping)
	if err != nil {
		return nil, err
	}
	if cnx.clientName != "" {
		resp := c.Cmd("CLIENT", "SETNAME", cnx.clientName)
		return c, cnx.ValidateResp(resp, addr, "Unable to run command CLIENT SETNAME")
	}
	return c, nil
}