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 }