google.golang.org/grpc@v1.72.2/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 "google.golang.org/grpc/balancer" 28 "google.golang.org/grpc/connectivity" 29 "google.golang.org/grpc/internal/balancer/weight" 30 "google.golang.org/grpc/internal/grpctest" 31 "google.golang.org/grpc/internal/testutils" 32 "google.golang.org/grpc/resolver" 33 "google.golang.org/grpc/xds/internal" 34 ) 35 36 const ( 37 defaultTestTimeout = 10 * time.Second 38 defaultTestShortTimeout = 10 * time.Millisecond 39 40 testBackendAddrsCount = 12 41 ) 42 43 var ( 44 testBackendAddrStrs []string 45 testConfig = &LBConfig{MinRingSize: 1, MaxRingSize: 10} 46 ) 47 48 func init() { 49 for i := 0; i < testBackendAddrsCount; i++ { 50 testBackendAddrStrs = append(testBackendAddrStrs, fmt.Sprintf("%d.%d.%d.%d:%d", i, i, i, i, i)) 51 } 52 } 53 54 // setupTest creates the balancer, and does an initial sanity check. 55 func setupTest(t *testing.T, endpoints []resolver.Endpoint) (*testutils.BalancerClientConn, balancer.Balancer, balancer.Picker) { 56 t.Helper() 57 cc := testutils.NewBalancerClientConn(t) 58 builder := balancer.Get(Name) 59 b := builder.Build(cc, balancer.BuildOptions{}) 60 if b == nil { 61 t.Fatalf("builder.Build(%s) failed and returned nil", Name) 62 } 63 if err := b.UpdateClientConnState(balancer.ClientConnState{ 64 ResolverState: resolver.State{Endpoints: endpoints}, 65 BalancerConfig: testConfig, 66 }); err != nil { 67 t.Fatalf("UpdateClientConnState returned err: %v", err) 68 } 69 70 // The leaf pickfirst are created lazily, only when their endpoint is picked 71 // or other endpoints are in TF. No SubConns should be created immediately. 72 select { 73 case sc := <-cc.NewSubConnCh: 74 t.Errorf("unexpected SubConn creation: %v", sc) 75 case <-time.After(defaultTestShortTimeout): 76 } 77 78 // Should also have a picker, with all endpoints in Idle. 79 p1 := <-cc.NewPickerCh 80 81 ringHashPicker := p1.(*picker) 82 if got, want := len(ringHashPicker.endpointStates), len(endpoints); got != want { 83 t.Errorf("Number of child balancers = %d, want = %d", got, want) 84 } 85 for firstAddr, bs := range ringHashPicker.endpointStates { 86 if got, want := bs.state.ConnectivityState, connectivity.Idle; got != want { 87 t.Errorf("Child balancer connectivity state for address %q = %v, want = %v", firstAddr, got, want) 88 } 89 } 90 return cc, b, p1 91 } 92 93 type s struct { 94 grpctest.Tester 95 } 96 97 func Test(t *testing.T) { 98 grpctest.RunSubTests(t, s{}) 99 } 100 101 // TestUpdateClientConnState_NewRingSize tests the scenario where the ringhash 102 // LB policy receives new configuration which specifies new values for the ring 103 // min and max sizes. The test verifies that a new ring is created and a new 104 // picker is sent to the ClientConn. 105 func (s) TestUpdateClientConnState_NewRingSize(t *testing.T) { 106 origMinRingSize, origMaxRingSize := 1, 10 // Configured from `testConfig` in `setupTest` 107 newMinRingSize, newMaxRingSize := 20, 100 108 109 endpoints := []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: testBackendAddrStrs[0]}}}} 110 cc, b, p1 := setupTest(t, endpoints) 111 ring1 := p1.(*picker).ring 112 if ringSize := len(ring1.items); ringSize < origMinRingSize || ringSize > origMaxRingSize { 113 t.Fatalf("Ring created with size %d, want between [%d, %d]", ringSize, origMinRingSize, origMaxRingSize) 114 } 115 116 if err := b.UpdateClientConnState(balancer.ClientConnState{ 117 ResolverState: resolver.State{Endpoints: endpoints}, 118 BalancerConfig: &LBConfig{MinRingSize: uint64(newMinRingSize), MaxRingSize: uint64(newMaxRingSize)}, 119 }); err != nil { 120 t.Fatalf("UpdateClientConnState returned err: %v", err) 121 } 122 123 var ring2 *ring 124 select { 125 case <-time.After(defaultTestTimeout): 126 t.Fatal("Timeout when waiting for a picker update after a configuration update") 127 case p2 := <-cc.NewPickerCh: 128 ring2 = p2.(*picker).ring 129 } 130 if ringSize := len(ring2.items); ringSize < newMinRingSize || ringSize > newMaxRingSize { 131 t.Fatalf("Ring created with size %d, want between [%d, %d]", ringSize, newMinRingSize, newMaxRingSize) 132 } 133 } 134 135 func (s) TestOneEndpoint(t *testing.T) { 136 wantAddr1 := resolver.Address{Addr: testBackendAddrStrs[0]} 137 cc, _, p0 := setupTest(t, []resolver.Endpoint{{Addresses: []resolver.Address{wantAddr1}}}) 138 ring0 := p0.(*picker).ring 139 140 firstHash := ring0.items[0].hash 141 // firstHash-1 will pick the first (and only) SubConn from the ring. 142 testHash := firstHash - 1 143 // The first pick should be queued, and should trigger a connection to the 144 // only Endpoint which has a single address. 145 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 146 defer cancel() 147 if _, err := p0.Pick(balancer.PickInfo{Ctx: SetXDSRequestHash(ctx, testHash)}); err != balancer.ErrNoSubConnAvailable { 148 t.Fatalf("first pick returned err %v, want %v", err, balancer.ErrNoSubConnAvailable) 149 } 150 var sc0 *testutils.TestSubConn 151 select { 152 case <-ctx.Done(): 153 t.Fatalf("Timed out waiting for SubConn creation.") 154 case sc0 = <-cc.NewSubConnCh: 155 } 156 if got, want := sc0.Addresses[0].Addr, wantAddr1.Addr; got != want { 157 t.Fatalf("SubConn.Addresses = %v, want = %v", got, want) 158 } 159 select { 160 case <-sc0.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 sc0.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Connecting}) 167 sc0.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Ready}) 168 if err := cc.WaitForConnectivityState(ctx, connectivity.Ready); err != nil { 169 t.Fatal(err) 170 } 171 172 // Test pick with one backend. 173 p1 := <-cc.NewPickerCh 174 for i := 0; i < 5; i++ { 175 gotSCSt, _ := p1.Pick(balancer.PickInfo{Ctx: SetXDSRequestHash(ctx, testHash)}) 176 if gotSCSt.SubConn != sc0 { 177 t.Fatalf("picker.Pick, got %v, want SubConn=%v", gotSCSt, sc0) 178 } 179 } 180 } 181 182 // TestThreeBackendsAffinity covers that there are 3 SubConns, RPCs with the 183 // same hash always pick the same SubConn. When the one picked is down, another 184 // one will be picked. 185 func (s) TestThreeSubConnsAffinity(t *testing.T) { 186 endpoints := []resolver.Endpoint{ 187 {Addresses: []resolver.Address{{Addr: testBackendAddrStrs[0]}}}, 188 {Addresses: []resolver.Address{{Addr: testBackendAddrStrs[1]}}}, 189 {Addresses: []resolver.Address{{Addr: testBackendAddrStrs[2]}}}, 190 } 191 remainingAddrs := map[string]bool{ 192 testBackendAddrStrs[0]: true, 193 testBackendAddrStrs[1]: true, 194 testBackendAddrStrs[2]: true, 195 } 196 cc, _, p0 := setupTest(t, endpoints) 197 // This test doesn't update addresses, so this ring will be used by all the 198 // pickers. 199 ring := p0.(*picker).ring 200 201 firstHash := ring.items[0].hash 202 // firstHash+1 will pick the second endpoint from the ring. 203 testHash := firstHash + 1 204 // The first pick should be queued, and should trigger Connect() on the only 205 // SubConn. 206 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 207 defer cancel() 208 if _, err := p0.Pick(balancer.PickInfo{Ctx: SetXDSRequestHash(ctx, testHash)}); err != balancer.ErrNoSubConnAvailable { 209 t.Fatalf("first pick returned err %v, want %v", err, balancer.ErrNoSubConnAvailable) 210 } 211 212 // The picked endpoint should be the second in the ring. 213 var subConns [3]*testutils.TestSubConn 214 select { 215 case <-ctx.Done(): 216 t.Fatalf("Timed out waiting for SubConn creation.") 217 case subConns[1] = <-cc.NewSubConnCh: 218 } 219 if got, want := subConns[1].Addresses[0].Addr, ring.items[1].hashKey; got != want { 220 t.Fatalf("SubConn.Address = %v, want = %v", got, want) 221 } 222 select { 223 case <-subConns[1].ConnectCh: 224 case <-time.After(defaultTestTimeout): 225 t.Errorf("timeout waiting for Connect() from SubConn %v", subConns[1]) 226 } 227 delete(remainingAddrs, ring.items[1].hashKey) 228 229 // Turn down the subConn in use. 230 subConns[1].UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Connecting}) 231 subConns[1].UpdateState(balancer.SubConnState{ConnectivityState: connectivity.TransientFailure}) 232 233 // This should trigger a connection to a new endpoint. 234 <-cc.NewPickerCh 235 var sc *testutils.TestSubConn 236 select { 237 case <-ctx.Done(): 238 t.Fatalf("Timed out waiting for SubConn creation.") 239 case sc = <-cc.NewSubConnCh: 240 } 241 scAddr := sc.Addresses[0].Addr 242 if _, ok := remainingAddrs[scAddr]; !ok { 243 t.Fatalf("New SubConn created with previously used address: %q", scAddr) 244 } 245 delete(remainingAddrs, scAddr) 246 select { 247 case <-sc.ConnectCh: 248 case <-time.After(defaultTestTimeout): 249 t.Errorf("timeout waiting for Connect() from SubConn %v", subConns[1]) 250 } 251 if scAddr == ring.items[0].hashKey { 252 subConns[0] = sc 253 } else if scAddr == ring.items[2].hashKey { 254 subConns[2] = sc 255 } 256 257 // Turning down the SubConn should cause creation of a connection to the 258 // final endpoint. 259 sc.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Connecting}) 260 sc.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.TransientFailure}) 261 select { 262 case <-ctx.Done(): 263 t.Fatalf("Timed out waiting for SubConn creation.") 264 case sc = <-cc.NewSubConnCh: 265 } 266 scAddr = sc.Addresses[0].Addr 267 if _, ok := remainingAddrs[scAddr]; !ok { 268 t.Fatalf("New SubConn created with previously used address: %q", scAddr) 269 } 270 delete(remainingAddrs, scAddr) 271 select { 272 case <-sc.ConnectCh: 273 case <-time.After(defaultTestTimeout): 274 t.Errorf("timeout waiting for Connect() from SubConn %v", subConns[1]) 275 } 276 if scAddr == ring.items[0].hashKey { 277 subConns[0] = sc 278 } else if scAddr == ring.items[2].hashKey { 279 subConns[2] = sc 280 } 281 sc.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Connecting}) 282 sc.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.TransientFailure}) 283 284 // All endpoints are in TransientFailure. Make the first endpoint in the 285 // ring report Ready. All picks should go to this endpoint which is two 286 // indexes away from the endpoint with the chosen hash. 287 subConns[0].UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Idle}) 288 subConns[0].UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Connecting}) 289 subConns[0].UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Ready}) 290 if err := cc.WaitForConnectivityState(ctx, connectivity.Ready); err != nil { 291 t.Fatalf("Context timed out while waiting for channel to report Ready.") 292 } 293 p1 := <-cc.NewPickerCh 294 for i := 0; i < 5; i++ { 295 gotSCSt, _ := p1.Pick(balancer.PickInfo{Ctx: SetXDSRequestHash(ctx, testHash)}) 296 if gotSCSt.SubConn != subConns[0] { 297 t.Fatalf("picker.Pick, got %v, want SubConn=%v", gotSCSt, subConns[0]) 298 } 299 } 300 301 // Make the last endpoint in the ring report Ready. All picks should go to 302 // this endpoint since it is one index away from the chosen hash. 303 subConns[2].UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Idle}) 304 subConns[2].UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Connecting}) 305 subConns[2].UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Ready}) 306 p2 := <-cc.NewPickerCh 307 for i := 0; i < 5; i++ { 308 gotSCSt, _ := p2.Pick(balancer.PickInfo{Ctx: SetXDSRequestHash(ctx, testHash)}) 309 if gotSCSt.SubConn != subConns[2] { 310 t.Fatalf("picker.Pick, got %v, want SubConn=%v", gotSCSt, subConns[2]) 311 } 312 } 313 314 // Make the second endpoint in the ring report Ready. All picks should go to 315 // this endpoint as it is the one with the chosen hash. 316 subConns[1].UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Idle}) 317 subConns[1].UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Connecting}) 318 subConns[1].UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Ready}) 319 p3 := <-cc.NewPickerCh 320 for i := 0; i < 5; i++ { 321 gotSCSt, _ := p3.Pick(balancer.PickInfo{Ctx: SetXDSRequestHash(ctx, testHash)}) 322 if gotSCSt.SubConn != subConns[1] { 323 t.Fatalf("picker.Pick, got %v, want SubConn=%v", gotSCSt, subConns[1]) 324 } 325 } 326 } 327 328 // TestThreeBackendsAffinity covers that there are 3 SubConns, RPCs with the 329 // same hash always pick the same SubConn. Then try different hash to pick 330 // another backend, and verify the first hash still picks the first backend. 331 func (s) TestThreeBackendsAffinityMultiple(t *testing.T) { 332 wantEndpoints := []resolver.Endpoint{ 333 {Addresses: []resolver.Address{{Addr: testBackendAddrStrs[0]}}}, 334 {Addresses: []resolver.Address{{Addr: testBackendAddrStrs[1]}}}, 335 {Addresses: []resolver.Address{{Addr: testBackendAddrStrs[2]}}}, 336 } 337 cc, _, p0 := setupTest(t, wantEndpoints) 338 // This test doesn't update addresses, so this ring will be used by all the 339 // pickers. 340 ring0 := p0.(*picker).ring 341 342 firstHash := ring0.items[0].hash 343 // firstHash+1 will pick the second SubConn from the ring. 344 testHash := firstHash + 1 345 // The first pick should be queued, and should trigger Connect() on the only 346 // SubConn. 347 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 348 defer cancel() 349 if _, err := p0.Pick(balancer.PickInfo{Ctx: SetXDSRequestHash(ctx, testHash)}); err != balancer.ErrNoSubConnAvailable { 350 t.Fatalf("first pick returned err %v, want %v", err, balancer.ErrNoSubConnAvailable) 351 } 352 // The picked SubConn should be the second in the ring. 353 var sc0 *testutils.TestSubConn 354 select { 355 case <-ctx.Done(): 356 t.Fatalf("Timed out waiting for SubConn creation.") 357 case sc0 = <-cc.NewSubConnCh: 358 } 359 if got, want := sc0.Addresses[0].Addr, ring0.items[1].hashKey; got != want { 360 t.Fatalf("SubConn.Address = %v, want = %v", got, want) 361 } 362 select { 363 case <-sc0.ConnectCh: 364 case <-time.After(defaultTestTimeout): 365 t.Errorf("timeout waiting for Connect() from SubConn %v", sc0) 366 } 367 368 // Send state updates to Ready. 369 sc0.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Connecting}) 370 sc0.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Ready}) 371 if err := cc.WaitForConnectivityState(ctx, connectivity.Ready); err != nil { 372 t.Fatal(err) 373 } 374 375 // First hash should always pick sc0. 376 p1 := <-cc.NewPickerCh 377 for i := 0; i < 5; i++ { 378 gotSCSt, _ := p1.Pick(balancer.PickInfo{Ctx: SetXDSRequestHash(ctx, testHash)}) 379 if gotSCSt.SubConn != sc0 { 380 t.Fatalf("picker.Pick, got %v, want SubConn=%v", gotSCSt, sc0) 381 } 382 } 383 384 secondHash := ring0.items[1].hash 385 // secondHash+1 will pick the third SubConn from the ring. 386 testHash2 := secondHash + 1 387 if _, err := p0.Pick(balancer.PickInfo{Ctx: SetXDSRequestHash(ctx, testHash2)}); err != balancer.ErrNoSubConnAvailable { 388 t.Fatalf("first pick returned err %v, want %v", err, balancer.ErrNoSubConnAvailable) 389 } 390 var sc1 *testutils.TestSubConn 391 select { 392 case <-ctx.Done(): 393 t.Fatalf("Timed out waiting for SubConn creation.") 394 case sc1 = <-cc.NewSubConnCh: 395 } 396 if got, want := sc1.Addresses[0].Addr, ring0.items[2].hashKey; got != want { 397 t.Fatalf("SubConn.Address = %v, want = %v", got, want) 398 } 399 select { 400 case <-sc1.ConnectCh: 401 case <-time.After(defaultTestTimeout): 402 t.Errorf("timeout waiting for Connect() from SubConn %v", sc1) 403 } 404 sc1.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Connecting}) 405 sc1.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Ready}) 406 407 // With the new generated picker, hash2 always picks sc1. 408 p2 := <-cc.NewPickerCh 409 for i := 0; i < 5; i++ { 410 gotSCSt, _ := p2.Pick(balancer.PickInfo{Ctx: SetXDSRequestHash(ctx, testHash2)}) 411 if gotSCSt.SubConn != sc1 { 412 t.Fatalf("picker.Pick, got %v, want SubConn=%v", gotSCSt, sc1) 413 } 414 } 415 // But the first hash still picks sc0. 416 for i := 0; i < 5; i++ { 417 gotSCSt, _ := p2.Pick(balancer.PickInfo{Ctx: SetXDSRequestHash(ctx, testHash)}) 418 if gotSCSt.SubConn != sc0 { 419 t.Fatalf("picker.Pick, got %v, want SubConn=%v", gotSCSt, sc0) 420 } 421 } 422 } 423 424 // TestAddrWeightChange covers the following scenarios after setting up the 425 // balancer with 3 addresses [A, B, C]: 426 // - updates balancer with [A, B, C], a new Picker should not be sent. 427 // - updates balancer with [A, B] (C removed), a new Picker is sent and the 428 // ring is updated. 429 // - updates balancer with [A, B], but B has a weight of 2, a new Picker is 430 // sent. And the new ring should contain the correct number of entries 431 // and weights. 432 func (s) TestAddrWeightChange(t *testing.T) { 433 endpoints := []resolver.Endpoint{ 434 {Addresses: []resolver.Address{{Addr: testBackendAddrStrs[0]}}}, 435 {Addresses: []resolver.Address{{Addr: testBackendAddrStrs[1]}}}, 436 {Addresses: []resolver.Address{{Addr: testBackendAddrStrs[2]}}}, 437 } 438 cc, b, p0 := setupTest(t, endpoints) 439 ring0 := p0.(*picker).ring 440 441 // Update with the same addresses, it will result in a new picker, but with 442 // the same ring. 443 if err := b.UpdateClientConnState(balancer.ClientConnState{ 444 ResolverState: resolver.State{Endpoints: endpoints}, 445 BalancerConfig: testConfig, 446 }); err != nil { 447 t.Fatalf("UpdateClientConnState returned err: %v", err) 448 } 449 var p1 balancer.Picker 450 select { 451 case p1 = <-cc.NewPickerCh: 452 case <-time.After(defaultTestTimeout): 453 t.Fatalf("timeout waiting for picker after UpdateClientConn with same addresses") 454 } 455 ring1 := p1.(*picker).ring 456 if ring1 != ring0 { 457 t.Fatalf("new picker with same address has a different ring than before, want same") 458 } 459 460 // Delete an address, should send a new Picker. 461 if err := b.UpdateClientConnState(balancer.ClientConnState{ 462 ResolverState: resolver.State{Endpoints: endpoints[:2]}, 463 BalancerConfig: testConfig, 464 }); err != nil { 465 t.Fatalf("UpdateClientConnState returned err: %v", err) 466 } 467 var p2 balancer.Picker 468 select { 469 case p2 = <-cc.NewPickerCh: 470 case <-time.After(defaultTestTimeout): 471 t.Fatalf("timeout waiting for picker after UpdateClientConn with different addresses") 472 } 473 ring2 := p2.(*picker).ring 474 if ring2 == ring0 { 475 t.Fatalf("new picker after removing address has the same ring as before, want different") 476 } 477 478 // Another update with the same addresses, but different weight. 479 if err := b.UpdateClientConnState(balancer.ClientConnState{ 480 ResolverState: resolver.State{Endpoints: []resolver.Endpoint{ 481 endpoints[0], 482 weight.Set(endpoints[1], weight.EndpointInfo{Weight: 2}), 483 }}, 484 BalancerConfig: testConfig, 485 }); err != nil { 486 t.Fatalf("UpdateClientConnState returned err: %v", err) 487 } 488 var p3 balancer.Picker 489 select { 490 case p3 = <-cc.NewPickerCh: 491 case <-time.After(defaultTestTimeout): 492 t.Fatalf("timeout waiting for picker after UpdateClientConn with different addresses") 493 } 494 if p3.(*picker).ring == ring2 { 495 t.Fatalf("new picker after changing address weight has the same ring as before, want different") 496 } 497 // With the new update, the ring must look like this: 498 // [ 499 // {idx:0 endpoint: {addr: testBackendAddrStrs[0], weight: 1}}, 500 // {idx:1 endpoint: {addr: testBackendAddrStrs[1], weight: 2}}, 501 // {idx:2 endpoint: {addr: testBackendAddrStrs[2], weight: 1}}, 502 // ]. 503 if len(p3.(*picker).ring.items) != 3 { 504 t.Fatalf("new picker after changing address weight has %d entries, want 3", len(p3.(*picker).ring.items)) 505 } 506 for _, i := range p3.(*picker).ring.items { 507 if i.hashKey == testBackendAddrStrs[0] { 508 if i.weight != 1 { 509 t.Fatalf("new picker after changing address weight has weight %d for %v, want 1", i.weight, i.hashKey) 510 } 511 } 512 if i.hashKey == testBackendAddrStrs[1] { 513 if i.weight != 2 { 514 t.Fatalf("new picker after changing address weight has weight %d for %v, want 2", i.weight, i.hashKey) 515 } 516 } 517 } 518 } 519 520 // TestAutoConnectEndpointOnTransientFailure covers the situation when an 521 // endpoint fails. It verifies that a new endpoint is automatically tried 522 // (without a pick) when there is no endpoint already in Connecting state. 523 func (s) TestAutoConnectEndpointOnTransientFailure(t *testing.T) { 524 wantEndpoints := []resolver.Endpoint{ 525 {Addresses: []resolver.Address{{Addr: testBackendAddrStrs[0]}}}, 526 {Addresses: []resolver.Address{{Addr: testBackendAddrStrs[1]}}}, 527 {Addresses: []resolver.Address{{Addr: testBackendAddrStrs[2]}}}, 528 {Addresses: []resolver.Address{{Addr: testBackendAddrStrs[3]}}}, 529 } 530 cc, _, p0 := setupTest(t, wantEndpoints) 531 532 // ringhash won't tell SCs to connect until there is an RPC, so simulate 533 // one now. 534 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 535 ctx = SetXDSRequestHash(ctx, 0) 536 defer cancel() 537 p0.Pick(balancer.PickInfo{Ctx: ctx}) 538 539 // The picked SubConn should be the second in the ring. 540 var sc0 *testutils.TestSubConn 541 select { 542 case <-ctx.Done(): 543 t.Fatalf("Timed out waiting for SubConn creation.") 544 case sc0 = <-cc.NewSubConnCh: 545 } 546 select { 547 case <-sc0.ConnectCh: 548 case <-time.After(defaultTestTimeout): 549 t.Errorf("timeout waiting for Connect() from SubConn %v", sc0) 550 } 551 552 // Turn the first subconn to transient failure. This should set the overall 553 // connectivity state to CONNECTING. 554 sc0.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Connecting}) 555 sc0.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.TransientFailure}) 556 cc.WaitForConnectivityState(ctx, connectivity.Connecting) 557 558 // It will trigger the second subconn to connect since there is only one 559 // endpoint, which is in TF. 560 var sc1 *testutils.TestSubConn 561 select { 562 case <-ctx.Done(): 563 t.Fatalf("Timed out waiting for SubConn creation.") 564 case sc1 = <-cc.NewSubConnCh: 565 } 566 select { 567 case <-sc1.ConnectCh: 568 case <-time.After(defaultTestShortTimeout): 569 t.Fatalf("timeout waiting for Connect() from SubConn %v", sc1) 570 } 571 572 // Turn the second subconn to TF. This will set the overall state to TF. 573 sc1.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Connecting}) 574 sc1.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.TransientFailure}) 575 cc.WaitForConnectivityState(ctx, connectivity.TransientFailure) 576 577 // It will trigger the third subconn to connect. 578 var sc2 *testutils.TestSubConn 579 select { 580 case <-ctx.Done(): 581 t.Fatalf("Timed out waiting for SubConn creation.") 582 case sc2 = <-cc.NewSubConnCh: 583 } 584 select { 585 case <-sc2.ConnectCh: 586 case <-time.After(defaultTestShortTimeout): 587 t.Fatalf("timeout waiting for Connect() from SubConn %v", sc2) 588 } 589 590 sc2.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Connecting}) 591 592 // Send the first SubConn into CONNECTING. To do this, first make it READY, 593 // then CONNECTING. 594 sc0.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Ready}) 595 cc.WaitForConnectivityState(ctx, connectivity.Ready) 596 sc0.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Idle}) 597 // Since one endpoint is in TF and one in CONNECTING, the aggregated state 598 // will be CONNECTING. 599 cc.WaitForConnectivityState(ctx, connectivity.Connecting) 600 p1 := <-cc.NewPickerCh 601 p1.Pick(balancer.PickInfo{Ctx: ctx}) 602 select { 603 case <-sc0.ConnectCh: 604 case <-time.After(defaultTestTimeout): 605 t.Errorf("timeout waiting for Connect() from SubConn %v", sc0) 606 } 607 sc0.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Connecting}) 608 609 // This will not trigger any new SubCOnns to be created, because sc0 is 610 // still attempting to connect, and we only need one SubConn to connect. 611 sc2.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.TransientFailure}) 612 613 select { 614 case sc := <-cc.NewSubConnCh: 615 t.Fatalf("unexpected SubConn creation: %v", sc) 616 case <-sc0.ConnectCh: 617 t.Fatalf("unexpected Connect() from SubConn %v", sc0) 618 case <-sc1.ConnectCh: 619 t.Fatalf("unexpected Connect() from SubConn %v", sc1) 620 case <-sc2.ConnectCh: 621 t.Fatalf("unexpected Connect() from SubConn %v", sc2) 622 case <-time.After(defaultTestShortTimeout): 623 } 624 } 625 626 func (s) TestAggregatedConnectivityState(t *testing.T) { 627 tests := []struct { 628 name string 629 endpointStates []connectivity.State 630 want connectivity.State 631 }{ 632 { 633 name: "one ready", 634 endpointStates: []connectivity.State{connectivity.Ready}, 635 want: connectivity.Ready, 636 }, 637 { 638 name: "one connecting", 639 endpointStates: []connectivity.State{connectivity.Connecting}, 640 want: connectivity.Connecting, 641 }, 642 { 643 name: "one ready one transient failure", 644 endpointStates: []connectivity.State{connectivity.Ready, connectivity.TransientFailure}, 645 want: connectivity.Ready, 646 }, 647 { 648 name: "one connecting one transient failure", 649 endpointStates: []connectivity.State{connectivity.Connecting, connectivity.TransientFailure}, 650 want: connectivity.Connecting, 651 }, 652 { 653 name: "one connecting two transient failure", 654 endpointStates: []connectivity.State{connectivity.Connecting, connectivity.TransientFailure, connectivity.TransientFailure}, 655 want: connectivity.TransientFailure, 656 }, 657 } 658 for _, tt := range tests { 659 t.Run(tt.name, func(t *testing.T) { 660 bal := &ringhashBalancer{endpointStates: resolver.NewEndpointMap[*endpointState]()} 661 for i, cs := range tt.endpointStates { 662 es := &endpointState{ 663 state: balancer.State{ConnectivityState: cs}, 664 } 665 ep := resolver.Endpoint{Addresses: []resolver.Address{{Addr: fmt.Sprintf("%d.%d.%d.%d:%d", i, i, i, i, i)}}} 666 bal.endpointStates.Set(ep, es) 667 } 668 if got := bal.aggregatedStateLocked(); got != tt.want { 669 t.Errorf("recordTransition() = %v, want %v", got, tt.want) 670 } 671 }) 672 } 673 } 674 675 // TestAddrBalancerAttributesChange tests the case where the ringhash balancer 676 // receives a ClientConnUpdate with the same config and addresses as received in 677 // the previous update. Although the `BalancerAttributes` and endpoint 678 // attributes contents are the same, the pointers are different. This test 679 // verifies that subConns are not recreated in this scenario. 680 func (s) TestAddrBalancerAttributesChange(t *testing.T) { 681 locality := internal.LocalityID{Region: "americas"} 682 addrs1 := []resolver.Address{internal.SetLocalityID(resolver.Address{Addr: testBackendAddrStrs[0]}, locality)} 683 wantEndpoints1 := []resolver.Endpoint{ 684 internal.SetLocalityIDInEndpoint(resolver.Endpoint{Addresses: addrs1}, locality), 685 } 686 cc, b, p0 := setupTest(t, wantEndpoints1) 687 ring0 := p0.(*picker).ring 688 689 firstHash := ring0.items[0].hash 690 // The first pick should be queued, and should trigger a connection to the 691 // only Endpoint which has a single address. 692 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 693 defer cancel() 694 if _, err := p0.Pick(balancer.PickInfo{Ctx: SetXDSRequestHash(ctx, firstHash)}); err != balancer.ErrNoSubConnAvailable { 695 t.Fatalf("first pick returned err %v, want %v", err, balancer.ErrNoSubConnAvailable) 696 } 697 select { 698 case <-ctx.Done(): 699 t.Fatalf("Timed out waiting for SubConn creation.") 700 case <-cc.NewSubConnCh: 701 } 702 703 addrs2 := []resolver.Address{internal.SetLocalityID(resolver.Address{Addr: testBackendAddrStrs[0]}, locality)} 704 wantEndpoints2 := []resolver.Endpoint{ 705 internal.SetLocalityIDInEndpoint(resolver.Endpoint{Addresses: addrs2}, locality), 706 } 707 if err := b.UpdateClientConnState(balancer.ClientConnState{ 708 ResolverState: resolver.State{Endpoints: wantEndpoints2}, 709 BalancerConfig: testConfig, 710 }); err != nil { 711 t.Fatalf("UpdateClientConnState returned err: %v", err) 712 } 713 select { 714 case <-cc.NewSubConnCh: 715 t.Fatal("new subConn created for an update with the same addresses") 716 case <-time.After(defaultTestShortTimeout): 717 } 718 }