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  }