...

Source file src/github.com/redis/go-redis/v9/sentinel_test.go

Documentation: github.com/redis/go-redis/v9

     1  package redis_test
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"errors"
     7  	"net"
     8  	"sort"
     9  	"testing"
    10  	"time"
    11  
    12  	. "github.com/bsm/ginkgo/v2"
    13  	. "github.com/bsm/gomega"
    14  	"github.com/redis/go-redis/v9"
    15  )
    16  
    17  var _ = Describe("Sentinel PROTO 2", func() {
    18  	var client *redis.Client
    19  	BeforeEach(func() {
    20  		client = redis.NewFailoverClient(&redis.FailoverOptions{
    21  			MasterName:    sentinelName,
    22  			SentinelAddrs: sentinelAddrs,
    23  			MaxRetries:    -1,
    24  			Protocol:      2,
    25  		})
    26  		Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
    27  	})
    28  
    29  	AfterEach(func() {
    30  		_ = client.Close()
    31  	})
    32  
    33  	It("should sentinel client PROTO 2", func() {
    34  		val, err := client.Do(ctx, "HELLO").Result()
    35  		Expect(err).NotTo(HaveOccurred())
    36  		Expect(val).Should(ContainElements("proto", int64(2)))
    37  	})
    38  })
    39  
    40  var _ = Describe("Sentinel resolution", func() {
    41  	It("should resolve master without context exhaustion", func() {
    42  		shortCtx, cancel := context.WithTimeout(ctx, 500*time.Millisecond)
    43  		defer cancel()
    44  
    45  		client := redis.NewFailoverClient(&redis.FailoverOptions{
    46  			MasterName:    sentinelName,
    47  			SentinelAddrs: sentinelAddrs,
    48  			MaxRetries:    -1,
    49  		})
    50  
    51  		err := client.Ping(shortCtx).Err()
    52  		Expect(err).NotTo(HaveOccurred(), "expected master to resolve without context exhaustion")
    53  
    54  		_ = client.Close()
    55  	})
    56  })
    57  
    58  var _ = Describe("Sentinel", func() {
    59  	var client *redis.Client
    60  	var master *redis.Client
    61  	var sentinel *redis.SentinelClient
    62  
    63  	BeforeEach(func() {
    64  		client = redis.NewFailoverClient(&redis.FailoverOptions{
    65  			ClientName:    "sentinel_hi",
    66  			MasterName:    sentinelName,
    67  			SentinelAddrs: sentinelAddrs,
    68  			MaxRetries:    -1,
    69  		})
    70  		Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
    71  
    72  		sentinel = redis.NewSentinelClient(&redis.Options{
    73  			Addr:       ":" + sentinelPort1,
    74  			MaxRetries: -1,
    75  		})
    76  
    77  		addr, err := sentinel.GetMasterAddrByName(ctx, sentinelName).Result()
    78  		Expect(err).NotTo(HaveOccurred())
    79  
    80  		master = redis.NewClient(&redis.Options{
    81  			Addr:       net.JoinHostPort(addr[0], addr[1]),
    82  			MaxRetries: -1,
    83  		})
    84  
    85  		// Wait until slaves are picked up by sentinel.
    86  		Eventually(func() string {
    87  			return sentinel1.Info(ctx).Val()
    88  		}, "20s", "100ms").Should(ContainSubstring("slaves=2"))
    89  		Eventually(func() string {
    90  			return sentinel2.Info(ctx).Val()
    91  		}, "20s", "100ms").Should(ContainSubstring("slaves=2"))
    92  		Eventually(func() string {
    93  			return sentinel3.Info(ctx).Val()
    94  		}, "20s", "100ms").Should(ContainSubstring("slaves=2"))
    95  	})
    96  
    97  	AfterEach(func() {
    98  		_ = client.Close()
    99  		_ = master.Close()
   100  		_ = sentinel.Close()
   101  	})
   102  
   103  	It("should facilitate failover", func() {
   104  		// Set value on master.
   105  		err := client.Set(ctx, "foo", "master", 0).Err()
   106  		Expect(err).NotTo(HaveOccurred())
   107  
   108  		// Verify.
   109  		val, err := client.Get(ctx, "foo").Result()
   110  		Expect(err).NotTo(HaveOccurred())
   111  		Expect(val).To(Equal("master"))
   112  
   113  		// Verify master->slaves sync.
   114  		var slavesAddr []string
   115  		Eventually(func() []string {
   116  			slavesAddr = redis.GetSlavesAddrByName(ctx, sentinel, sentinelName)
   117  			return slavesAddr
   118  		}, "20s", "50ms").Should(HaveLen(2))
   119  		Eventually(func() bool {
   120  			sync := true
   121  			for _, addr := range slavesAddr {
   122  				slave := redis.NewClient(&redis.Options{
   123  					Addr:       addr,
   124  					MaxRetries: -1,
   125  				})
   126  				sync = slave.Get(ctx, "foo").Val() == "master"
   127  				_ = slave.Close()
   128  			}
   129  			return sync
   130  		}, "20s", "50ms").Should(BeTrue())
   131  
   132  		// Create subscription.
   133  		pub := client.Subscribe(ctx, "foo")
   134  		ch := pub.Channel()
   135  
   136  		// Kill master.
   137  		/*
   138  			err = master.Shutdown(ctx).Err()
   139  			Expect(err).NotTo(HaveOccurred())
   140  			Eventually(func() error {
   141  				return master.Ping(ctx).Err()
   142  			}, "20s", "50ms").Should(HaveOccurred())
   143  		*/
   144  
   145  		// Check that client picked up new master.
   146  		Eventually(func() string {
   147  			return client.Get(ctx, "foo").Val()
   148  		}, "20s", "100ms").Should(Equal("master"))
   149  
   150  		// Check if subscription is renewed.
   151  		var msg *redis.Message
   152  		Eventually(func() <-chan *redis.Message {
   153  			_ = client.Publish(ctx, "foo", "hello").Err()
   154  			return ch
   155  		}, "20s", "100ms").Should(Receive(&msg))
   156  		Expect(msg.Channel).To(Equal("foo"))
   157  		Expect(msg.Payload).To(Equal("hello"))
   158  		Expect(pub.Close()).NotTo(HaveOccurred())
   159  	})
   160  
   161  	It("supports DB selection", func() {
   162  		Expect(client.Close()).NotTo(HaveOccurred())
   163  
   164  		client = redis.NewFailoverClient(&redis.FailoverOptions{
   165  			MasterName:    sentinelName,
   166  			SentinelAddrs: sentinelAddrs,
   167  			DB:            1,
   168  		})
   169  		err := client.Ping(ctx).Err()
   170  		Expect(err).NotTo(HaveOccurred())
   171  	})
   172  
   173  	It("should sentinel client setname", func() {
   174  		Expect(client.Ping(ctx).Err()).NotTo(HaveOccurred())
   175  		val, err := client.ClientList(ctx).Result()
   176  		Expect(err).NotTo(HaveOccurred())
   177  		Expect(val).Should(ContainSubstring("name=sentinel_hi"))
   178  	})
   179  
   180  	It("should sentinel client PROTO 3", func() {
   181  		val, err := client.Do(ctx, "HELLO").Result()
   182  		Expect(err).NotTo(HaveOccurred())
   183  		Expect(val).Should(HaveKeyWithValue("proto", int64(3)))
   184  	})
   185  })
   186  
   187  var _ = Describe("NewFailoverClusterClient PROTO 2", func() {
   188  	var client *redis.ClusterClient
   189  
   190  	BeforeEach(func() {
   191  		client = redis.NewFailoverClusterClient(&redis.FailoverOptions{
   192  			MasterName:    sentinelName,
   193  			SentinelAddrs: sentinelAddrs,
   194  			Protocol:      2,
   195  
   196  			RouteRandomly: true,
   197  		})
   198  		Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
   199  	})
   200  
   201  	AfterEach(func() {
   202  		_ = client.Close()
   203  	})
   204  
   205  	It("should sentinel cluster PROTO 2", func() {
   206  		_ = client.ForEachShard(ctx, func(ctx context.Context, c *redis.Client) error {
   207  			val, err := client.Do(ctx, "HELLO").Result()
   208  			Expect(err).NotTo(HaveOccurred())
   209  			Expect(val).Should(ContainElements("proto", int64(2)))
   210  			return nil
   211  		})
   212  	})
   213  })
   214  
   215  var _ = Describe("NewFailoverClusterClient", func() {
   216  	var client *redis.ClusterClient
   217  	var master *redis.Client
   218  
   219  	BeforeEach(func() {
   220  		client = redis.NewFailoverClusterClient(&redis.FailoverOptions{
   221  			ClientName:    "sentinel_cluster_hi",
   222  			MasterName:    sentinelName,
   223  			SentinelAddrs: sentinelAddrs,
   224  
   225  			RouteRandomly: true,
   226  			DB:            1,
   227  		})
   228  		Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
   229  
   230  		sentinel := redis.NewSentinelClient(&redis.Options{
   231  			Addr:       ":" + sentinelPort1,
   232  			MaxRetries: -1,
   233  		})
   234  
   235  		addr, err := sentinel.GetMasterAddrByName(ctx, sentinelName).Result()
   236  		Expect(err).NotTo(HaveOccurred())
   237  
   238  		master = redis.NewClient(&redis.Options{
   239  			Addr:       net.JoinHostPort(addr[0], addr[1]),
   240  			MaxRetries: -1,
   241  		})
   242  
   243  		// Wait until slaves are picked up by sentinel.
   244  		Eventually(func() string {
   245  			return sentinel1.Info(ctx).Val()
   246  		}, "20s", "100ms").Should(ContainSubstring("slaves=2"))
   247  		Eventually(func() string {
   248  			return sentinel2.Info(ctx).Val()
   249  		}, "20s", "100ms").Should(ContainSubstring("slaves=2"))
   250  		Eventually(func() string {
   251  			return sentinel3.Info(ctx).Val()
   252  		}, "20s", "100ms").Should(ContainSubstring("slaves=2"))
   253  	})
   254  
   255  	AfterEach(func() {
   256  		_ = client.Close()
   257  		_ = master.Close()
   258  	})
   259  
   260  	It("should facilitate failover", func() {
   261  		// Set value.
   262  		err := client.Set(ctx, "foo", "master", 0).Err()
   263  		Expect(err).NotTo(HaveOccurred())
   264  
   265  		for i := 0; i < 100; i++ {
   266  			// Verify.
   267  			Eventually(func() string {
   268  				return client.Get(ctx, "foo").Val()
   269  			}, "20s", "1ms").Should(Equal("master"))
   270  		}
   271  
   272  		// Create subscription.
   273  		sub := client.Subscribe(ctx, "foo")
   274  		ch := sub.Channel()
   275  
   276  		// Kill master.
   277  		/*
   278  			err = master.Shutdown(ctx).Err()
   279  			Expect(err).NotTo(HaveOccurred())
   280  			Eventually(func() error {
   281  				return master.Ping(ctx).Err()
   282  			}, "20s", "100ms").Should(HaveOccurred())
   283  		*/
   284  
   285  		// Check that client picked up new master.
   286  		Eventually(func() string {
   287  			return client.Get(ctx, "foo").Val()
   288  		}, "20s", "100ms").Should(Equal("master"))
   289  
   290  		// Check if subscription is renewed.
   291  		var msg *redis.Message
   292  		Eventually(func() <-chan *redis.Message {
   293  			_ = client.Publish(ctx, "foo", "hello").Err()
   294  			return ch
   295  		}, "20s", "100ms").Should(Receive(&msg))
   296  		Expect(msg.Channel).To(Equal("foo"))
   297  		Expect(msg.Payload).To(Equal("hello"))
   298  		Expect(sub.Close()).NotTo(HaveOccurred())
   299  
   300  	})
   301  
   302  	It("should sentinel cluster client setname", func() {
   303  		err := client.ForEachShard(ctx, func(ctx context.Context, c *redis.Client) error {
   304  			return c.Ping(ctx).Err()
   305  		})
   306  		Expect(err).NotTo(HaveOccurred())
   307  
   308  		_ = client.ForEachShard(ctx, func(ctx context.Context, c *redis.Client) error {
   309  			val, err := c.ClientList(ctx).Result()
   310  			Expect(err).NotTo(HaveOccurred())
   311  			Expect(val).Should(ContainSubstring("name=sentinel_cluster_hi"))
   312  			return nil
   313  		})
   314  	})
   315  
   316  	It("should sentinel cluster client db", func() {
   317  		err := client.ForEachShard(ctx, func(ctx context.Context, c *redis.Client) error {
   318  			return c.Ping(ctx).Err()
   319  		})
   320  		Expect(err).NotTo(HaveOccurred())
   321  
   322  		_ = client.ForEachShard(ctx, func(ctx context.Context, c *redis.Client) error {
   323  			clientInfo, err := c.ClientInfo(ctx).Result()
   324  			Expect(err).NotTo(HaveOccurred())
   325  			Expect(clientInfo.DB).To(Equal(1))
   326  			return nil
   327  		})
   328  	})
   329  
   330  	It("should sentinel cluster PROTO 3", func() {
   331  		_ = client.ForEachShard(ctx, func(ctx context.Context, c *redis.Client) error {
   332  			val, err := client.Do(ctx, "HELLO").Result()
   333  			Expect(err).NotTo(HaveOccurred())
   334  			Expect(val).Should(HaveKeyWithValue("proto", int64(3)))
   335  			return nil
   336  		})
   337  	})
   338  })
   339  
   340  var _ = Describe("SentinelAclAuth", func() {
   341  	const (
   342  		aclSentinelUsername = "sentinel-user"
   343  		aclSentinelPassword = "sentinel-pass"
   344  	)
   345  
   346  	var client *redis.Client
   347  	var sentinel *redis.SentinelClient
   348  	sentinels := func() []*redis.Client {
   349  		return []*redis.Client{sentinel1, sentinel2, sentinel3}
   350  	}
   351  
   352  	BeforeEach(func() {
   353  		authCmd := redis.NewStatusCmd(ctx, "ACL", "SETUSER", aclSentinelUsername, "ON",
   354  			">"+aclSentinelPassword, "-@all", "+auth", "+client|getname", "+client|id", "+client|setname",
   355  			"+command", "+hello", "+ping", "+client|setinfo", "+role", "+sentinel|get-master-addr-by-name", "+sentinel|master",
   356  			"+sentinel|myid", "+sentinel|replicas", "+sentinel|sentinels")
   357  
   358  		for _, process := range sentinels() {
   359  			err := process.Process(ctx, authCmd)
   360  			Expect(err).NotTo(HaveOccurred())
   361  		}
   362  
   363  		client = redis.NewFailoverClient(&redis.FailoverOptions{
   364  			MasterName:       sentinelName,
   365  			SentinelAddrs:    sentinelAddrs,
   366  			MaxRetries:       -1,
   367  			SentinelUsername: aclSentinelUsername,
   368  			SentinelPassword: aclSentinelPassword,
   369  		})
   370  
   371  		Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
   372  
   373  		sentinel = redis.NewSentinelClient(&redis.Options{
   374  			Addr:       sentinelAddrs[0],
   375  			MaxRetries: -1,
   376  			Username:   aclSentinelUsername,
   377  			Password:   aclSentinelPassword,
   378  		})
   379  
   380  		_, err := sentinel.GetMasterAddrByName(ctx, sentinelName).Result()
   381  		Expect(err).NotTo(HaveOccurred())
   382  
   383  		// Wait until sentinels are picked up by each other.
   384  		for _, process := range sentinels() {
   385  			Eventually(func() string {
   386  				return process.Info(ctx).Val()
   387  			}, "20s", "100ms").Should(ContainSubstring("sentinels=3"))
   388  		}
   389  	})
   390  
   391  	AfterEach(func() {
   392  		unauthCommand := redis.NewStatusCmd(ctx, "ACL", "DELUSER", aclSentinelUsername)
   393  
   394  		for _, process := range sentinels() {
   395  			err := process.Process(ctx, unauthCommand)
   396  			Expect(err).NotTo(HaveOccurred())
   397  		}
   398  
   399  		_ = client.Close()
   400  		_ = sentinel.Close()
   401  	})
   402  
   403  	It("should still facilitate operations", func() {
   404  		err := client.Set(ctx, "wow", "acl-auth", 0).Err()
   405  		Expect(err).NotTo(HaveOccurred())
   406  
   407  		val, err := client.Get(ctx, "wow").Result()
   408  		Expect(err).NotTo(HaveOccurred())
   409  		Expect(val).To(Equal("acl-auth"))
   410  	})
   411  })
   412  
   413  func TestParseFailoverURL(t *testing.T) {
   414  	cases := []struct {
   415  		url string
   416  		o   *redis.FailoverOptions
   417  		err error
   418  	}{
   419  		{
   420  			url: "redis://localhost:6379?master_name=test",
   421  			o:   &redis.FailoverOptions{SentinelAddrs: []string{"localhost:6379"}, MasterName: "test"},
   422  		},
   423  		{
   424  			url: "redis://localhost:6379/5?master_name=test",
   425  			o:   &redis.FailoverOptions{SentinelAddrs: []string{"localhost:6379"}, MasterName: "test", DB: 5},
   426  		},
   427  		{
   428  			url: "rediss://localhost:6379/5?master_name=test",
   429  			o: &redis.FailoverOptions{SentinelAddrs: []string{"localhost:6379"}, MasterName: "test", DB: 5,
   430  				TLSConfig: &tls.Config{
   431  					ServerName: "localhost",
   432  				}},
   433  		},
   434  		{
   435  			url: "rediss://localhost:6379/5?master_name=test&skip_verify=true",
   436  			o: &redis.FailoverOptions{SentinelAddrs: []string{"localhost:6379"}, MasterName: "test", DB: 5,
   437  				TLSConfig: &tls.Config{
   438  					ServerName:         "localhost",
   439  					InsecureSkipVerify: true,
   440  				}},
   441  		},
   442  		{
   443  			url: "redis://localhost:6379/5?master_name=test&db=2",
   444  			o:   &redis.FailoverOptions{SentinelAddrs: []string{"localhost:6379"}, MasterName: "test", DB: 2},
   445  		},
   446  		{
   447  			url: "redis://localhost:6379/5?addr=localhost:6380&addr=localhost:6381",
   448  			o:   &redis.FailoverOptions{SentinelAddrs: []string{"localhost:6380", "localhost:6379", "localhost:6381"}, DB: 5},
   449  		},
   450  		{
   451  			url: "redis://foo:bar@localhost:6379/5?addr=localhost:6380",
   452  			o: &redis.FailoverOptions{SentinelAddrs: []string{"localhost:6380", "localhost:6379"},
   453  				SentinelUsername: "foo", SentinelPassword: "bar", DB: 5},
   454  		},
   455  		{
   456  			url: "redis://:bar@localhost:6379/5?addr=localhost:6380",
   457  			o: &redis.FailoverOptions{SentinelAddrs: []string{"localhost:6380", "localhost:6379"},
   458  				SentinelUsername: "", SentinelPassword: "bar", DB: 5},
   459  		},
   460  		{
   461  			url: "redis://foo@localhost:6379/5?addr=localhost:6380",
   462  			o: &redis.FailoverOptions{SentinelAddrs: []string{"localhost:6380", "localhost:6379"},
   463  				SentinelUsername: "foo", SentinelPassword: "", DB: 5},
   464  		},
   465  		{
   466  			url: "redis://foo:bar@localhost:6379/5?addr=localhost:6380&dial_timeout=3",
   467  			o: &redis.FailoverOptions{SentinelAddrs: []string{"localhost:6380", "localhost:6379"},
   468  				SentinelUsername: "foo", SentinelPassword: "bar", DB: 5, DialTimeout: 3 * time.Second},
   469  		},
   470  		{
   471  			url: "redis://foo:bar@localhost:6379/5?addr=localhost:6380&dial_timeout=3s",
   472  			o: &redis.FailoverOptions{SentinelAddrs: []string{"localhost:6380", "localhost:6379"},
   473  				SentinelUsername: "foo", SentinelPassword: "bar", DB: 5, DialTimeout: 3 * time.Second},
   474  		},
   475  		{
   476  			url: "redis://foo:bar@localhost:6379/5?addr=localhost:6380&dial_timeout=3ms",
   477  			o: &redis.FailoverOptions{SentinelAddrs: []string{"localhost:6380", "localhost:6379"},
   478  				SentinelUsername: "foo", SentinelPassword: "bar", DB: 5, DialTimeout: 3 * time.Millisecond},
   479  		},
   480  		{
   481  			url: "redis://foo:bar@localhost:6379/5?addr=localhost:6380&dial_timeout=3&pool_fifo=true",
   482  			o: &redis.FailoverOptions{SentinelAddrs: []string{"localhost:6380", "localhost:6379"},
   483  				SentinelUsername: "foo", SentinelPassword: "bar", DB: 5, DialTimeout: 3 * time.Second, PoolFIFO: true},
   484  		},
   485  		{
   486  			url: "redis://localhost:6379/5?addr=localhost:6380&dial_timeout=3&pool_fifo=false",
   487  			o: &redis.FailoverOptions{SentinelAddrs: []string{"localhost:6380", "localhost:6379"},
   488  				DB: 5, DialTimeout: 3 * time.Second, PoolFIFO: false},
   489  		},
   490  		{
   491  			url: "redis://localhost:6379/5?addr=localhost:6380&dial_timeout=3&pool_fifo",
   492  			o: &redis.FailoverOptions{SentinelAddrs: []string{"localhost:6380", "localhost:6379"},
   493  				DB: 5, DialTimeout: 3 * time.Second, PoolFIFO: false},
   494  		},
   495  		{
   496  			url: "redis://localhost:6379/5?addr=localhost:6380&dial_timeout",
   497  			o: &redis.FailoverOptions{SentinelAddrs: []string{"localhost:6380", "localhost:6379"},
   498  				DB: 5, DialTimeout: 0},
   499  		},
   500  		{
   501  			url: "redis://localhost:6379/5?addr=localhost:6380&dial_timeout=0",
   502  			o: &redis.FailoverOptions{SentinelAddrs: []string{"localhost:6380", "localhost:6379"},
   503  				DB: 5, DialTimeout: -1},
   504  		},
   505  		{
   506  			url: "redis://localhost:6379/5?addr=localhost:6380&dial_timeout=-1",
   507  			o: &redis.FailoverOptions{SentinelAddrs: []string{"localhost:6380", "localhost:6379"},
   508  				DB: 5, DialTimeout: -1},
   509  		},
   510  		{
   511  			url: "redis://localhost:6379/5?addr=localhost:6380&dial_timeout=-2",
   512  			o: &redis.FailoverOptions{SentinelAddrs: []string{"localhost:6380", "localhost:6379"},
   513  				DB: 5, DialTimeout: -1},
   514  		},
   515  		{
   516  			url: "redis://localhost:6379/5?addr=localhost:6380&dial_timeout=",
   517  			o: &redis.FailoverOptions{SentinelAddrs: []string{"localhost:6380", "localhost:6379"},
   518  				DB: 5, DialTimeout: 0},
   519  		},
   520  		{
   521  			url: "redis://localhost:6379/5?addr=localhost:6380&dial_timeout=0&abc=5",
   522  			o: &redis.FailoverOptions{SentinelAddrs: []string{"localhost:6380", "localhost:6379"},
   523  				DB: 5, DialTimeout: -1},
   524  			err: errors.New("redis: unexpected option: abc"),
   525  		},
   526  		{
   527  			url: "http://google.com",
   528  			err: errors.New("redis: invalid URL scheme: http"),
   529  		},
   530  		{
   531  			url: "redis://localhost/1/2/3/4",
   532  			err: errors.New("redis: invalid URL path: /1/2/3/4"),
   533  		},
   534  		{
   535  			url: "12345",
   536  			err: errors.New("redis: invalid URL scheme: "),
   537  		},
   538  		{
   539  			url: "redis://localhost/database",
   540  			err: errors.New(`redis: invalid database number: "database"`),
   541  		},
   542  	}
   543  
   544  	for i := range cases {
   545  		tc := cases[i]
   546  		t.Run(tc.url, func(t *testing.T) {
   547  			t.Parallel()
   548  
   549  			actual, err := redis.ParseFailoverURL(tc.url)
   550  			if tc.err == nil && err != nil {
   551  				t.Fatalf("unexpected error: %q", err)
   552  				return
   553  			}
   554  			if tc.err != nil && err == nil {
   555  				t.Fatalf("got nil, expected %q", tc.err)
   556  				return
   557  			}
   558  			if tc.err != nil && err != nil {
   559  				if tc.err.Error() != err.Error() {
   560  					t.Fatalf("got %q, expected %q", err, tc.err)
   561  				}
   562  				return
   563  			}
   564  			compareFailoverOptions(t, actual, tc.o)
   565  		})
   566  	}
   567  }
   568  
   569  func compareFailoverOptions(t *testing.T, a, e *redis.FailoverOptions) {
   570  	if a.MasterName != e.MasterName {
   571  		t.Errorf("MasterName got %q, want %q", a.MasterName, e.MasterName)
   572  	}
   573  	compareSlices(t, a.SentinelAddrs, e.SentinelAddrs, "SentinelAddrs")
   574  	if a.ClientName != e.ClientName {
   575  		t.Errorf("ClientName got %q, want %q", a.ClientName, e.ClientName)
   576  	}
   577  	if a.SentinelUsername != e.SentinelUsername {
   578  		t.Errorf("SentinelUsername got %q, want %q", a.SentinelUsername, e.SentinelUsername)
   579  	}
   580  	if a.SentinelPassword != e.SentinelPassword {
   581  		t.Errorf("SentinelPassword got %q, want %q", a.SentinelPassword, e.SentinelPassword)
   582  	}
   583  	if a.RouteByLatency != e.RouteByLatency {
   584  		t.Errorf("RouteByLatency got %v, want %v", a.RouteByLatency, e.RouteByLatency)
   585  	}
   586  	if a.RouteRandomly != e.RouteRandomly {
   587  		t.Errorf("RouteRandomly got %v, want %v", a.RouteRandomly, e.RouteRandomly)
   588  	}
   589  	if a.ReplicaOnly != e.ReplicaOnly {
   590  		t.Errorf("ReplicaOnly got %v, want %v", a.ReplicaOnly, e.ReplicaOnly)
   591  	}
   592  	if a.UseDisconnectedReplicas != e.UseDisconnectedReplicas {
   593  		t.Errorf("UseDisconnectedReplicas got %v, want %v", a.UseDisconnectedReplicas, e.UseDisconnectedReplicas)
   594  	}
   595  	if a.Protocol != e.Protocol {
   596  		t.Errorf("Protocol got %v, want %v", a.Protocol, e.Protocol)
   597  	}
   598  	if a.Username != e.Username {
   599  		t.Errorf("Username got %q, want %q", a.Username, e.Username)
   600  	}
   601  	if a.Password != e.Password {
   602  		t.Errorf("Password got %q, want %q", a.Password, e.Password)
   603  	}
   604  	if a.DB != e.DB {
   605  		t.Errorf("DB got %v, want %v", a.DB, e.DB)
   606  	}
   607  	if a.MaxRetries != e.MaxRetries {
   608  		t.Errorf("MaxRetries got %v, want %v", a.MaxRetries, e.MaxRetries)
   609  	}
   610  	if a.MinRetryBackoff != e.MinRetryBackoff {
   611  		t.Errorf("MinRetryBackoff got %v, want %v", a.MinRetryBackoff, e.MinRetryBackoff)
   612  	}
   613  	if a.MaxRetryBackoff != e.MaxRetryBackoff {
   614  		t.Errorf("MaxRetryBackoff got %v, want %v", a.MaxRetryBackoff, e.MaxRetryBackoff)
   615  	}
   616  	if a.DialTimeout != e.DialTimeout {
   617  		t.Errorf("DialTimeout got %v, want %v", a.DialTimeout, e.DialTimeout)
   618  	}
   619  	if a.ReadTimeout != e.ReadTimeout {
   620  		t.Errorf("ReadTimeout got %v, want %v", a.ReadTimeout, e.ReadTimeout)
   621  	}
   622  	if a.WriteTimeout != e.WriteTimeout {
   623  		t.Errorf("WriteTimeout got %v, want %v", a.WriteTimeout, e.WriteTimeout)
   624  	}
   625  	if a.ContextTimeoutEnabled != e.ContextTimeoutEnabled {
   626  		t.Errorf("ContentTimeoutEnabled got %v, want %v", a.ContextTimeoutEnabled, e.ContextTimeoutEnabled)
   627  	}
   628  	if a.PoolFIFO != e.PoolFIFO {
   629  		t.Errorf("PoolFIFO got %v, want %v", a.PoolFIFO, e.PoolFIFO)
   630  	}
   631  	if a.PoolSize != e.PoolSize {
   632  		t.Errorf("PoolSize got %v, want %v", a.PoolSize, e.PoolSize)
   633  	}
   634  	if a.PoolTimeout != e.PoolTimeout {
   635  		t.Errorf("PoolTimeout got %v, want %v", a.PoolTimeout, e.PoolTimeout)
   636  	}
   637  	if a.MinIdleConns != e.MinIdleConns {
   638  		t.Errorf("MinIdleConns got %v, want %v", a.MinIdleConns, e.MinIdleConns)
   639  	}
   640  	if a.MaxIdleConns != e.MaxIdleConns {
   641  		t.Errorf("MaxIdleConns got %v, want %v", a.MaxIdleConns, e.MaxIdleConns)
   642  	}
   643  	if a.MaxActiveConns != e.MaxActiveConns {
   644  		t.Errorf("MaxActiveConns got %v, want %v", a.MaxActiveConns, e.MaxActiveConns)
   645  	}
   646  	if a.ConnMaxIdleTime != e.ConnMaxIdleTime {
   647  		t.Errorf("ConnMaxIdleTime got %v, want %v", a.ConnMaxIdleTime, e.ConnMaxIdleTime)
   648  	}
   649  	if a.ConnMaxLifetime != e.ConnMaxLifetime {
   650  		t.Errorf("ConnMaxLifeTime got %v, want %v", a.ConnMaxLifetime, e.ConnMaxLifetime)
   651  	}
   652  	if a.DisableIdentity != e.DisableIdentity {
   653  		t.Errorf("DisableIdentity got %v, want %v", a.DisableIdentity, e.DisableIdentity)
   654  	}
   655  	if a.IdentitySuffix != e.IdentitySuffix {
   656  		t.Errorf("IdentitySuffix got %v, want %v", a.IdentitySuffix, e.IdentitySuffix)
   657  	}
   658  	if a.UnstableResp3 != e.UnstableResp3 {
   659  		t.Errorf("UnstableResp3 got %v, want %v", a.UnstableResp3, e.UnstableResp3)
   660  	}
   661  	if (a.TLSConfig == nil && e.TLSConfig != nil) || (a.TLSConfig != nil && e.TLSConfig == nil) {
   662  		t.Errorf("TLSConfig error")
   663  	}
   664  	if a.TLSConfig != nil && e.TLSConfig != nil {
   665  		if a.TLSConfig.ServerName != e.TLSConfig.ServerName {
   666  			t.Errorf("TLSConfig.ServerName got %q, want %q", a.TLSConfig.ServerName, e.TLSConfig.ServerName)
   667  		}
   668  	}
   669  }
   670  
   671  func compareSlices(t *testing.T, a, b []string, name string) {
   672  	sort.Slice(a, func(i, j int) bool { return a[i] < a[j] })
   673  	sort.Slice(b, func(i, j int) bool { return b[i] < b[j] })
   674  	if len(a) != len(b) {
   675  		t.Errorf("%s got %q, want %q", name, a, b)
   676  	}
   677  	for i := range a {
   678  		if a[i] != b[i] {
   679  			t.Errorf("%s got %q, want %q", name, a, b)
   680  		}
   681  	}
   682  }
   683  

View as plain text