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 }