gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/grpc/xds/internal/balancer/ringhash/picker_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  	"testing"
    24  	"time"
    25  
    26  	"gitee.com/ks-custle/core-gm/grpc/balancer"
    27  	"gitee.com/ks-custle/core-gm/grpc/connectivity"
    28  	"gitee.com/ks-custle/core-gm/grpc/internal/testutils"
    29  	"github.com/google/go-cmp/cmp"
    30  )
    31  
    32  func newTestRing(cStats []connectivity.State) *ring {
    33  	var items []*ringEntry
    34  	for i, st := range cStats {
    35  		testSC := testutils.TestSubConns[i]
    36  		items = append(items, &ringEntry{
    37  			idx:  i,
    38  			hash: uint64((i + 1) * 10),
    39  			sc: &subConn{
    40  				addr:  testSC.String(),
    41  				sc:    testSC,
    42  				state: st,
    43  			},
    44  		})
    45  	}
    46  	return &ring{items: items}
    47  }
    48  
    49  func TestPickerPickFirstTwo(t *testing.T) {
    50  	tests := []struct {
    51  		name            string
    52  		ring            *ring
    53  		hash            uint64
    54  		wantSC          balancer.SubConn
    55  		wantErr         error
    56  		wantSCToConnect balancer.SubConn
    57  	}{
    58  		{
    59  			name:   "picked is Ready",
    60  			ring:   newTestRing([]connectivity.State{connectivity.Ready, connectivity.Idle}),
    61  			hash:   5,
    62  			wantSC: testutils.TestSubConns[0],
    63  		},
    64  		{
    65  			name:    "picked is connecting, queue",
    66  			ring:    newTestRing([]connectivity.State{connectivity.Connecting, connectivity.Idle}),
    67  			hash:    5,
    68  			wantErr: balancer.ErrNoSubConnAvailable,
    69  		},
    70  		{
    71  			name:            "picked is Idle, connect and queue",
    72  			ring:            newTestRing([]connectivity.State{connectivity.Idle, connectivity.Idle}),
    73  			hash:            5,
    74  			wantErr:         balancer.ErrNoSubConnAvailable,
    75  			wantSCToConnect: testutils.TestSubConns[0],
    76  		},
    77  		{
    78  			name:   "picked is TransientFailure, next is ready, return",
    79  			ring:   newTestRing([]connectivity.State{connectivity.TransientFailure, connectivity.Ready}),
    80  			hash:   5,
    81  			wantSC: testutils.TestSubConns[1],
    82  		},
    83  		{
    84  			name:    "picked is TransientFailure, next is connecting, queue",
    85  			ring:    newTestRing([]connectivity.State{connectivity.TransientFailure, connectivity.Connecting}),
    86  			hash:    5,
    87  			wantErr: balancer.ErrNoSubConnAvailable,
    88  		},
    89  		{
    90  			name:            "picked is TransientFailure, next is Idle, connect and queue",
    91  			ring:            newTestRing([]connectivity.State{connectivity.TransientFailure, connectivity.Idle}),
    92  			hash:            5,
    93  			wantErr:         balancer.ErrNoSubConnAvailable,
    94  			wantSCToConnect: testutils.TestSubConns[1],
    95  		},
    96  	}
    97  	for _, tt := range tests {
    98  		t.Run(tt.name, func(t *testing.T) {
    99  			p := &picker{ring: tt.ring}
   100  			got, err := p.Pick(balancer.PickInfo{
   101  				Ctx: SetRequestHash(context.Background(), tt.hash),
   102  			})
   103  			if err != tt.wantErr {
   104  				t.Errorf("Pick() error = %v, wantErr %v", err, tt.wantErr)
   105  				return
   106  			}
   107  			if !cmp.Equal(got, balancer.PickResult{SubConn: tt.wantSC}, cmpOpts) {
   108  				t.Errorf("Pick() got = %v, want picked SubConn: %v", got, tt.wantSC)
   109  			}
   110  			if sc := tt.wantSCToConnect; sc != nil {
   111  				select {
   112  				case <-sc.(*testutils.TestSubConn).ConnectCh:
   113  				case <-time.After(defaultTestShortTimeout):
   114  					t.Errorf("timeout waiting for Connect() from SubConn %v", sc)
   115  				}
   116  			}
   117  		})
   118  	}
   119  }
   120  
   121  // TestPickerPickTriggerTFConnect covers that if the picked SubConn is
   122  // TransientFailures, all SubConns until a non-TransientFailure are queued for
   123  // Connect().
   124  func TestPickerPickTriggerTFConnect(t *testing.T) {
   125  	ring := newTestRing([]connectivity.State{
   126  		connectivity.TransientFailure, connectivity.TransientFailure, connectivity.TransientFailure, connectivity.TransientFailure,
   127  		connectivity.Idle, connectivity.TransientFailure, connectivity.TransientFailure, connectivity.TransientFailure,
   128  	})
   129  	p := &picker{ring: ring}
   130  	_, err := p.Pick(balancer.PickInfo{Ctx: SetRequestHash(context.Background(), 5)})
   131  	if err == nil {
   132  		t.Fatalf("Pick() error = %v, want non-nil", err)
   133  	}
   134  	// The first 4 SubConns, all in TransientFailure, should be queued to
   135  	// connect.
   136  	for i := 0; i < 4; i++ {
   137  		it := ring.items[i]
   138  		if !it.sc.connectQueued {
   139  			t.Errorf("the %d-th SubConn is not queued for connect", i)
   140  		}
   141  	}
   142  	// The other SubConns, after the first Idle, should not be queued to
   143  	// connect.
   144  	for i := 5; i < len(ring.items); i++ {
   145  		it := ring.items[i]
   146  		if it.sc.connectQueued {
   147  			t.Errorf("the %d-th SubConn is unexpected queued for connect", i)
   148  		}
   149  	}
   150  }
   151  
   152  // TestPickerPickTriggerTFReturnReady covers that if the picked SubConn is
   153  // TransientFailure, SubConn 2 and 3 are TransientFailure, 4 is Ready. SubConn 2
   154  // and 3 will Connect(), and 4 will be returned.
   155  func TestPickerPickTriggerTFReturnReady(t *testing.T) {
   156  	ring := newTestRing([]connectivity.State{
   157  		connectivity.TransientFailure, connectivity.TransientFailure, connectivity.TransientFailure, connectivity.Ready,
   158  	})
   159  	p := &picker{ring: ring}
   160  	pr, err := p.Pick(balancer.PickInfo{Ctx: SetRequestHash(context.Background(), 5)})
   161  	if err != nil {
   162  		t.Fatalf("Pick() error = %v, want nil", err)
   163  	}
   164  	if wantSC := testutils.TestSubConns[3]; pr.SubConn != wantSC {
   165  		t.Fatalf("Pick() = %v, want %v", pr.SubConn, wantSC)
   166  	}
   167  	// The first 3 SubConns, all in TransientFailure, should be queued to
   168  	// connect.
   169  	for i := 0; i < 3; i++ {
   170  		it := ring.items[i]
   171  		if !it.sc.connectQueued {
   172  			t.Errorf("the %d-th SubConn is not queued for connect", i)
   173  		}
   174  	}
   175  }
   176  
   177  // TestPickerPickTriggerTFWithIdle covers that if the picked SubConn is
   178  // TransientFailure, SubConn 2 is TransientFailure, 3 is Idle (init Idle). Pick
   179  // will be queue, SubConn 3 will Connect(), SubConn 4 and 5 (in TransientFailre)
   180  // will not queue a Connect.
   181  func TestPickerPickTriggerTFWithIdle(t *testing.T) {
   182  	ring := newTestRing([]connectivity.State{
   183  		connectivity.TransientFailure, connectivity.TransientFailure, connectivity.Idle, connectivity.TransientFailure, connectivity.TransientFailure,
   184  	})
   185  	p := &picker{ring: ring}
   186  	_, err := p.Pick(balancer.PickInfo{Ctx: SetRequestHash(context.Background(), 5)})
   187  	if err == balancer.ErrNoSubConnAvailable {
   188  		t.Fatalf("Pick() error = %v, want %v", err, balancer.ErrNoSubConnAvailable)
   189  	}
   190  	// The first 2 SubConns, all in TransientFailure, should be queued to
   191  	// connect.
   192  	for i := 0; i < 2; i++ {
   193  		it := ring.items[i]
   194  		if !it.sc.connectQueued {
   195  			t.Errorf("the %d-th SubConn is not queued for connect", i)
   196  		}
   197  	}
   198  	// SubConn 3 was in Idle, so should Connect()
   199  	select {
   200  	case <-testutils.TestSubConns[2].ConnectCh:
   201  	case <-time.After(defaultTestShortTimeout):
   202  		t.Errorf("timeout waiting for Connect() from SubConn %v", testutils.TestSubConns[2])
   203  	}
   204  	// The other SubConns, after the first Idle, should not be queued to
   205  	// connect.
   206  	for i := 3; i < len(ring.items); i++ {
   207  		it := ring.items[i]
   208  		if it.sc.connectQueued {
   209  			t.Errorf("the %d-th SubConn is unexpected queued for connect", i)
   210  		}
   211  	}
   212  }
   213  
   214  func TestNextSkippingDuplicatesNoDup(t *testing.T) {
   215  	testRing := newTestRing([]connectivity.State{connectivity.Idle, connectivity.Idle})
   216  	tests := []struct {
   217  		name string
   218  		ring *ring
   219  		cur  *ringEntry
   220  		want *ringEntry
   221  	}{
   222  		{
   223  			name: "no dup",
   224  			ring: testRing,
   225  			cur:  testRing.items[0],
   226  			want: testRing.items[1],
   227  		},
   228  		{
   229  			name: "only one entry",
   230  			ring: &ring{items: []*ringEntry{testRing.items[0]}},
   231  			cur:  testRing.items[0],
   232  			want: nil,
   233  		},
   234  	}
   235  	for _, tt := range tests {
   236  		t.Run(tt.name, func(t *testing.T) {
   237  			if got := nextSkippingDuplicates(tt.ring, tt.cur); !cmp.Equal(got, tt.want, cmpOpts) {
   238  				t.Errorf("nextSkippingDuplicates() = %v, want %v", got, tt.want)
   239  			}
   240  		})
   241  	}
   242  }
   243  
   244  // addDups adds duplicates of items[0] to the ring.
   245  func addDups(r *ring, count int) *ring {
   246  	var (
   247  		items []*ringEntry
   248  		idx   int
   249  	)
   250  	for i, it := range r.items {
   251  		itt := *it
   252  		itt.idx = idx
   253  		items = append(items, &itt)
   254  		idx++
   255  		if i == 0 {
   256  			// Add duplicate of items[0] to the ring
   257  			for j := 0; j < count; j++ {
   258  				itt2 := *it
   259  				itt2.idx = idx
   260  				items = append(items, &itt2)
   261  				idx++
   262  			}
   263  		}
   264  	}
   265  	return &ring{items: items}
   266  }
   267  
   268  func TestNextSkippingDuplicatesMoreDup(t *testing.T) {
   269  	testRing := newTestRing([]connectivity.State{connectivity.Idle, connectivity.Idle})
   270  	// Make a new ring with duplicate SubConns.
   271  	dupTestRing := addDups(testRing, 3)
   272  	if got := nextSkippingDuplicates(dupTestRing, dupTestRing.items[0]); !cmp.Equal(got, dupTestRing.items[len(dupTestRing.items)-1], cmpOpts) {
   273  		t.Errorf("nextSkippingDuplicates() = %v, want %v", got, dupTestRing.items[len(dupTestRing.items)-1])
   274  	}
   275  }
   276  
   277  func TestNextSkippingDuplicatesOnlyDup(t *testing.T) {
   278  	testRing := newTestRing([]connectivity.State{connectivity.Idle})
   279  	// Make a new ring with only duplicate SubConns.
   280  	dupTestRing := addDups(testRing, 3)
   281  	// This ring only has duplicates of items[0], should return nil.
   282  	if got := nextSkippingDuplicates(dupTestRing, dupTestRing.items[0]); got != nil {
   283  		t.Errorf("nextSkippingDuplicates() = %v, want nil", got)
   284  	}
   285  }