google.golang.org/grpc@v1.72.2/balancer/leastrequest/leastrequest_test.go (about) 1 /* 2 * 3 * Copyright 2023 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 package leastrequest 19 20 import ( 21 "context" 22 "encoding/json" 23 "fmt" 24 "strings" 25 "sync" 26 "testing" 27 "time" 28 29 "github.com/google/go-cmp/cmp" 30 "google.golang.org/grpc" 31 "google.golang.org/grpc/connectivity" 32 "google.golang.org/grpc/credentials/insecure" 33 "google.golang.org/grpc/internal" 34 "google.golang.org/grpc/internal/grpctest" 35 "google.golang.org/grpc/internal/stubserver" 36 "google.golang.org/grpc/internal/testutils" 37 testgrpc "google.golang.org/grpc/interop/grpc_testing" 38 testpb "google.golang.org/grpc/interop/grpc_testing" 39 "google.golang.org/grpc/peer" 40 "google.golang.org/grpc/resolver" 41 "google.golang.org/grpc/resolver/manual" 42 "google.golang.org/grpc/serviceconfig" 43 ) 44 45 const ( 46 defaultTestTimeout = 5 * time.Second 47 defaultTestShortTimeout = 10 * time.Millisecond 48 ) 49 50 type s struct { 51 grpctest.Tester 52 } 53 54 func Test(t *testing.T) { 55 grpctest.RunSubTests(t, s{}) 56 } 57 58 func (s) TestParseConfig(t *testing.T) { 59 parser := bb{} 60 tests := []struct { 61 name string 62 input string 63 wantCfg serviceconfig.LoadBalancingConfig 64 wantErr string 65 }{ 66 { 67 name: "happy-case-default", 68 input: `{}`, 69 wantCfg: &LBConfig{ 70 ChoiceCount: 2, 71 }, 72 }, 73 { 74 name: "happy-case-choice-count-set", 75 input: `{"choiceCount": 3}`, 76 wantCfg: &LBConfig{ 77 ChoiceCount: 3, 78 }, 79 }, 80 { 81 name: "happy-case-choice-count-greater-than-ten", 82 input: `{"choiceCount": 11}`, 83 wantCfg: &LBConfig{ 84 ChoiceCount: 10, 85 }, 86 }, 87 { 88 name: "choice-count-less-than-2", 89 input: `{"choiceCount": 1}`, 90 wantErr: "must be >= 2", 91 }, 92 { 93 name: "invalid-json", 94 input: "{{invalidjson{{", 95 wantErr: "invalid character", 96 }, 97 } 98 for _, test := range tests { 99 t.Run(test.name, func(t *testing.T) { 100 gotCfg, gotErr := parser.ParseConfig(json.RawMessage(test.input)) 101 // Substring match makes this very tightly coupled to the 102 // internalserviceconfig.BalancerConfig error strings. However, it 103 // is important to distinguish the different types of error messages 104 // possible as the parser has a few defined buckets of ways it can 105 // error out. 106 if (gotErr != nil) != (test.wantErr != "") { 107 t.Fatalf("ParseConfig(%v) = %v, wantErr %v", test.input, gotErr, test.wantErr) 108 } 109 if gotErr != nil && !strings.Contains(gotErr.Error(), test.wantErr) { 110 t.Fatalf("ParseConfig(%v) = %v, wantErr %v", test.input, gotErr, test.wantErr) 111 } 112 if test.wantErr != "" { 113 return 114 } 115 if diff := cmp.Diff(gotCfg, test.wantCfg); diff != "" { 116 t.Fatalf("ParseConfig(%v) got unexpected output, diff (-got +want): %v", test.input, diff) 117 } 118 }) 119 } 120 } 121 122 func startBackends(t *testing.T, numBackends int) []*stubserver.StubServer { 123 backends := make([]*stubserver.StubServer, 0, numBackends) 124 // Construct and start working backends. 125 for i := 0; i < numBackends; i++ { 126 backend := &stubserver.StubServer{ 127 EmptyCallF: func(context.Context, *testpb.Empty) (*testpb.Empty, error) { 128 return &testpb.Empty{}, nil 129 }, 130 FullDuplexCallF: func(stream testgrpc.TestService_FullDuplexCallServer) error { 131 <-stream.Context().Done() 132 return nil 133 }, 134 } 135 if err := backend.StartServer(); err != nil { 136 t.Fatalf("Failed to start backend: %v", err) 137 } 138 t.Logf("Started good TestService backend at: %q", backend.Address) 139 t.Cleanup(func() { backend.Stop() }) 140 backends = append(backends, backend) 141 } 142 return backends 143 } 144 145 // setupBackends spins up three test backends, each listening on a port on 146 // localhost. The three backends always reply with an empty response with no 147 // error, and for streaming receive until hitting an EOF error. 148 func setupBackends(t *testing.T, numBackends int) []string { 149 t.Helper() 150 addresses := make([]string, numBackends) 151 backends := startBackends(t, numBackends) 152 // Construct and start working backends. 153 for i := 0; i < numBackends; i++ { 154 addresses[i] = backends[i].Address 155 } 156 return addresses 157 } 158 159 // checkRoundRobinRPCs verifies that EmptyCall RPCs on the given ClientConn, 160 // connected to a server exposing the test.grpc_testing.TestService, are 161 // roundrobined across the given backend addresses. 162 // 163 // Returns a non-nil error if context deadline expires before RPCs start to get 164 // roundrobined across the given backends. 165 func checkRoundRobinRPCs(ctx context.Context, client testgrpc.TestServiceClient, addrs []resolver.Address) error { 166 wantAddrCount := make(map[string]int) 167 for _, addr := range addrs { 168 wantAddrCount[addr.Addr]++ 169 } 170 gotAddrCount := make(map[string]int) 171 for ; ctx.Err() == nil; <-time.After(time.Millisecond) { 172 gotAddrCount = make(map[string]int) 173 // Perform 3 iterations. 174 var iterations [][]string 175 for i := 0; i < 3; i++ { 176 iteration := make([]string, len(addrs)) 177 for c := 0; c < len(addrs); c++ { 178 var peer peer.Peer 179 client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&peer)) 180 iteration[c] = peer.Addr.String() 181 } 182 iterations = append(iterations, iteration) 183 } 184 // Ensure the first iteration contains all addresses in addrs. 185 for _, addr := range iterations[0] { 186 gotAddrCount[addr]++ 187 } 188 if !cmp.Equal(gotAddrCount, wantAddrCount) { 189 continue 190 } 191 // Ensure all three iterations contain the same addresses. 192 if !cmp.Equal(iterations[0], iterations[1]) || !cmp.Equal(iterations[0], iterations[2]) { 193 continue 194 } 195 return nil 196 } 197 return fmt.Errorf("timeout when waiting for roundrobin distribution of RPCs across addresses: %v; got: %v", addrs, gotAddrCount) 198 } 199 200 // TestLeastRequestE2E tests the Least Request LB policy in an e2e style. The 201 // Least Request balancer is configured as the top level balancer of the 202 // channel, and is passed three addresses. Eventually, the test creates three 203 // streams, which should be on certain backends according to the least request 204 // algorithm. The randomness in the picker is injected in the test to be 205 // deterministic, allowing the test to make assertions on the distribution. 206 func (s) TestLeastRequestE2E(t *testing.T) { 207 defer func(u func() uint32) { 208 randuint32 = u 209 }(randuint32) 210 var index int 211 indexes := []uint32{ 212 0, 0, 1, 1, 2, 2, // Triggers a round robin distribution. 213 } 214 randuint32 = func() uint32 { 215 ret := indexes[index%len(indexes)] 216 index++ 217 return ret 218 } 219 addresses := setupBackends(t, 3) 220 221 mr := manual.NewBuilderWithScheme("lr-e2e") 222 defer mr.Close() 223 224 // Configure least request as top level balancer of channel. 225 lrscJSON := ` 226 { 227 "loadBalancingConfig": [ 228 { 229 "least_request_experimental": { 230 "choiceCount": 2 231 } 232 } 233 ] 234 }` 235 sc := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(lrscJSON) 236 firstThreeAddresses := []resolver.Address{ 237 {Addr: addresses[0]}, 238 {Addr: addresses[1]}, 239 {Addr: addresses[2]}, 240 } 241 mr.InitialState(resolver.State{ 242 Addresses: firstThreeAddresses, 243 ServiceConfig: sc, 244 }) 245 246 cc, err := grpc.NewClient(mr.Scheme()+":///", grpc.WithResolvers(mr), grpc.WithTransportCredentials(insecure.NewCredentials())) 247 if err != nil { 248 t.Fatalf("grpc.NewClient() failed: %v", err) 249 } 250 defer cc.Close() 251 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 252 defer cancel() 253 testServiceClient := testgrpc.NewTestServiceClient(cc) 254 255 // Wait for all 3 backends to round robin across. The happens because a 256 // SubConn transitioning into READY causes a new picker update. Once the 257 // picker update with all 3 backends is present, this test can start to make 258 // assertions based on those backends. 259 if err := checkRoundRobinRPCs(ctx, testServiceClient, firstThreeAddresses); err != nil { 260 t.Fatalf("error in expected round robin: %v", err) 261 } 262 263 // Map ordering of READY SubConns is non deterministic. Thus, perform 3 RPCs 264 // mocked from the random to each index to learn the addresses of SubConns 265 // at each index. 266 index = 0 267 peerAtIndex := make([]string, 3) 268 var peer0 peer.Peer 269 if _, err := testServiceClient.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&peer0)); err != nil { 270 t.Fatalf("testServiceClient.EmptyCall failed: %v", err) 271 } 272 peerAtIndex[0] = peer0.Addr.String() 273 if _, err := testServiceClient.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&peer0)); err != nil { 274 t.Fatalf("testServiceClient.EmptyCall failed: %v", err) 275 } 276 peerAtIndex[1] = peer0.Addr.String() 277 if _, err := testServiceClient.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(&peer0)); err != nil { 278 t.Fatalf("testServiceClient.EmptyCall failed: %v", err) 279 } 280 peerAtIndex[2] = peer0.Addr.String() 281 282 // Start streaming RPCs, but do not finish them. Each subsequent stream 283 // should be started according to the least request algorithm, and chosen 284 // between the indexes provided. 285 index = 0 286 indexes = []uint32{ 287 0, 0, // Causes first stream to be on first address. 288 0, 1, // Compares first address (one RPC) to second (no RPCs), so choose second. 289 1, 2, // Compares second address (one RPC) to third (no RPCs), so choose third. 290 0, 3, // Causes another stream on first address. 291 1, 0, // Compares second address (one RPC) to first (two RPCs), so choose second. 292 2, 0, // Compares third address (one RPC) to first (two RPCs), so choose third. 293 0, 0, // Causes another stream on first address. 294 2, 2, // Causes a stream on third address. 295 2, 1, // Compares third address (three RPCs) to second (two RPCs), so choose third. 296 } 297 wantIndex := []uint32{0, 1, 2, 0, 1, 2, 0, 2, 1} 298 299 // Start streaming RPC's, but do not finish them. Each created stream should 300 // be started based on the least request algorithm and injected randomness 301 // (see indexes slice above for exact expectations). 302 for _, wantIndex := range wantIndex { 303 stream, err := testServiceClient.FullDuplexCall(ctx) 304 if err != nil { 305 t.Fatalf("testServiceClient.FullDuplexCall failed: %v", err) 306 } 307 p, ok := peer.FromContext(stream.Context()) 308 if !ok { 309 t.Fatalf("testServiceClient.FullDuplexCall has no Peer") 310 } 311 if p.Addr.String() != peerAtIndex[wantIndex] { 312 t.Fatalf("testServiceClient.FullDuplexCall's Peer got: %v, want: %v", p.Addr.String(), peerAtIndex[wantIndex]) 313 } 314 } 315 } 316 317 // TestLeastRequestPersistsCounts tests that the Least Request Balancer persists 318 // counts once it gets a new picker update. It first updates the Least Request 319 // Balancer with two backends, and creates a bunch of streams on them. Then, it 320 // updates the Least Request Balancer with three backends, including the two 321 // previous. Any created streams should then be started on the new backend. 322 func (s) TestLeastRequestPersistsCounts(t *testing.T) { 323 defer func(u func() uint32) { 324 randuint32 = u 325 }(randuint32) 326 var index int 327 indexes := []uint32{ 328 0, 0, 1, 1, 329 } 330 randuint32 = func() uint32 { 331 ret := indexes[index%len(indexes)] 332 index++ 333 return ret 334 } 335 addresses := setupBackends(t, 3) 336 337 mr := manual.NewBuilderWithScheme("lr-e2e") 338 defer mr.Close() 339 340 // Configure least request as top level balancer of channel. 341 lrscJSON := ` 342 { 343 "loadBalancingConfig": [ 344 { 345 "least_request_experimental": { 346 "choiceCount": 2 347 } 348 } 349 ] 350 }` 351 sc := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(lrscJSON) 352 firstTwoAddresses := []resolver.Address{ 353 {Addr: addresses[0]}, 354 {Addr: addresses[1]}, 355 } 356 mr.InitialState(resolver.State{ 357 Addresses: firstTwoAddresses, 358 ServiceConfig: sc, 359 }) 360 361 cc, err := grpc.NewClient(mr.Scheme()+":///", grpc.WithResolvers(mr), grpc.WithTransportCredentials(insecure.NewCredentials())) 362 if err != nil { 363 t.Fatalf("grpc.NewClient() failed: %v", err) 364 } 365 defer cc.Close() 366 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 367 defer cancel() 368 testServiceClient := testgrpc.NewTestServiceClient(cc) 369 370 // Wait for the two backends to round robin across. The happens because a 371 // SubConn transitioning into READY causes a new picker update. Once the 372 // picker update with the two backends is present, this test can start to 373 // populate those backends with streams. 374 if err := checkRoundRobinRPCs(ctx, testServiceClient, firstTwoAddresses); err != nil { 375 t.Fatalf("error in expected round robin: %v", err) 376 } 377 378 // Start 50 streaming RPCs, and leave them unfinished for the duration of 379 // the test. This will populate the first two addresses with many active 380 // RPCs. 381 for i := 0; i < 50; i++ { 382 _, err := testServiceClient.FullDuplexCall(ctx) 383 if err != nil { 384 t.Fatalf("testServiceClient.FullDuplexCall failed: %v", err) 385 } 386 } 387 388 // Update the least request balancer to choice count 3. Also update the 389 // address list adding a third address. Alongside the injected randomness, 390 // this should trigger the least request balancer to search all created 391 // SubConns. Thus, since address 3 is the new address and the first two 392 // addresses are populated with RPCs, once the picker update of all 3 READY 393 // SubConns takes effect, all new streams should be started on address 3. 394 index = 0 395 indexes = []uint32{ 396 0, 1, 2, 3, 4, 5, 397 } 398 lrscJSON = ` 399 { 400 "loadBalancingConfig": [ 401 { 402 "least_request_experimental": { 403 "choiceCount": 3 404 } 405 } 406 ] 407 }` 408 sc = internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(lrscJSON) 409 fullAddresses := []resolver.Address{ 410 {Addr: addresses[0]}, 411 {Addr: addresses[1]}, 412 {Addr: addresses[2]}, 413 } 414 mr.UpdateState(resolver.State{ 415 Addresses: fullAddresses, 416 ServiceConfig: sc, 417 }) 418 newAddress := fullAddresses[2] 419 // Poll for only address 3 to show up. This requires a polling loop because 420 // picker update with all three SubConns doesn't take into effect 421 // immediately, needs the third SubConn to become READY. 422 if err := checkRoundRobinRPCs(ctx, testServiceClient, []resolver.Address{newAddress}); err != nil { 423 t.Fatalf("error in expected round robin: %v", err) 424 } 425 426 // Start 25 rpcs, but don't finish them. They should all start on address 3, 427 // since the first two addresses both have 25 RPCs (and randomness 428 // injection/choiceCount causes all 3 to be compared every iteration). 429 for i := 0; i < 25; i++ { 430 stream, err := testServiceClient.FullDuplexCall(ctx) 431 if err != nil { 432 t.Fatalf("testServiceClient.FullDuplexCall failed: %v", err) 433 } 434 p, ok := peer.FromContext(stream.Context()) 435 if !ok { 436 t.Fatalf("testServiceClient.FullDuplexCall has no Peer") 437 } 438 if p.Addr.String() != addresses[2] { 439 t.Fatalf("testServiceClient.FullDuplexCall's Peer got: %v, want: %v", p.Addr.String(), addresses[2]) 440 } 441 } 442 443 // Now 25 RPC's are active on each address, the next three RPC's should 444 // round robin, since choiceCount is three and the injected random indexes 445 // cause it to search all three addresses for fewest outstanding requests on 446 // each iteration. 447 wantAddrCount := map[string]int{ 448 addresses[0]: 1, 449 addresses[1]: 1, 450 addresses[2]: 1, 451 } 452 gotAddrCount := make(map[string]int) 453 for i := 0; i < len(addresses); i++ { 454 stream, err := testServiceClient.FullDuplexCall(ctx) 455 if err != nil { 456 t.Fatalf("testServiceClient.FullDuplexCall failed: %v", err) 457 } 458 p, ok := peer.FromContext(stream.Context()) 459 if !ok { 460 t.Fatalf("testServiceClient.FullDuplexCall has no Peer") 461 } 462 if p.Addr != nil { 463 gotAddrCount[p.Addr.String()]++ 464 } 465 } 466 if diff := cmp.Diff(gotAddrCount, wantAddrCount); diff != "" { 467 t.Fatalf("addr count (-got:, +want): %v", diff) 468 } 469 } 470 471 // TestConcurrentRPCs tests concurrent RPCs on the least request balancer. It 472 // configures a channel with a least request balancer as the top level balancer, 473 // and makes 100 RPCs asynchronously. This makes sure no race conditions happen 474 // in this scenario. 475 func (s) TestConcurrentRPCs(t *testing.T) { 476 addresses := setupBackends(t, 3) 477 478 mr := manual.NewBuilderWithScheme("lr-e2e") 479 defer mr.Close() 480 481 // Configure least request as top level balancer of channel. 482 lrscJSON := ` 483 { 484 "loadBalancingConfig": [ 485 { 486 "least_request_experimental": { 487 "choiceCount": 2 488 } 489 } 490 ] 491 }` 492 sc := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(lrscJSON) 493 firstTwoAddresses := []resolver.Address{ 494 {Addr: addresses[0]}, 495 {Addr: addresses[1]}, 496 } 497 mr.InitialState(resolver.State{ 498 Addresses: firstTwoAddresses, 499 ServiceConfig: sc, 500 }) 501 502 cc, err := grpc.NewClient(mr.Scheme()+":///", grpc.WithResolvers(mr), grpc.WithTransportCredentials(insecure.NewCredentials())) 503 if err != nil { 504 t.Fatalf("grpc.NewClient() failed: %v", err) 505 } 506 defer cc.Close() 507 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 508 defer cancel() 509 testServiceClient := testgrpc.NewTestServiceClient(cc) 510 511 var wg sync.WaitGroup 512 for i := 0; i < 100; i++ { 513 wg.Add(1) 514 go func() { 515 defer wg.Done() 516 for j := 0; j < 5; j++ { 517 testServiceClient.EmptyCall(ctx, &testpb.Empty{}) 518 } 519 }() 520 } 521 wg.Wait() 522 } 523 524 // Test tests that the least request balancer persists RPC counts once it gets 525 // new picker updates and backends within an endpoint go down. It first updates 526 // the balancer with two endpoints having two addresses each. It verifies the 527 // requests are round robined across the first address of each endpoint. It then 528 // stops the active backend in endpoint[0]. It verified that the balancer starts 529 // using the second address in endpoint[0]. The test then creates a bunch of 530 // streams on two endpoints. Then, it updates the balancer with three endpoints, 531 // including the two previous. Any created streams should then be started on the 532 // new endpoint. The test shuts down the active backed in endpoint[1] and 533 // endpoint[2]. The test verifies that new RPCs are round robined across the 534 // active backends in endpoint[1] and endpoint[2]. 535 func (s) TestLeastRequestEndpoints_MultipleAddresses(t *testing.T) { 536 defer func(u func() uint32) { 537 randuint32 = u 538 }(randuint32) 539 var index int 540 indexes := []uint32{ 541 0, 0, 1, 1, 542 } 543 randuint32 = func() uint32 { 544 ret := indexes[index%len(indexes)] 545 index++ 546 return ret 547 } 548 backends := startBackends(t, 6) 549 mr := manual.NewBuilderWithScheme("lr-e2e") 550 defer mr.Close() 551 552 // Configure least request as top level balancer of channel. 553 lrscJSON := ` 554 { 555 "loadBalancingConfig": [ 556 { 557 "least_request_experimental": { 558 "choiceCount": 2 559 } 560 } 561 ] 562 }` 563 endpoints := []resolver.Endpoint{ 564 {Addresses: []resolver.Address{{Addr: backends[0].Address}, {Addr: backends[1].Address}}}, 565 {Addresses: []resolver.Address{{Addr: backends[2].Address}, {Addr: backends[3].Address}}}, 566 {Addresses: []resolver.Address{{Addr: backends[4].Address}, {Addr: backends[5].Address}}}, 567 } 568 sc := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(lrscJSON) 569 firstTwoEndpoints := []resolver.Endpoint{endpoints[0], endpoints[1]} 570 mr.InitialState(resolver.State{ 571 Endpoints: firstTwoEndpoints, 572 ServiceConfig: sc, 573 }) 574 575 cc, err := grpc.NewClient(mr.Scheme()+":///", grpc.WithResolvers(mr), grpc.WithTransportCredentials(insecure.NewCredentials())) 576 if err != nil { 577 t.Fatalf("grpc.NewClient() failed: %v", err) 578 } 579 defer cc.Close() 580 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 581 defer cancel() 582 testServiceClient := testgrpc.NewTestServiceClient(cc) 583 584 // Wait for the two backends to round robin across. The happens because a 585 // child pickfirst transitioning into READY causes a new picker update. Once 586 // the picker update with the two backends is present, this test can start 587 // to populate those backends with streams. 588 wantAddrs := []resolver.Address{ 589 endpoints[0].Addresses[0], 590 endpoints[1].Addresses[0], 591 } 592 if err := checkRoundRobinRPCs(ctx, testServiceClient, wantAddrs); err != nil { 593 t.Fatalf("error in expected round robin: %v", err) 594 } 595 596 // Shut down one of the addresses in endpoints[0], the child pickfirst 597 // should fallback to the next address in endpoints[0]. 598 backends[0].Stop() 599 wantAddrs = []resolver.Address{ 600 endpoints[0].Addresses[1], 601 endpoints[1].Addresses[0], 602 } 603 if err := checkRoundRobinRPCs(ctx, testServiceClient, wantAddrs); err != nil { 604 t.Fatalf("error in expected round robin: %v", err) 605 } 606 607 // Start 50 streaming RPCs, and leave them unfinished for the duration of 608 // the test. This will populate the first two endpoints with many active 609 // RPCs. 610 for i := 0; i < 50; i++ { 611 _, err := testServiceClient.FullDuplexCall(ctx) 612 if err != nil { 613 t.Fatalf("testServiceClient.FullDuplexCall failed: %v", err) 614 } 615 } 616 617 // Update the least request balancer to choice count 3. Also update the 618 // address list adding a third endpoint. Alongside the injected randomness, 619 // this should trigger the least request balancer to search all created 620 // endpoints. Thus, since endpoint 3 is the new endpoint and the first two 621 // endpoint are populated with RPCs, once the picker update of all 3 READY 622 // pickfirsts takes effect, all new streams should be started on endpoint 3. 623 index = 0 624 indexes = []uint32{ 625 0, 1, 2, 3, 4, 5, 626 } 627 lrscJSON = ` 628 { 629 "loadBalancingConfig": [ 630 { 631 "least_request_experimental": { 632 "choiceCount": 3 633 } 634 } 635 ] 636 }` 637 sc = internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(lrscJSON) 638 mr.UpdateState(resolver.State{ 639 Endpoints: endpoints, 640 ServiceConfig: sc, 641 }) 642 newAddress := endpoints[2].Addresses[0] 643 // Poll for only endpoint 3 to show up. This requires a polling loop because 644 // picker update with all three endpoints doesn't take into effect 645 // immediately, needs the third pickfirst to become READY. 646 if err := checkRoundRobinRPCs(ctx, testServiceClient, []resolver.Address{newAddress}); err != nil { 647 t.Fatalf("error in expected round robin: %v", err) 648 } 649 650 // Start 25 rpcs, but don't finish them. They should all start on endpoint 3, 651 // since the first two endpoints both have 25 RPCs (and randomness 652 // injection/choiceCount causes all 3 to be compared every iteration). 653 for i := 0; i < 25; i++ { 654 stream, err := testServiceClient.FullDuplexCall(ctx) 655 if err != nil { 656 t.Fatalf("testServiceClient.FullDuplexCall failed: %v", err) 657 } 658 p, ok := peer.FromContext(stream.Context()) 659 if !ok { 660 t.Fatalf("testServiceClient.FullDuplexCall has no Peer") 661 } 662 if p.Addr.String() != newAddress.Addr { 663 t.Fatalf("testServiceClient.FullDuplexCall's Peer got: %v, want: %v", p.Addr.String(), newAddress) 664 } 665 } 666 667 // Now 25 RPC's are active on each endpoint, the next three RPC's should 668 // round robin, since choiceCount is three and the injected random indexes 669 // cause it to search all three endpoints for fewest outstanding requests on 670 // each iteration. 671 wantAddrCount := map[string]int{ 672 endpoints[0].Addresses[1].Addr: 1, 673 endpoints[1].Addresses[0].Addr: 1, 674 endpoints[2].Addresses[0].Addr: 1, 675 } 676 gotAddrCount := make(map[string]int) 677 for i := 0; i < len(endpoints); i++ { 678 stream, err := testServiceClient.FullDuplexCall(ctx) 679 if err != nil { 680 t.Fatalf("testServiceClient.FullDuplexCall failed: %v", err) 681 } 682 p, ok := peer.FromContext(stream.Context()) 683 if !ok { 684 t.Fatalf("testServiceClient.FullDuplexCall has no Peer") 685 } 686 if p.Addr != nil { 687 gotAddrCount[p.Addr.String()]++ 688 } 689 } 690 if diff := cmp.Diff(gotAddrCount, wantAddrCount); diff != "" { 691 t.Fatalf("addr count (-got:, +want): %v", diff) 692 } 693 694 // Shutdown the active address for endpoint[1] and endpoint[2]. This should 695 // result in their streams failing. Now the requests should roundrobin b/w 696 // endpoint[1] and endpoint[2]. 697 backends[2].Stop() 698 backends[4].Stop() 699 index = 0 700 indexes = []uint32{ 701 0, 1, 2, 2, 1, 0, 702 } 703 wantAddrs = []resolver.Address{ 704 endpoints[1].Addresses[1], 705 endpoints[2].Addresses[1], 706 } 707 if err := checkRoundRobinRPCs(ctx, testServiceClient, wantAddrs); err != nil { 708 t.Fatalf("error in expected round robin: %v", err) 709 } 710 } 711 712 // Test tests that the least request balancer properly surfaces resolver 713 // errors. 714 func (s) TestLeastRequestEndpoints_ResolverError(t *testing.T) { 715 const sc = `{"loadBalancingConfig": [{"least_request_experimental": {}}]}` 716 mr := manual.NewBuilderWithScheme("lr-e2e") 717 defer mr.Close() 718 719 cc, err := grpc.NewClient( 720 mr.Scheme()+":///", 721 grpc.WithResolvers(mr), 722 grpc.WithTransportCredentials(insecure.NewCredentials()), 723 grpc.WithDefaultServiceConfig(sc), 724 ) 725 if err != nil { 726 t.Fatalf("grpc.NewClient() failed: %v", err) 727 } 728 defer cc.Close() 729 730 // We need to pass an endpoint with a valid address to the resolver before 731 // reporting an error - otherwise endpointsharding does not report the 732 // error through. 733 lis, err := testutils.LocalTCPListener() 734 if err != nil { 735 t.Fatalf("net.Listen() failed: %v", err) 736 } 737 // Act like a server that closes the connection without sending a server 738 // preface. 739 go func() { 740 conn, err := lis.Accept() 741 if err != nil { 742 t.Errorf("Unexpected error when accepting a connection: %v", err) 743 } 744 conn.Close() 745 }() 746 mr.UpdateState(resolver.State{ 747 Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: lis.Addr().String()}}}}, 748 }) 749 cc.Connect() 750 751 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 752 defer cancel() 753 testutils.AwaitState(ctx, t, cc, connectivity.TransientFailure) 754 755 // Report an error through the resolver 756 resolverErr := fmt.Errorf("simulated resolver error") 757 mr.CC().ReportError(resolverErr) 758 759 // Ensure the client returns the expected resolver error. 760 testServiceClient := testgrpc.NewTestServiceClient(cc) 761 for ; ctx.Err() == nil; <-time.After(defaultTestShortTimeout) { 762 _, err = testServiceClient.EmptyCall(ctx, &testpb.Empty{}) 763 if strings.Contains(err.Error(), resolverErr.Error()) { 764 break 765 } 766 } 767 if ctx.Err() != nil { 768 t.Fatalf("Timeout when waiting for RPCs to fail with error containing %s. Last error: %v", resolverErr, err) 769 } 770 }