gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/grpc/xds/internal/balancer/clustermanager/clustermanager_test.go (about) 1 /* 2 * 3 * Copyright 2020 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 clustermanager 20 21 import ( 22 "context" 23 "fmt" 24 "testing" 25 "time" 26 27 "gitee.com/ks-custle/core-gm/grpc/balancer" 28 "gitee.com/ks-custle/core-gm/grpc/balancer/roundrobin" 29 "gitee.com/ks-custle/core-gm/grpc/codes" 30 "gitee.com/ks-custle/core-gm/grpc/connectivity" 31 "gitee.com/ks-custle/core-gm/grpc/credentials/insecure" 32 "gitee.com/ks-custle/core-gm/grpc/internal/balancer/stub" 33 "gitee.com/ks-custle/core-gm/grpc/internal/balancergroup" 34 "gitee.com/ks-custle/core-gm/grpc/internal/grpctest" 35 "gitee.com/ks-custle/core-gm/grpc/internal/hierarchy" 36 "gitee.com/ks-custle/core-gm/grpc/internal/testutils" 37 "gitee.com/ks-custle/core-gm/grpc/resolver" 38 "gitee.com/ks-custle/core-gm/grpc/status" 39 "github.com/google/go-cmp/cmp" 40 ) 41 42 type s struct { 43 grpctest.Tester 44 } 45 46 func Test(t *testing.T) { 47 grpctest.RunSubTests(t, s{}) 48 } 49 50 var ( 51 rtBuilder balancer.Builder 52 rtParser balancer.ConfigParser 53 testBackendAddrStrs []string 54 ) 55 56 const ignoreAttrsRRName = "ignore_attrs_round_robin" 57 58 type ignoreAttrsRRBuilder struct { 59 balancer.Builder 60 } 61 62 func (trrb *ignoreAttrsRRBuilder) Build(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer { 63 return &ignoreAttrsRRBalancer{trrb.Builder.Build(cc, opts)} 64 } 65 66 func (*ignoreAttrsRRBuilder) Name() string { 67 return ignoreAttrsRRName 68 } 69 70 // ignoreAttrsRRBalancer clears attributes from all addresses. 71 // 72 // It's necessary in this tests because hierarchy modifies address.Attributes. 73 // Even if rr gets addresses with empty hierarchy, the attributes fields are 74 // different. This is a temporary walkaround for the tests to ignore attributes. 75 // Eventually, we need a way for roundrobin to know that two addresses with 76 // empty attributes are equal. 77 // 78 // TODO: delete this when the issue is resolved: 79 // https://github.com/grpc/grpc-go/issues/3611. 80 type ignoreAttrsRRBalancer struct { 81 balancer.Balancer 82 } 83 84 func (trrb *ignoreAttrsRRBalancer) UpdateClientConnState(s balancer.ClientConnState) error { 85 var newAddrs []resolver.Address 86 for _, a := range s.ResolverState.Addresses { 87 a.BalancerAttributes = nil 88 newAddrs = append(newAddrs, a) 89 } 90 s.ResolverState.Addresses = newAddrs 91 return trrb.Balancer.UpdateClientConnState(s) 92 } 93 94 const testBackendAddrsCount = 12 95 96 func init() { 97 for i := 0; i < testBackendAddrsCount; i++ { 98 testBackendAddrStrs = append(testBackendAddrStrs, fmt.Sprintf("%d.%d.%d.%d:%d", i, i, i, i, i)) 99 } 100 rtBuilder = balancer.Get(balancerName) 101 rtParser = rtBuilder.(balancer.ConfigParser) 102 103 balancer.Register(&ignoreAttrsRRBuilder{balancer.Get(roundrobin.Name)}) 104 105 balancergroup.DefaultSubBalancerCloseTimeout = time.Millisecond 106 } 107 108 func testPick(t *testing.T, p balancer.Picker, info balancer.PickInfo, wantSC balancer.SubConn, wantErr error) { 109 t.Helper() 110 for i := 0; i < 5; i++ { 111 gotSCSt, err := p.Pick(info) 112 if fmt.Sprint(err) != fmt.Sprint(wantErr) { 113 t.Fatalf("picker.Pick(%+v), got error %v, want %v", info, err, wantErr) 114 } 115 if !cmp.Equal(gotSCSt.SubConn, wantSC, cmp.AllowUnexported(testutils.TestSubConn{})) { 116 t.Fatalf("picker.Pick(%+v), got %v, want SubConn=%v", info, gotSCSt, wantSC) 117 } 118 } 119 } 120 121 func TestClusterPicks(t *testing.T) { 122 cc := testutils.NewTestClientConn(t) 123 rtb := rtBuilder.Build(cc, balancer.BuildOptions{}) 124 125 configJSON1 := `{ 126 "children": { 127 "cds:cluster_1":{ "childPolicy": [{"ignore_attrs_round_robin":""}] }, 128 "cds:cluster_2":{ "childPolicy": [{"ignore_attrs_round_robin":""}] } 129 } 130 }` 131 132 config1, err := rtParser.ParseConfig([]byte(configJSON1)) 133 if err != nil { 134 t.Fatalf("failed to parse balancer config: %v", err) 135 } 136 137 // Send the config, and an address with hierarchy path ["cluster_1"]. 138 wantAddrs := []resolver.Address{ 139 {Addr: testBackendAddrStrs[0], BalancerAttributes: nil}, 140 {Addr: testBackendAddrStrs[1], BalancerAttributes: nil}, 141 } 142 if err := rtb.UpdateClientConnState(balancer.ClientConnState{ 143 ResolverState: resolver.State{Addresses: []resolver.Address{ 144 hierarchy.Set(wantAddrs[0], []string{"cds:cluster_1"}), 145 hierarchy.Set(wantAddrs[1], []string{"cds:cluster_2"}), 146 }}, 147 BalancerConfig: config1, 148 }); err != nil { 149 t.Fatalf("failed to update ClientConn state: %v", err) 150 } 151 152 m1 := make(map[resolver.Address]balancer.SubConn) 153 // Verify that a subconn is created with the address, and the hierarchy path 154 // in the address is cleared. 155 for range wantAddrs { 156 addrs := <-cc.NewSubConnAddrsCh 157 if len(hierarchy.Get(addrs[0])) != 0 { 158 t.Fatalf("NewSubConn with address %+v, attrs %+v, want address with hierarchy cleared", addrs[0], addrs[0].BalancerAttributes) 159 } 160 sc := <-cc.NewSubConnCh 161 // Clear the attributes before adding to map. 162 addrs[0].BalancerAttributes = nil 163 m1[addrs[0]] = sc 164 rtb.UpdateSubConnState(sc, balancer.SubConnState{ConnectivityState: connectivity.Connecting}) 165 rtb.UpdateSubConnState(sc, balancer.SubConnState{ConnectivityState: connectivity.Ready}) 166 } 167 168 p1 := <-cc.NewPickerCh 169 for _, tt := range []struct { 170 pickInfo balancer.PickInfo 171 wantSC balancer.SubConn 172 wantErr error 173 }{ 174 { 175 pickInfo: balancer.PickInfo{ 176 Ctx: SetPickedCluster(context.Background(), "cds:cluster_1"), 177 }, 178 wantSC: m1[wantAddrs[0]], 179 }, 180 { 181 pickInfo: balancer.PickInfo{ 182 Ctx: SetPickedCluster(context.Background(), "cds:cluster_2"), 183 }, 184 wantSC: m1[wantAddrs[1]], 185 }, 186 { 187 pickInfo: balancer.PickInfo{ 188 Ctx: SetPickedCluster(context.Background(), "notacluster"), 189 }, 190 wantErr: status.Errorf(codes.Unavailable, `unknown cluster selected for RPC: "notacluster"`), 191 }, 192 } { 193 testPick(t, p1, tt.pickInfo, tt.wantSC, tt.wantErr) 194 } 195 } 196 197 // TestConfigUpdateAddCluster covers the cases the balancer receives config 198 // update with extra clusters. 199 func TestConfigUpdateAddCluster(t *testing.T) { 200 cc := testutils.NewTestClientConn(t) 201 rtb := rtBuilder.Build(cc, balancer.BuildOptions{}) 202 203 configJSON1 := `{ 204 "children": { 205 "cds:cluster_1":{ "childPolicy": [{"ignore_attrs_round_robin":""}] }, 206 "cds:cluster_2":{ "childPolicy": [{"ignore_attrs_round_robin":""}] } 207 } 208 }` 209 210 config1, err := rtParser.ParseConfig([]byte(configJSON1)) 211 if err != nil { 212 t.Fatalf("failed to parse balancer config: %v", err) 213 } 214 215 // Send the config, and an address with hierarchy path ["cluster_1"]. 216 wantAddrs := []resolver.Address{ 217 {Addr: testBackendAddrStrs[0], BalancerAttributes: nil}, 218 {Addr: testBackendAddrStrs[1], BalancerAttributes: nil}, 219 } 220 if err := rtb.UpdateClientConnState(balancer.ClientConnState{ 221 ResolverState: resolver.State{Addresses: []resolver.Address{ 222 hierarchy.Set(wantAddrs[0], []string{"cds:cluster_1"}), 223 hierarchy.Set(wantAddrs[1], []string{"cds:cluster_2"}), 224 }}, 225 BalancerConfig: config1, 226 }); err != nil { 227 t.Fatalf("failed to update ClientConn state: %v", err) 228 } 229 230 m1 := make(map[resolver.Address]balancer.SubConn) 231 // Verify that a subconn is created with the address, and the hierarchy path 232 // in the address is cleared. 233 for range wantAddrs { 234 addrs := <-cc.NewSubConnAddrsCh 235 if len(hierarchy.Get(addrs[0])) != 0 { 236 t.Fatalf("NewSubConn with address %+v, attrs %+v, want address with hierarchy cleared", addrs[0], addrs[0].BalancerAttributes) 237 } 238 sc := <-cc.NewSubConnCh 239 // Clear the attributes before adding to map. 240 addrs[0].BalancerAttributes = nil 241 m1[addrs[0]] = sc 242 rtb.UpdateSubConnState(sc, balancer.SubConnState{ConnectivityState: connectivity.Connecting}) 243 rtb.UpdateSubConnState(sc, balancer.SubConnState{ConnectivityState: connectivity.Ready}) 244 } 245 246 p1 := <-cc.NewPickerCh 247 for _, tt := range []struct { 248 pickInfo balancer.PickInfo 249 wantSC balancer.SubConn 250 wantErr error 251 }{ 252 { 253 pickInfo: balancer.PickInfo{ 254 Ctx: SetPickedCluster(context.Background(), "cds:cluster_1"), 255 }, 256 wantSC: m1[wantAddrs[0]], 257 }, 258 { 259 pickInfo: balancer.PickInfo{ 260 Ctx: SetPickedCluster(context.Background(), "cds:cluster_2"), 261 }, 262 wantSC: m1[wantAddrs[1]], 263 }, 264 { 265 pickInfo: balancer.PickInfo{ 266 Ctx: SetPickedCluster(context.Background(), "cds:notacluster"), 267 }, 268 wantErr: status.Errorf(codes.Unavailable, `unknown cluster selected for RPC: "cds:notacluster"`), 269 }, 270 } { 271 testPick(t, p1, tt.pickInfo, tt.wantSC, tt.wantErr) 272 } 273 274 // A config update with different routes, and different actions. Expect a 275 // new subconn and a picker update. 276 configJSON2 := `{ 277 "children": { 278 "cds:cluster_1":{ "childPolicy": [{"ignore_attrs_round_robin":""}] }, 279 "cds:cluster_2":{ "childPolicy": [{"ignore_attrs_round_robin":""}] }, 280 "cds:cluster_3":{ "childPolicy": [{"ignore_attrs_round_robin":""}] } 281 } 282 }` 283 config2, err := rtParser.ParseConfig([]byte(configJSON2)) 284 if err != nil { 285 t.Fatalf("failed to parse balancer config: %v", err) 286 } 287 wantAddrs = append(wantAddrs, resolver.Address{Addr: testBackendAddrStrs[2], BalancerAttributes: nil}) 288 if err := rtb.UpdateClientConnState(balancer.ClientConnState{ 289 ResolverState: resolver.State{Addresses: []resolver.Address{ 290 hierarchy.Set(wantAddrs[0], []string{"cds:cluster_1"}), 291 hierarchy.Set(wantAddrs[1], []string{"cds:cluster_2"}), 292 hierarchy.Set(wantAddrs[2], []string{"cds:cluster_3"}), 293 }}, 294 BalancerConfig: config2, 295 }); err != nil { 296 t.Fatalf("failed to update ClientConn state: %v", err) 297 } 298 299 // Expect exactly one new subconn. 300 addrs := <-cc.NewSubConnAddrsCh 301 if len(hierarchy.Get(addrs[0])) != 0 { 302 t.Fatalf("NewSubConn with address %+v, attrs %+v, want address with hierarchy cleared", addrs[0], addrs[0].BalancerAttributes) 303 } 304 sc := <-cc.NewSubConnCh 305 // Clear the attributes before adding to map. 306 addrs[0].BalancerAttributes = nil 307 m1[addrs[0]] = sc 308 rtb.UpdateSubConnState(sc, balancer.SubConnState{ConnectivityState: connectivity.Connecting}) 309 rtb.UpdateSubConnState(sc, balancer.SubConnState{ConnectivityState: connectivity.Ready}) 310 311 // Should have no more newSubConn. 312 select { 313 case <-time.After(time.Millisecond * 500): 314 case <-cc.NewSubConnCh: 315 addrs := <-cc.NewSubConnAddrsCh 316 t.Fatalf("unexpected NewSubConn with address %v", addrs) 317 } 318 319 p2 := <-cc.NewPickerCh 320 for _, tt := range []struct { 321 pickInfo balancer.PickInfo 322 wantSC balancer.SubConn 323 wantErr error 324 }{ 325 { 326 pickInfo: balancer.PickInfo{ 327 Ctx: SetPickedCluster(context.Background(), "cds:cluster_1"), 328 }, 329 wantSC: m1[wantAddrs[0]], 330 }, 331 { 332 pickInfo: balancer.PickInfo{ 333 Ctx: SetPickedCluster(context.Background(), "cds:cluster_2"), 334 }, 335 wantSC: m1[wantAddrs[1]], 336 }, 337 { 338 pickInfo: balancer.PickInfo{ 339 Ctx: SetPickedCluster(context.Background(), "cds:cluster_3"), 340 }, 341 wantSC: m1[wantAddrs[2]], 342 }, 343 { 344 pickInfo: balancer.PickInfo{ 345 Ctx: SetPickedCluster(context.Background(), "cds:notacluster"), 346 }, 347 wantErr: status.Errorf(codes.Unavailable, `unknown cluster selected for RPC: "cds:notacluster"`), 348 }, 349 } { 350 testPick(t, p2, tt.pickInfo, tt.wantSC, tt.wantErr) 351 } 352 } 353 354 // TestRoutingConfigUpdateDeleteAll covers the cases the balancer receives 355 // config update with no clusters. Pick should fail with details in error. 356 func TestRoutingConfigUpdateDeleteAll(t *testing.T) { 357 cc := testutils.NewTestClientConn(t) 358 rtb := rtBuilder.Build(cc, balancer.BuildOptions{}) 359 360 configJSON1 := `{ 361 "children": { 362 "cds:cluster_1":{ "childPolicy": [{"ignore_attrs_round_robin":""}] }, 363 "cds:cluster_2":{ "childPolicy": [{"ignore_attrs_round_robin":""}] } 364 } 365 }` 366 367 config1, err := rtParser.ParseConfig([]byte(configJSON1)) 368 if err != nil { 369 t.Fatalf("failed to parse balancer config: %v", err) 370 } 371 372 // Send the config, and an address with hierarchy path ["cluster_1"]. 373 wantAddrs := []resolver.Address{ 374 {Addr: testBackendAddrStrs[0], BalancerAttributes: nil}, 375 {Addr: testBackendAddrStrs[1], BalancerAttributes: nil}, 376 } 377 if err := rtb.UpdateClientConnState(balancer.ClientConnState{ 378 ResolverState: resolver.State{Addresses: []resolver.Address{ 379 hierarchy.Set(wantAddrs[0], []string{"cds:cluster_1"}), 380 hierarchy.Set(wantAddrs[1], []string{"cds:cluster_2"}), 381 }}, 382 BalancerConfig: config1, 383 }); err != nil { 384 t.Fatalf("failed to update ClientConn state: %v", err) 385 } 386 387 m1 := make(map[resolver.Address]balancer.SubConn) 388 // Verify that a subconn is created with the address, and the hierarchy path 389 // in the address is cleared. 390 for range wantAddrs { 391 addrs := <-cc.NewSubConnAddrsCh 392 if len(hierarchy.Get(addrs[0])) != 0 { 393 t.Fatalf("NewSubConn with address %+v, attrs %+v, want address with hierarchy cleared", addrs[0], addrs[0].BalancerAttributes) 394 } 395 sc := <-cc.NewSubConnCh 396 // Clear the attributes before adding to map. 397 addrs[0].BalancerAttributes = nil 398 m1[addrs[0]] = sc 399 rtb.UpdateSubConnState(sc, balancer.SubConnState{ConnectivityState: connectivity.Connecting}) 400 rtb.UpdateSubConnState(sc, balancer.SubConnState{ConnectivityState: connectivity.Ready}) 401 } 402 403 p1 := <-cc.NewPickerCh 404 for _, tt := range []struct { 405 pickInfo balancer.PickInfo 406 wantSC balancer.SubConn 407 wantErr error 408 }{ 409 { 410 pickInfo: balancer.PickInfo{ 411 Ctx: SetPickedCluster(context.Background(), "cds:cluster_1"), 412 }, 413 wantSC: m1[wantAddrs[0]], 414 }, 415 { 416 pickInfo: balancer.PickInfo{ 417 Ctx: SetPickedCluster(context.Background(), "cds:cluster_2"), 418 }, 419 wantSC: m1[wantAddrs[1]], 420 }, 421 { 422 pickInfo: balancer.PickInfo{ 423 Ctx: SetPickedCluster(context.Background(), "cds:notacluster"), 424 }, 425 wantErr: status.Errorf(codes.Unavailable, `unknown cluster selected for RPC: "cds:notacluster"`), 426 }, 427 } { 428 testPick(t, p1, tt.pickInfo, tt.wantSC, tt.wantErr) 429 } 430 431 // A config update with no clusters. 432 configJSON2 := `{}` 433 config2, err := rtParser.ParseConfig([]byte(configJSON2)) 434 if err != nil { 435 t.Fatalf("failed to parse balancer config: %v", err) 436 } 437 if err := rtb.UpdateClientConnState(balancer.ClientConnState{ 438 BalancerConfig: config2, 439 }); err != nil { 440 t.Fatalf("failed to update ClientConn state: %v", err) 441 } 442 443 // Expect two removed subconns. 444 for range wantAddrs { 445 select { 446 case <-time.After(time.Millisecond * 500): 447 t.Fatalf("timeout waiting for remove subconn") 448 case <-cc.RemoveSubConnCh: 449 } 450 } 451 452 p2 := <-cc.NewPickerCh 453 for i := 0; i < 5; i++ { 454 gotSCSt, err := p2.Pick(balancer.PickInfo{Ctx: SetPickedCluster(context.Background(), "cds:notacluster")}) 455 if fmt.Sprint(err) != status.Errorf(codes.Unavailable, `unknown cluster selected for RPC: "cds:notacluster"`).Error() { 456 t.Fatalf("picker.Pick, got %v, %v, want error %v", gotSCSt, err, `unknown cluster selected for RPC: "cds:notacluster"`) 457 } 458 } 459 460 // Resend the previous config with clusters 461 if err := rtb.UpdateClientConnState(balancer.ClientConnState{ 462 ResolverState: resolver.State{Addresses: []resolver.Address{ 463 hierarchy.Set(wantAddrs[0], []string{"cds:cluster_1"}), 464 hierarchy.Set(wantAddrs[1], []string{"cds:cluster_2"}), 465 }}, 466 BalancerConfig: config1, 467 }); err != nil { 468 t.Fatalf("failed to update ClientConn state: %v", err) 469 } 470 471 m2 := make(map[resolver.Address]balancer.SubConn) 472 // Verify that a subconn is created with the address, and the hierarchy path 473 // in the address is cleared. 474 for range wantAddrs { 475 addrs := <-cc.NewSubConnAddrsCh 476 if len(hierarchy.Get(addrs[0])) != 0 { 477 t.Fatalf("NewSubConn with address %+v, attrs %+v, want address with hierarchy cleared", addrs[0], addrs[0].BalancerAttributes) 478 } 479 sc := <-cc.NewSubConnCh 480 // Clear the attributes before adding to map. 481 addrs[0].BalancerAttributes = nil 482 m2[addrs[0]] = sc 483 rtb.UpdateSubConnState(sc, balancer.SubConnState{ConnectivityState: connectivity.Connecting}) 484 rtb.UpdateSubConnState(sc, balancer.SubConnState{ConnectivityState: connectivity.Ready}) 485 } 486 487 p3 := <-cc.NewPickerCh 488 for _, tt := range []struct { 489 pickInfo balancer.PickInfo 490 wantSC balancer.SubConn 491 wantErr error 492 }{ 493 { 494 pickInfo: balancer.PickInfo{ 495 Ctx: SetPickedCluster(context.Background(), "cds:cluster_1"), 496 }, 497 wantSC: m2[wantAddrs[0]], 498 }, 499 { 500 pickInfo: balancer.PickInfo{ 501 Ctx: SetPickedCluster(context.Background(), "cds:cluster_2"), 502 }, 503 wantSC: m2[wantAddrs[1]], 504 }, 505 { 506 pickInfo: balancer.PickInfo{ 507 Ctx: SetPickedCluster(context.Background(), "cds:notacluster"), 508 }, 509 wantErr: status.Errorf(codes.Unavailable, `unknown cluster selected for RPC: "cds:notacluster"`), 510 }, 511 } { 512 testPick(t, p3, tt.pickInfo, tt.wantSC, tt.wantErr) 513 } 514 } 515 516 func TestClusterManagerForwardsBalancerBuildOptions(t *testing.T) { 517 const ( 518 balancerName = "stubBalancer-TestClusterManagerForwardsBalancerBuildOptions" 519 parent = int64(1234) 520 userAgent = "ua" 521 defaultTestTimeout = 1 * time.Second 522 ) 523 524 // Setup the stub balancer such that we can read the build options passed to 525 // it in the UpdateClientConnState method. 526 ccsCh := testutils.NewChannel() 527 bOpts := balancer.BuildOptions{ 528 DialCreds: insecure.NewCredentials(), 529 ChannelzParentID: parent, 530 CustomUserAgent: userAgent, 531 } 532 stub.Register(balancerName, stub.BalancerFuncs{ 533 UpdateClientConnState: func(bd *stub.BalancerData, _ balancer.ClientConnState) error { 534 if !cmp.Equal(bd.BuildOptions, bOpts) { 535 err := fmt.Errorf("buildOptions in child balancer: %v, want %v", bd, bOpts) 536 ccsCh.Send(err) 537 return err 538 } 539 ccsCh.Send(nil) 540 return nil 541 }, 542 }) 543 544 cc := testutils.NewTestClientConn(t) 545 rtb := rtBuilder.Build(cc, bOpts) 546 547 configJSON1 := fmt.Sprintf(`{ 548 "children": { 549 "cds:cluster_1":{ "childPolicy": [{"%s":""}] } 550 } 551 }`, balancerName) 552 config1, err := rtParser.ParseConfig([]byte(configJSON1)) 553 if err != nil { 554 t.Fatalf("failed to parse balancer config: %v", err) 555 } 556 557 if err := rtb.UpdateClientConnState(balancer.ClientConnState{BalancerConfig: config1}); err != nil { 558 t.Fatalf("failed to update ClientConn state: %v", err) 559 } 560 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 561 defer cancel() 562 v, err := ccsCh.Receive(ctx) 563 if err != nil { 564 t.Fatalf("timed out waiting for UpdateClientConnState result: %v", err) 565 } 566 if v != nil { 567 t.Fatal(v) 568 } 569 } 570 571 const initIdleBalancerName = "test-init-Idle-balancer" 572 573 var errTestInitIdle = fmt.Errorf("init Idle balancer error 0") 574 575 func init() { 576 stub.Register(initIdleBalancerName, stub.BalancerFuncs{ 577 UpdateClientConnState: func(bd *stub.BalancerData, opts balancer.ClientConnState) error { 578 bd.ClientConn.NewSubConn(opts.ResolverState.Addresses, balancer.NewSubConnOptions{}) 579 return nil 580 }, 581 UpdateSubConnState: func(bd *stub.BalancerData, sc balancer.SubConn, state balancer.SubConnState) { 582 err := fmt.Errorf("wrong picker error") 583 if state.ConnectivityState == connectivity.Idle { 584 err = errTestInitIdle 585 } 586 bd.ClientConn.UpdateState(balancer.State{ 587 ConnectivityState: state.ConnectivityState, 588 Picker: &testutils.TestConstPicker{Err: err}, 589 }) 590 }, 591 }) 592 } 593 594 // TestInitialIdle covers the case that if the child reports Idle, the overall 595 // state will be Idle. 596 func TestInitialIdle(t *testing.T) { 597 cc := testutils.NewTestClientConn(t) 598 rtb := rtBuilder.Build(cc, balancer.BuildOptions{}) 599 600 configJSON1 := `{ 601 "children": { 602 "cds:cluster_1":{ "childPolicy": [{"test-init-Idle-balancer":""}] } 603 } 604 }` 605 606 config1, err := rtParser.ParseConfig([]byte(configJSON1)) 607 if err != nil { 608 t.Fatalf("failed to parse balancer config: %v", err) 609 } 610 611 // Send the config, and an address with hierarchy path ["cluster_1"]. 612 wantAddrs := []resolver.Address{ 613 {Addr: testBackendAddrStrs[0], BalancerAttributes: nil}, 614 } 615 if err := rtb.UpdateClientConnState(balancer.ClientConnState{ 616 ResolverState: resolver.State{Addresses: []resolver.Address{ 617 hierarchy.Set(wantAddrs[0], []string{"cds:cluster_1"}), 618 }}, 619 BalancerConfig: config1, 620 }); err != nil { 621 t.Fatalf("failed to update ClientConn state: %v", err) 622 } 623 624 // Verify that a subconn is created with the address, and the hierarchy path 625 // in the address is cleared. 626 for range wantAddrs { 627 sc := <-cc.NewSubConnCh 628 rtb.UpdateSubConnState(sc, balancer.SubConnState{ConnectivityState: connectivity.Idle}) 629 } 630 631 if state1 := <-cc.NewStateCh; state1 != connectivity.Idle { 632 t.Fatalf("Received aggregated state: %v, want Idle", state1) 633 } 634 }