github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/xds/internal/balancer/ringhash/ringhash_test.go (about)

     1  /*
     2   *
     3   * Copyright 2021 gRPC authors.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   */
    18  
    19  package ringhash
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/google/go-cmp/cmp"
    28  	"github.com/google/go-cmp/cmp/cmpopts"
    29  	"github.com/hxx258456/ccgo/grpc/attributes"
    30  	"github.com/hxx258456/ccgo/grpc/balancer"
    31  	"github.com/hxx258456/ccgo/grpc/balancer/weightedroundrobin"
    32  	"github.com/hxx258456/ccgo/grpc/connectivity"
    33  	"github.com/hxx258456/ccgo/grpc/internal/testutils"
    34  	"github.com/hxx258456/ccgo/grpc/resolver"
    35  )
    36  
    37  var (
    38  	cmpOpts = cmp.Options{
    39  		cmp.AllowUnexported(testutils.TestSubConn{}, ringEntry{}, subConn{}),
    40  		cmpopts.IgnoreFields(subConn{}, "mu"),
    41  	}
    42  )
    43  
    44  const (
    45  	defaultTestTimeout      = 10 * time.Second
    46  	defaultTestShortTimeout = 10 * time.Millisecond
    47  
    48  	testBackendAddrsCount = 12
    49  )
    50  
    51  var (
    52  	testBackendAddrStrs []string
    53  	testConfig          = &LBConfig{MinRingSize: 1, MaxRingSize: 10}
    54  )
    55  
    56  func init() {
    57  	for i := 0; i < testBackendAddrsCount; i++ {
    58  		testBackendAddrStrs = append(testBackendAddrStrs, fmt.Sprintf("%d.%d.%d.%d:%d", i, i, i, i, i))
    59  	}
    60  }
    61  
    62  func ctxWithHash(h uint64) context.Context {
    63  	return SetRequestHash(context.Background(), h)
    64  }
    65  
    66  // setupTest creates the balancer, and does an initial sanity check.
    67  func setupTest(t *testing.T, addrs []resolver.Address) (*testutils.TestClientConn, balancer.Balancer, balancer.Picker) {
    68  	t.Helper()
    69  	cc := testutils.NewTestClientConn(t)
    70  	builder := balancer.Get(Name)
    71  	b := builder.Build(cc, balancer.BuildOptions{})
    72  	if b == nil {
    73  		t.Fatalf("builder.Build(%s) failed and returned nil", Name)
    74  	}
    75  	if err := b.UpdateClientConnState(balancer.ClientConnState{
    76  		ResolverState:  resolver.State{Addresses: addrs},
    77  		BalancerConfig: testConfig,
    78  	}); err != nil {
    79  		t.Fatalf("UpdateClientConnState returned err: %v", err)
    80  	}
    81  
    82  	for _, addr := range addrs {
    83  		addr1 := <-cc.NewSubConnAddrsCh
    84  		if want := []resolver.Address{addr}; !cmp.Equal(addr1, want, cmp.AllowUnexported(attributes.Attributes{})) {
    85  			t.Fatalf("got unexpected new subconn addrs: %v", cmp.Diff(addr1, want, cmp.AllowUnexported(attributes.Attributes{})))
    86  		}
    87  		sc1 := <-cc.NewSubConnCh
    88  		// All the SubConns start in Idle, and should not Connect().
    89  		select {
    90  		case <-sc1.(*testutils.TestSubConn).ConnectCh:
    91  			t.Errorf("unexpected Connect() from SubConn %v", sc1)
    92  		case <-time.After(defaultTestShortTimeout):
    93  		}
    94  	}
    95  
    96  	// Should also have a picker, with all SubConns in Idle.
    97  	p1 := <-cc.NewPickerCh
    98  	return cc, b, p1
    99  }
   100  
   101  func TestOneSubConn(t *testing.T) {
   102  	wantAddr1 := resolver.Address{Addr: testBackendAddrStrs[0]}
   103  	cc, b, p0 := setupTest(t, []resolver.Address{wantAddr1})
   104  	ring0 := p0.(*picker).ring
   105  
   106  	firstHash := ring0.items[0].hash
   107  	// firstHash-1 will pick the first (and only) SubConn from the ring.
   108  	testHash := firstHash - 1
   109  	// The first pick should be queued, and should trigger Connect() on the only
   110  	// SubConn.
   111  	if _, err := p0.Pick(balancer.PickInfo{Ctx: ctxWithHash(testHash)}); err != balancer.ErrNoSubConnAvailable {
   112  		t.Fatalf("first pick returned err %v, want %v", err, balancer.ErrNoSubConnAvailable)
   113  	}
   114  	sc0 := ring0.items[0].sc.sc
   115  	select {
   116  	case <-sc0.(*testutils.TestSubConn).ConnectCh:
   117  	case <-time.After(defaultTestTimeout):
   118  		t.Errorf("timeout waiting for Connect() from SubConn %v", sc0)
   119  	}
   120  
   121  	// Send state updates to Ready.
   122  	b.UpdateSubConnState(sc0, balancer.SubConnState{ConnectivityState: connectivity.Connecting})
   123  	b.UpdateSubConnState(sc0, balancer.SubConnState{ConnectivityState: connectivity.Ready})
   124  
   125  	// Test pick with one backend.
   126  	p1 := <-cc.NewPickerCh
   127  	for i := 0; i < 5; i++ {
   128  		gotSCSt, _ := p1.Pick(balancer.PickInfo{Ctx: ctxWithHash(testHash)})
   129  		if !cmp.Equal(gotSCSt.SubConn, sc0, cmp.AllowUnexported(testutils.TestSubConn{})) {
   130  			t.Fatalf("picker.Pick, got %v, want SubConn=%v", gotSCSt, sc0)
   131  		}
   132  	}
   133  }
   134  
   135  // TestThreeBackendsAffinity covers that there are 3 SubConns, RPCs with the
   136  // same hash always pick the same SubConn. When the one picked is down, another
   137  // one will be picked.
   138  func TestThreeSubConnsAffinity(t *testing.T) {
   139  	wantAddrs := []resolver.Address{
   140  		{Addr: testBackendAddrStrs[0]},
   141  		{Addr: testBackendAddrStrs[1]},
   142  		{Addr: testBackendAddrStrs[2]},
   143  	}
   144  	cc, b, p0 := setupTest(t, wantAddrs)
   145  	// This test doesn't update addresses, so this ring will be used by all the
   146  	// pickers.
   147  	ring0 := p0.(*picker).ring
   148  
   149  	firstHash := ring0.items[0].hash
   150  	// firstHash+1 will pick the second SubConn from the ring.
   151  	testHash := firstHash + 1
   152  	// The first pick should be queued, and should trigger Connect() on the only
   153  	// SubConn.
   154  	if _, err := p0.Pick(balancer.PickInfo{Ctx: ctxWithHash(testHash)}); err != balancer.ErrNoSubConnAvailable {
   155  		t.Fatalf("first pick returned err %v, want %v", err, balancer.ErrNoSubConnAvailable)
   156  	}
   157  	// The picked SubConn should be the second in the ring.
   158  	sc0 := ring0.items[1].sc.sc
   159  	select {
   160  	case <-sc0.(*testutils.TestSubConn).ConnectCh:
   161  	case <-time.After(defaultTestTimeout):
   162  		t.Errorf("timeout waiting for Connect() from SubConn %v", sc0)
   163  	}
   164  
   165  	// Send state updates to Ready.
   166  	b.UpdateSubConnState(sc0, balancer.SubConnState{ConnectivityState: connectivity.Connecting})
   167  	b.UpdateSubConnState(sc0, balancer.SubConnState{ConnectivityState: connectivity.Ready})
   168  	p1 := <-cc.NewPickerCh
   169  	for i := 0; i < 5; i++ {
   170  		gotSCSt, _ := p1.Pick(balancer.PickInfo{Ctx: ctxWithHash(testHash)})
   171  		if !cmp.Equal(gotSCSt.SubConn, sc0, cmp.AllowUnexported(testutils.TestSubConn{})) {
   172  			t.Fatalf("picker.Pick, got %v, want SubConn=%v", gotSCSt, sc0)
   173  		}
   174  	}
   175  
   176  	// Turn down the subConn in use.
   177  	b.UpdateSubConnState(sc0, balancer.SubConnState{ConnectivityState: connectivity.TransientFailure})
   178  	p2 := <-cc.NewPickerCh
   179  	// Pick with the same hash should be queued, because the SubConn after the
   180  	// first picked is Idle.
   181  	if _, err := p2.Pick(balancer.PickInfo{Ctx: ctxWithHash(testHash)}); err != balancer.ErrNoSubConnAvailable {
   182  		t.Fatalf("first pick returned err %v, want %v", err, balancer.ErrNoSubConnAvailable)
   183  	}
   184  
   185  	// The third SubConn in the ring should connect.
   186  	sc1 := ring0.items[2].sc.sc
   187  	select {
   188  	case <-sc1.(*testutils.TestSubConn).ConnectCh:
   189  	case <-time.After(defaultTestTimeout):
   190  		t.Errorf("timeout waiting for Connect() from SubConn %v", sc1)
   191  	}
   192  
   193  	// Send state updates to Ready.
   194  	b.UpdateSubConnState(sc1, balancer.SubConnState{ConnectivityState: connectivity.Connecting})
   195  	b.UpdateSubConnState(sc1, balancer.SubConnState{ConnectivityState: connectivity.Ready})
   196  	// New picks should all return this SubConn.
   197  	p3 := <-cc.NewPickerCh
   198  	for i := 0; i < 5; i++ {
   199  		gotSCSt, _ := p3.Pick(balancer.PickInfo{Ctx: ctxWithHash(testHash)})
   200  		if !cmp.Equal(gotSCSt.SubConn, sc1, cmp.AllowUnexported(testutils.TestSubConn{})) {
   201  			t.Fatalf("picker.Pick, got %v, want SubConn=%v", gotSCSt, sc1)
   202  		}
   203  	}
   204  
   205  	// Now, after backoff, the first picked SubConn will turn Idle.
   206  	b.UpdateSubConnState(sc0, balancer.SubConnState{ConnectivityState: connectivity.Idle})
   207  	// The picks above should have queued Connect() for the first picked
   208  	// SubConn, so this Idle state change will trigger a Connect().
   209  	select {
   210  	case <-sc0.(*testutils.TestSubConn).ConnectCh:
   211  	case <-time.After(defaultTestTimeout):
   212  		t.Errorf("timeout waiting for Connect() from SubConn %v", sc0)
   213  	}
   214  
   215  	// After the first picked SubConn turn Ready, new picks should return it
   216  	// again (even though the second picked SubConn is also Ready).
   217  	b.UpdateSubConnState(sc0, balancer.SubConnState{ConnectivityState: connectivity.Connecting})
   218  	b.UpdateSubConnState(sc0, balancer.SubConnState{ConnectivityState: connectivity.Ready})
   219  	p4 := <-cc.NewPickerCh
   220  	for i := 0; i < 5; i++ {
   221  		gotSCSt, _ := p4.Pick(balancer.PickInfo{Ctx: ctxWithHash(testHash)})
   222  		if !cmp.Equal(gotSCSt.SubConn, sc0, cmp.AllowUnexported(testutils.TestSubConn{})) {
   223  			t.Fatalf("picker.Pick, got %v, want SubConn=%v", gotSCSt, sc0)
   224  		}
   225  	}
   226  }
   227  
   228  // TestThreeBackendsAffinity covers that there are 3 SubConns, RPCs with the
   229  // same hash always pick the same SubConn. Then try different hash to pick
   230  // another backend, and verify the first hash still picks the first backend.
   231  func TestThreeSubConnsAffinityMultiple(t *testing.T) {
   232  	wantAddrs := []resolver.Address{
   233  		{Addr: testBackendAddrStrs[0]},
   234  		{Addr: testBackendAddrStrs[1]},
   235  		{Addr: testBackendAddrStrs[2]},
   236  	}
   237  	cc, b, p0 := setupTest(t, wantAddrs)
   238  	// This test doesn't update addresses, so this ring will be used by all the
   239  	// pickers.
   240  	ring0 := p0.(*picker).ring
   241  
   242  	firstHash := ring0.items[0].hash
   243  	// firstHash+1 will pick the second SubConn from the ring.
   244  	testHash := firstHash + 1
   245  	// The first pick should be queued, and should trigger Connect() on the only
   246  	// SubConn.
   247  	if _, err := p0.Pick(balancer.PickInfo{Ctx: ctxWithHash(testHash)}); err != balancer.ErrNoSubConnAvailable {
   248  		t.Fatalf("first pick returned err %v, want %v", err, balancer.ErrNoSubConnAvailable)
   249  	}
   250  	sc0 := ring0.items[1].sc.sc
   251  	select {
   252  	case <-sc0.(*testutils.TestSubConn).ConnectCh:
   253  	case <-time.After(defaultTestTimeout):
   254  		t.Errorf("timeout waiting for Connect() from SubConn %v", sc0)
   255  	}
   256  
   257  	// Send state updates to Ready.
   258  	b.UpdateSubConnState(sc0, balancer.SubConnState{ConnectivityState: connectivity.Connecting})
   259  	b.UpdateSubConnState(sc0, balancer.SubConnState{ConnectivityState: connectivity.Ready})
   260  
   261  	// First hash should always pick sc0.
   262  	p1 := <-cc.NewPickerCh
   263  	for i := 0; i < 5; i++ {
   264  		gotSCSt, _ := p1.Pick(balancer.PickInfo{Ctx: ctxWithHash(testHash)})
   265  		if !cmp.Equal(gotSCSt.SubConn, sc0, cmp.AllowUnexported(testutils.TestSubConn{})) {
   266  			t.Fatalf("picker.Pick, got %v, want SubConn=%v", gotSCSt, sc0)
   267  		}
   268  	}
   269  
   270  	secondHash := ring0.items[1].hash
   271  	// secondHash+1 will pick the third SubConn from the ring.
   272  	testHash2 := secondHash + 1
   273  	if _, err := p0.Pick(balancer.PickInfo{Ctx: ctxWithHash(testHash2)}); err != balancer.ErrNoSubConnAvailable {
   274  		t.Fatalf("first pick returned err %v, want %v", err, balancer.ErrNoSubConnAvailable)
   275  	}
   276  	sc1 := ring0.items[2].sc.sc
   277  	select {
   278  	case <-sc1.(*testutils.TestSubConn).ConnectCh:
   279  	case <-time.After(defaultTestTimeout):
   280  		t.Errorf("timeout waiting for Connect() from SubConn %v", sc1)
   281  	}
   282  	b.UpdateSubConnState(sc1, balancer.SubConnState{ConnectivityState: connectivity.Connecting})
   283  	b.UpdateSubConnState(sc1, balancer.SubConnState{ConnectivityState: connectivity.Ready})
   284  
   285  	// With the new generated picker, hash2 always picks sc1.
   286  	p2 := <-cc.NewPickerCh
   287  	for i := 0; i < 5; i++ {
   288  		gotSCSt, _ := p2.Pick(balancer.PickInfo{Ctx: ctxWithHash(testHash2)})
   289  		if !cmp.Equal(gotSCSt.SubConn, sc1, cmp.AllowUnexported(testutils.TestSubConn{})) {
   290  			t.Fatalf("picker.Pick, got %v, want SubConn=%v", gotSCSt, sc1)
   291  		}
   292  	}
   293  	// But the first hash still picks sc0.
   294  	for i := 0; i < 5; i++ {
   295  		gotSCSt, _ := p2.Pick(balancer.PickInfo{Ctx: ctxWithHash(testHash)})
   296  		if !cmp.Equal(gotSCSt.SubConn, sc0, cmp.AllowUnexported(testutils.TestSubConn{})) {
   297  			t.Fatalf("picker.Pick, got %v, want SubConn=%v", gotSCSt, sc0)
   298  		}
   299  	}
   300  }
   301  
   302  func TestAddrWeightChange(t *testing.T) {
   303  	wantAddrs := []resolver.Address{
   304  		{Addr: testBackendAddrStrs[0]},
   305  		{Addr: testBackendAddrStrs[1]},
   306  		{Addr: testBackendAddrStrs[2]},
   307  	}
   308  	cc, b, p0 := setupTest(t, wantAddrs)
   309  	ring0 := p0.(*picker).ring
   310  
   311  	if err := b.UpdateClientConnState(balancer.ClientConnState{
   312  		ResolverState:  resolver.State{Addresses: wantAddrs},
   313  		BalancerConfig: nil,
   314  	}); err != nil {
   315  		t.Fatalf("UpdateClientConnState returned err: %v", err)
   316  	}
   317  	select {
   318  	case <-cc.NewPickerCh:
   319  		t.Fatalf("unexpected picker after UpdateClientConn with the same addresses")
   320  	case <-time.After(defaultTestShortTimeout):
   321  	}
   322  
   323  	// Delete an address, should send a new Picker.
   324  	if err := b.UpdateClientConnState(balancer.ClientConnState{
   325  		ResolverState: resolver.State{Addresses: []resolver.Address{
   326  			{Addr: testBackendAddrStrs[0]},
   327  			{Addr: testBackendAddrStrs[1]},
   328  		}},
   329  		BalancerConfig: nil,
   330  	}); err != nil {
   331  		t.Fatalf("UpdateClientConnState returned err: %v", err)
   332  	}
   333  	var p1 balancer.Picker
   334  	select {
   335  	case p1 = <-cc.NewPickerCh:
   336  	case <-time.After(defaultTestTimeout):
   337  		t.Fatalf("timeout waiting for picker after UpdateClientConn with different addresses")
   338  	}
   339  	ring1 := p1.(*picker).ring
   340  	if ring1 == ring0 {
   341  		t.Fatalf("new picker after removing address has the same ring as before, want different")
   342  	}
   343  
   344  	// Another update with the same addresses, but different weight.
   345  	if err := b.UpdateClientConnState(balancer.ClientConnState{
   346  		ResolverState: resolver.State{Addresses: []resolver.Address{
   347  			{Addr: testBackendAddrStrs[0]},
   348  			weightedroundrobin.SetAddrInfo(
   349  				resolver.Address{Addr: testBackendAddrStrs[1]},
   350  				weightedroundrobin.AddrInfo{Weight: 2}),
   351  		}},
   352  		BalancerConfig: nil,
   353  	}); err != nil {
   354  		t.Fatalf("UpdateClientConnState returned err: %v", err)
   355  	}
   356  	var p2 balancer.Picker
   357  	select {
   358  	case p2 = <-cc.NewPickerCh:
   359  	case <-time.After(defaultTestTimeout):
   360  		t.Fatalf("timeout waiting for picker after UpdateClientConn with different addresses")
   361  	}
   362  	if p2.(*picker).ring == ring1 {
   363  		t.Fatalf("new picker after changing address weight has the same ring as before, want different")
   364  	}
   365  }
   366  
   367  // TestSubConnToConnectWhenOverallTransientFailure covers the situation when the
   368  // overall state is TransientFailure, the SubConns turning Idle will be
   369  // triggered to Connect(). But not when the overall state is not
   370  // TransientFailure.
   371  func TestSubConnToConnectWhenOverallTransientFailure(t *testing.T) {
   372  	wantAddrs := []resolver.Address{
   373  		{Addr: testBackendAddrStrs[0]},
   374  		{Addr: testBackendAddrStrs[1]},
   375  		{Addr: testBackendAddrStrs[2]},
   376  	}
   377  	_, b, p0 := setupTest(t, wantAddrs)
   378  	ring0 := p0.(*picker).ring
   379  
   380  	// Turn all SubConns to TransientFailure.
   381  	for _, it := range ring0.items {
   382  		b.UpdateSubConnState(it.sc.sc, balancer.SubConnState{ConnectivityState: connectivity.TransientFailure})
   383  	}
   384  
   385  	// The next one turning Idle should Connect().
   386  	sc0 := ring0.items[0].sc.sc
   387  	b.UpdateSubConnState(sc0, balancer.SubConnState{ConnectivityState: connectivity.Idle})
   388  	select {
   389  	case <-sc0.(*testutils.TestSubConn).ConnectCh:
   390  	case <-time.After(defaultTestTimeout):
   391  		t.Errorf("timeout waiting for Connect() from SubConn %v", sc0)
   392  	}
   393  
   394  	// If this SubConn is ready. Other SubConns turning Idle will not Connect().
   395  	b.UpdateSubConnState(sc0, balancer.SubConnState{ConnectivityState: connectivity.Connecting})
   396  	b.UpdateSubConnState(sc0, balancer.SubConnState{ConnectivityState: connectivity.Ready})
   397  
   398  	// The third SubConn in the ring should connect.
   399  	sc1 := ring0.items[1].sc.sc
   400  	b.UpdateSubConnState(sc1, balancer.SubConnState{ConnectivityState: connectivity.Idle})
   401  	select {
   402  	case <-sc1.(*testutils.TestSubConn).ConnectCh:
   403  		t.Errorf("unexpected Connect() from SubConn %v", sc1)
   404  	case <-time.After(defaultTestShortTimeout):
   405  	}
   406  }
   407  
   408  func TestConnectivityStateEvaluatorRecordTransition(t *testing.T) {
   409  	tests := []struct {
   410  		name     string
   411  		from, to []connectivity.State
   412  		want     connectivity.State
   413  	}{
   414  		{
   415  			name: "one ready",
   416  			from: []connectivity.State{connectivity.Idle},
   417  			to:   []connectivity.State{connectivity.Ready},
   418  			want: connectivity.Ready,
   419  		},
   420  		{
   421  			name: "one connecting",
   422  			from: []connectivity.State{connectivity.Idle},
   423  			to:   []connectivity.State{connectivity.Connecting},
   424  			want: connectivity.Connecting,
   425  		},
   426  		{
   427  			name: "one ready one transient failure",
   428  			from: []connectivity.State{connectivity.Idle, connectivity.Idle},
   429  			to:   []connectivity.State{connectivity.Ready, connectivity.TransientFailure},
   430  			want: connectivity.Ready,
   431  		},
   432  		{
   433  			name: "one connecting one transient failure",
   434  			from: []connectivity.State{connectivity.Idle, connectivity.Idle},
   435  			to:   []connectivity.State{connectivity.Connecting, connectivity.TransientFailure},
   436  			want: connectivity.Connecting,
   437  		},
   438  		{
   439  			name: "one connecting two transient failure",
   440  			from: []connectivity.State{connectivity.Idle, connectivity.Idle, connectivity.Idle},
   441  			to:   []connectivity.State{connectivity.Connecting, connectivity.TransientFailure, connectivity.TransientFailure},
   442  			want: connectivity.TransientFailure,
   443  		},
   444  	}
   445  	for _, tt := range tests {
   446  		t.Run(tt.name, func(t *testing.T) {
   447  			cse := &connectivityStateEvaluator{}
   448  			var got connectivity.State
   449  			for i, fff := range tt.from {
   450  				ttt := tt.to[i]
   451  				got = cse.recordTransition(fff, ttt)
   452  			}
   453  			if got != tt.want {
   454  				t.Errorf("recordTransition() = %v, want %v", got, tt.want)
   455  			}
   456  		})
   457  	}
   458  }