gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/grpc/xds/internal/balancer/clusterresolver/configbuilder_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 clusterresolver
    20  
    21  import (
    22  	"bytes"
    23  	"encoding/json"
    24  	"fmt"
    25  	"sort"
    26  	"testing"
    27  
    28  	"gitee.com/ks-custle/core-gm/grpc/attributes"
    29  	"gitee.com/ks-custle/core-gm/grpc/balancer"
    30  	"gitee.com/ks-custle/core-gm/grpc/balancer/roundrobin"
    31  	"gitee.com/ks-custle/core-gm/grpc/balancer/weightedroundrobin"
    32  	"gitee.com/ks-custle/core-gm/grpc/balancer/weightedtarget"
    33  	"gitee.com/ks-custle/core-gm/grpc/internal/hierarchy"
    34  	internalserviceconfig "gitee.com/ks-custle/core-gm/grpc/internal/serviceconfig"
    35  	"gitee.com/ks-custle/core-gm/grpc/resolver"
    36  	"gitee.com/ks-custle/core-gm/grpc/xds/internal"
    37  	"gitee.com/ks-custle/core-gm/grpc/xds/internal/balancer/clusterimpl"
    38  	"gitee.com/ks-custle/core-gm/grpc/xds/internal/balancer/priority"
    39  	"gitee.com/ks-custle/core-gm/grpc/xds/internal/balancer/ringhash"
    40  	"gitee.com/ks-custle/core-gm/grpc/xds/internal/xdsclient/xdsresource"
    41  	"github.com/google/go-cmp/cmp"
    42  )
    43  
    44  const (
    45  	testLRSServer       = "test-lrs-server"
    46  	testMaxRequests     = 314
    47  	testEDSServiceName  = "service-name-from-parent"
    48  	testDropCategory    = "test-drops"
    49  	testDropOverMillion = 1
    50  
    51  	localityCount      = 5
    52  	addressPerLocality = 2
    53  )
    54  
    55  var (
    56  	testLocalityIDs []internal.LocalityID
    57  	testAddressStrs [][]string
    58  	testEndpoints   [][]xdsresource.Endpoint
    59  
    60  	testLocalitiesP0, testLocalitiesP1 []xdsresource.Locality
    61  
    62  	addrCmpOpts = cmp.Options{
    63  		cmp.AllowUnexported(attributes.Attributes{}),
    64  		cmp.Transformer("SortAddrs", func(in []resolver.Address) []resolver.Address {
    65  			out := append([]resolver.Address(nil), in...) // Copy input to avoid mutating it
    66  			sort.Slice(out, func(i, j int) bool {
    67  				return out[i].Addr < out[j].Addr
    68  			})
    69  			return out
    70  		})}
    71  )
    72  
    73  func init() {
    74  	for i := 0; i < localityCount; i++ {
    75  		testLocalityIDs = append(testLocalityIDs, internal.LocalityID{Zone: fmt.Sprintf("test-zone-%d", i)})
    76  		var (
    77  			addrs []string
    78  			ends  []xdsresource.Endpoint
    79  		)
    80  		for j := 0; j < addressPerLocality; j++ {
    81  			addr := fmt.Sprintf("addr-%d-%d", i, j)
    82  			addrs = append(addrs, addr)
    83  			ends = append(ends, xdsresource.Endpoint{
    84  				Address:      addr,
    85  				HealthStatus: xdsresource.EndpointHealthStatusHealthy,
    86  			})
    87  		}
    88  		testAddressStrs = append(testAddressStrs, addrs)
    89  		testEndpoints = append(testEndpoints, ends)
    90  	}
    91  
    92  	testLocalitiesP0 = []xdsresource.Locality{
    93  		{
    94  			Endpoints: testEndpoints[0],
    95  			ID:        testLocalityIDs[0],
    96  			Weight:    20,
    97  			Priority:  0,
    98  		},
    99  		{
   100  			Endpoints: testEndpoints[1],
   101  			ID:        testLocalityIDs[1],
   102  			Weight:    80,
   103  			Priority:  0,
   104  		},
   105  	}
   106  	testLocalitiesP1 = []xdsresource.Locality{
   107  		{
   108  			Endpoints: testEndpoints[2],
   109  			ID:        testLocalityIDs[2],
   110  			Weight:    20,
   111  			Priority:  1,
   112  		},
   113  		{
   114  			Endpoints: testEndpoints[3],
   115  			ID:        testLocalityIDs[3],
   116  			Weight:    80,
   117  			Priority:  1,
   118  		},
   119  	}
   120  }
   121  
   122  // TestBuildPriorityConfigJSON is a sanity check that the built balancer config
   123  // can be parsed. The behavior test is covered by TestBuildPriorityConfig.
   124  func TestBuildPriorityConfigJSON(t *testing.T) {
   125  	gotConfig, _, err := buildPriorityConfigJSON([]priorityConfig{
   126  		{
   127  			mechanism: DiscoveryMechanism{
   128  				Cluster:                 testClusterName,
   129  				LoadReportingServerName: newString(testLRSServer),
   130  				MaxConcurrentRequests:   newUint32(testMaxRequests),
   131  				Type:                    DiscoveryMechanismTypeEDS,
   132  				EDSServiceName:          testEDSServiceName,
   133  			},
   134  			edsResp: xdsresource.EndpointsUpdate{
   135  				Drops: []xdsresource.OverloadDropConfig{
   136  					{
   137  						Category:    testDropCategory,
   138  						Numerator:   testDropOverMillion,
   139  						Denominator: million,
   140  					},
   141  				},
   142  				Localities: []xdsresource.Locality{
   143  					testLocalitiesP0[0],
   144  					testLocalitiesP0[1],
   145  					testLocalitiesP1[0],
   146  					testLocalitiesP1[1],
   147  				},
   148  			},
   149  		},
   150  		{
   151  			mechanism: DiscoveryMechanism{
   152  				Type: DiscoveryMechanismTypeLogicalDNS,
   153  			},
   154  			addresses: testAddressStrs[4],
   155  		},
   156  	}, nil)
   157  	if err != nil {
   158  		t.Fatalf("buildPriorityConfigJSON(...) failed: %v", err)
   159  	}
   160  
   161  	var prettyGot bytes.Buffer
   162  	if err := json.Indent(&prettyGot, gotConfig, ">>> ", "  "); err != nil {
   163  		t.Fatalf("json.Indent() failed: %v", err)
   164  	}
   165  	// Print the indented json if this test fails.
   166  	t.Log(prettyGot.String())
   167  
   168  	priorityB := balancer.Get(priority.Name)
   169  	if _, err = priorityB.(balancer.ConfigParser).ParseConfig(gotConfig); err != nil {
   170  		t.Fatalf("ParseConfig(%+v) failed: %v", gotConfig, err)
   171  	}
   172  }
   173  
   174  func TestBuildPriorityConfig(t *testing.T) {
   175  	gotConfig, gotAddrs, _ := buildPriorityConfig([]priorityConfig{
   176  		{
   177  			mechanism: DiscoveryMechanism{
   178  				Cluster:                 testClusterName,
   179  				LoadReportingServerName: newString(testLRSServer),
   180  				MaxConcurrentRequests:   newUint32(testMaxRequests),
   181  				Type:                    DiscoveryMechanismTypeEDS,
   182  				EDSServiceName:          testEDSServiceName,
   183  			},
   184  			edsResp: xdsresource.EndpointsUpdate{
   185  				Drops: []xdsresource.OverloadDropConfig{
   186  					{
   187  						Category:    testDropCategory,
   188  						Numerator:   testDropOverMillion,
   189  						Denominator: million,
   190  					},
   191  				},
   192  				Localities: []xdsresource.Locality{
   193  					testLocalitiesP0[0],
   194  					testLocalitiesP0[1],
   195  					testLocalitiesP1[0],
   196  					testLocalitiesP1[1],
   197  				},
   198  			},
   199  		},
   200  		{
   201  			mechanism: DiscoveryMechanism{
   202  				Type: DiscoveryMechanismTypeLogicalDNS,
   203  			},
   204  			addresses: testAddressStrs[4],
   205  		},
   206  	}, nil)
   207  
   208  	wantConfig := &priority.LBConfig{
   209  		Children: map[string]*priority.Child{
   210  			"priority-0-0": {
   211  				Config: &internalserviceconfig.BalancerConfig{
   212  					Name: clusterimpl.Name,
   213  					Config: &clusterimpl.LBConfig{
   214  						Cluster:                 testClusterName,
   215  						EDSServiceName:          testEDSServiceName,
   216  						LoadReportingServerName: newString(testLRSServer),
   217  						MaxConcurrentRequests:   newUint32(testMaxRequests),
   218  						DropCategories: []clusterimpl.DropConfig{
   219  							{
   220  								Category:           testDropCategory,
   221  								RequestsPerMillion: testDropOverMillion,
   222  							},
   223  						},
   224  						ChildPolicy: &internalserviceconfig.BalancerConfig{
   225  							Name: weightedtarget.Name,
   226  							Config: &weightedtarget.LBConfig{
   227  								Targets: map[string]weightedtarget.Target{
   228  									assertString(testLocalityIDs[0].ToString): {
   229  										Weight:      20,
   230  										ChildPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name},
   231  									},
   232  									assertString(testLocalityIDs[1].ToString): {
   233  										Weight:      80,
   234  										ChildPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name},
   235  									},
   236  								},
   237  							},
   238  						},
   239  					},
   240  				},
   241  				IgnoreReresolutionRequests: true,
   242  			},
   243  			"priority-0-1": {
   244  				Config: &internalserviceconfig.BalancerConfig{
   245  					Name: clusterimpl.Name,
   246  					Config: &clusterimpl.LBConfig{
   247  						Cluster:                 testClusterName,
   248  						EDSServiceName:          testEDSServiceName,
   249  						LoadReportingServerName: newString(testLRSServer),
   250  						MaxConcurrentRequests:   newUint32(testMaxRequests),
   251  						DropCategories: []clusterimpl.DropConfig{
   252  							{
   253  								Category:           testDropCategory,
   254  								RequestsPerMillion: testDropOverMillion,
   255  							},
   256  						},
   257  						ChildPolicy: &internalserviceconfig.BalancerConfig{
   258  							Name: weightedtarget.Name,
   259  							Config: &weightedtarget.LBConfig{
   260  								Targets: map[string]weightedtarget.Target{
   261  									assertString(testLocalityIDs[2].ToString): {
   262  										Weight:      20,
   263  										ChildPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name},
   264  									},
   265  									assertString(testLocalityIDs[3].ToString): {
   266  										Weight:      80,
   267  										ChildPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name},
   268  									},
   269  								},
   270  							},
   271  						},
   272  					},
   273  				},
   274  				IgnoreReresolutionRequests: true,
   275  			},
   276  			"priority-1": {
   277  				Config: &internalserviceconfig.BalancerConfig{
   278  					Name: clusterimpl.Name,
   279  					Config: &clusterimpl.LBConfig{
   280  						ChildPolicy: &internalserviceconfig.BalancerConfig{Name: "pick_first"},
   281  					},
   282  				},
   283  				IgnoreReresolutionRequests: false,
   284  			},
   285  		},
   286  		Priorities: []string{"priority-0-0", "priority-0-1", "priority-1"},
   287  	}
   288  	wantAddrs := []resolver.Address{
   289  		testAddrWithAttrs(testAddressStrs[0][0], nil, "priority-0-0", &testLocalityIDs[0]),
   290  		testAddrWithAttrs(testAddressStrs[0][1], nil, "priority-0-0", &testLocalityIDs[0]),
   291  		testAddrWithAttrs(testAddressStrs[1][0], nil, "priority-0-0", &testLocalityIDs[1]),
   292  		testAddrWithAttrs(testAddressStrs[1][1], nil, "priority-0-0", &testLocalityIDs[1]),
   293  		testAddrWithAttrs(testAddressStrs[2][0], nil, "priority-0-1", &testLocalityIDs[2]),
   294  		testAddrWithAttrs(testAddressStrs[2][1], nil, "priority-0-1", &testLocalityIDs[2]),
   295  		testAddrWithAttrs(testAddressStrs[3][0], nil, "priority-0-1", &testLocalityIDs[3]),
   296  		testAddrWithAttrs(testAddressStrs[3][1], nil, "priority-0-1", &testLocalityIDs[3]),
   297  		testAddrWithAttrs(testAddressStrs[4][0], nil, "priority-1", nil),
   298  		testAddrWithAttrs(testAddressStrs[4][1], nil, "priority-1", nil),
   299  	}
   300  
   301  	if diff := cmp.Diff(gotConfig, wantConfig); diff != "" {
   302  		t.Errorf("buildPriorityConfig() diff (-got +want) %v", diff)
   303  	}
   304  	if diff := cmp.Diff(gotAddrs, wantAddrs, addrCmpOpts); diff != "" {
   305  		t.Errorf("buildPriorityConfig() diff (-got +want) %v", diff)
   306  	}
   307  }
   308  
   309  func TestBuildClusterImplConfigForDNS(t *testing.T) {
   310  	gotName, gotConfig, gotAddrs := buildClusterImplConfigForDNS(3, testAddressStrs[0])
   311  	wantName := "priority-3"
   312  	wantConfig := &clusterimpl.LBConfig{
   313  		ChildPolicy: &internalserviceconfig.BalancerConfig{
   314  			Name: "pick_first",
   315  		},
   316  	}
   317  	wantAddrs := []resolver.Address{
   318  		hierarchy.Set(resolver.Address{Addr: testAddressStrs[0][0]}, []string{"priority-3"}),
   319  		hierarchy.Set(resolver.Address{Addr: testAddressStrs[0][1]}, []string{"priority-3"}),
   320  	}
   321  
   322  	if diff := cmp.Diff(gotName, wantName); diff != "" {
   323  		t.Errorf("buildClusterImplConfigForDNS() diff (-got +want) %v", diff)
   324  	}
   325  	if diff := cmp.Diff(gotConfig, wantConfig); diff != "" {
   326  		t.Errorf("buildClusterImplConfigForDNS() diff (-got +want) %v", diff)
   327  	}
   328  	if diff := cmp.Diff(gotAddrs, wantAddrs, addrCmpOpts); diff != "" {
   329  		t.Errorf("buildClusterImplConfigForDNS() diff (-got +want) %v", diff)
   330  	}
   331  }
   332  
   333  func TestBuildClusterImplConfigForEDS(t *testing.T) {
   334  	gotNames, gotConfigs, gotAddrs, _ := buildClusterImplConfigForEDS(
   335  		2,
   336  		xdsresource.EndpointsUpdate{
   337  			Drops: []xdsresource.OverloadDropConfig{
   338  				{
   339  					Category:    testDropCategory,
   340  					Numerator:   testDropOverMillion,
   341  					Denominator: million,
   342  				},
   343  			},
   344  			Localities: []xdsresource.Locality{
   345  				{
   346  					Endpoints: testEndpoints[3],
   347  					ID:        testLocalityIDs[3],
   348  					Weight:    80,
   349  					Priority:  1,
   350  				}, {
   351  					Endpoints: testEndpoints[1],
   352  					ID:        testLocalityIDs[1],
   353  					Weight:    80,
   354  					Priority:  0,
   355  				}, {
   356  					Endpoints: testEndpoints[2],
   357  					ID:        testLocalityIDs[2],
   358  					Weight:    20,
   359  					Priority:  1,
   360  				}, {
   361  					Endpoints: testEndpoints[0],
   362  					ID:        testLocalityIDs[0],
   363  					Weight:    20,
   364  					Priority:  0,
   365  				},
   366  			},
   367  		},
   368  		DiscoveryMechanism{
   369  			Cluster:                 testClusterName,
   370  			MaxConcurrentRequests:   newUint32(testMaxRequests),
   371  			LoadReportingServerName: newString(testLRSServer),
   372  			Type:                    DiscoveryMechanismTypeEDS,
   373  			EDSServiceName:          testEDSServiceName,
   374  		},
   375  		nil,
   376  	)
   377  
   378  	wantNames := []string{
   379  		fmt.Sprintf("priority-%v-%v", 2, 0),
   380  		fmt.Sprintf("priority-%v-%v", 2, 1),
   381  	}
   382  	wantConfigs := map[string]*clusterimpl.LBConfig{
   383  		"priority-2-0": {
   384  			Cluster:                 testClusterName,
   385  			EDSServiceName:          testEDSServiceName,
   386  			LoadReportingServerName: newString(testLRSServer),
   387  			MaxConcurrentRequests:   newUint32(testMaxRequests),
   388  			DropCategories: []clusterimpl.DropConfig{
   389  				{
   390  					Category:           testDropCategory,
   391  					RequestsPerMillion: testDropOverMillion,
   392  				},
   393  			},
   394  			ChildPolicy: &internalserviceconfig.BalancerConfig{
   395  				Name: weightedtarget.Name,
   396  				Config: &weightedtarget.LBConfig{
   397  					Targets: map[string]weightedtarget.Target{
   398  						assertString(testLocalityIDs[0].ToString): {
   399  							Weight:      20,
   400  							ChildPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name},
   401  						},
   402  						assertString(testLocalityIDs[1].ToString): {
   403  							Weight:      80,
   404  							ChildPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name},
   405  						},
   406  					},
   407  				},
   408  			},
   409  		},
   410  		"priority-2-1": {
   411  			Cluster:                 testClusterName,
   412  			EDSServiceName:          testEDSServiceName,
   413  			LoadReportingServerName: newString(testLRSServer),
   414  			MaxConcurrentRequests:   newUint32(testMaxRequests),
   415  			DropCategories: []clusterimpl.DropConfig{
   416  				{
   417  					Category:           testDropCategory,
   418  					RequestsPerMillion: testDropOverMillion,
   419  				},
   420  			},
   421  			ChildPolicy: &internalserviceconfig.BalancerConfig{
   422  				Name: weightedtarget.Name,
   423  				Config: &weightedtarget.LBConfig{
   424  					Targets: map[string]weightedtarget.Target{
   425  						assertString(testLocalityIDs[2].ToString): {
   426  							Weight:      20,
   427  							ChildPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name},
   428  						},
   429  						assertString(testLocalityIDs[3].ToString): {
   430  							Weight:      80,
   431  							ChildPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name},
   432  						},
   433  					},
   434  				},
   435  			},
   436  		},
   437  	}
   438  	wantAddrs := []resolver.Address{
   439  		testAddrWithAttrs(testAddressStrs[0][0], nil, "priority-2-0", &testLocalityIDs[0]),
   440  		testAddrWithAttrs(testAddressStrs[0][1], nil, "priority-2-0", &testLocalityIDs[0]),
   441  		testAddrWithAttrs(testAddressStrs[1][0], nil, "priority-2-0", &testLocalityIDs[1]),
   442  		testAddrWithAttrs(testAddressStrs[1][1], nil, "priority-2-0", &testLocalityIDs[1]),
   443  		testAddrWithAttrs(testAddressStrs[2][0], nil, "priority-2-1", &testLocalityIDs[2]),
   444  		testAddrWithAttrs(testAddressStrs[2][1], nil, "priority-2-1", &testLocalityIDs[2]),
   445  		testAddrWithAttrs(testAddressStrs[3][0], nil, "priority-2-1", &testLocalityIDs[3]),
   446  		testAddrWithAttrs(testAddressStrs[3][1], nil, "priority-2-1", &testLocalityIDs[3]),
   447  	}
   448  
   449  	if diff := cmp.Diff(gotNames, wantNames); diff != "" {
   450  		t.Errorf("buildClusterImplConfigForEDS() diff (-got +want) %v", diff)
   451  	}
   452  	if diff := cmp.Diff(gotConfigs, wantConfigs); diff != "" {
   453  		t.Errorf("buildClusterImplConfigForEDS() diff (-got +want) %v", diff)
   454  	}
   455  	if diff := cmp.Diff(gotAddrs, wantAddrs, addrCmpOpts); diff != "" {
   456  		t.Errorf("buildClusterImplConfigForEDS() diff (-got +want) %v", diff)
   457  	}
   458  
   459  }
   460  
   461  func TestGroupLocalitiesByPriority(t *testing.T) {
   462  	tests := []struct {
   463  		name           string
   464  		localities     []xdsresource.Locality
   465  		wantPriorities []string
   466  		wantLocalities map[string][]xdsresource.Locality
   467  	}{
   468  		{
   469  			name:           "1 locality 1 priority",
   470  			localities:     []xdsresource.Locality{testLocalitiesP0[0]},
   471  			wantPriorities: []string{"0"},
   472  			wantLocalities: map[string][]xdsresource.Locality{
   473  				"0": {testLocalitiesP0[0]},
   474  			},
   475  		},
   476  		{
   477  			name:           "2 locality 1 priority",
   478  			localities:     []xdsresource.Locality{testLocalitiesP0[0], testLocalitiesP0[1]},
   479  			wantPriorities: []string{"0"},
   480  			wantLocalities: map[string][]xdsresource.Locality{
   481  				"0": {testLocalitiesP0[0], testLocalitiesP0[1]},
   482  			},
   483  		},
   484  		{
   485  			name:           "1 locality in each",
   486  			localities:     []xdsresource.Locality{testLocalitiesP0[0], testLocalitiesP1[0]},
   487  			wantPriorities: []string{"0", "1"},
   488  			wantLocalities: map[string][]xdsresource.Locality{
   489  				"0": {testLocalitiesP0[0]},
   490  				"1": {testLocalitiesP1[0]},
   491  			},
   492  		},
   493  		{
   494  			name: "2 localities in each sorted",
   495  			localities: []xdsresource.Locality{
   496  				testLocalitiesP0[0], testLocalitiesP0[1],
   497  				testLocalitiesP1[0], testLocalitiesP1[1]},
   498  			wantPriorities: []string{"0", "1"},
   499  			wantLocalities: map[string][]xdsresource.Locality{
   500  				"0": {testLocalitiesP0[0], testLocalitiesP0[1]},
   501  				"1": {testLocalitiesP1[0], testLocalitiesP1[1]},
   502  			},
   503  		},
   504  		{
   505  			// The localities are given in order [p1, p0, p1, p0], but the
   506  			// returned priority list must be sorted [p0, p1], because the list
   507  			// order is the priority order.
   508  			name: "2 localities in each needs to sort",
   509  			localities: []xdsresource.Locality{
   510  				testLocalitiesP1[1], testLocalitiesP0[1],
   511  				testLocalitiesP1[0], testLocalitiesP0[0]},
   512  			wantPriorities: []string{"0", "1"},
   513  			wantLocalities: map[string][]xdsresource.Locality{
   514  				"0": {testLocalitiesP0[1], testLocalitiesP0[0]},
   515  				"1": {testLocalitiesP1[1], testLocalitiesP1[0]},
   516  			},
   517  		},
   518  	}
   519  	for _, tt := range tests {
   520  		t.Run(tt.name, func(t *testing.T) {
   521  			gotPriorities, gotLocalities := groupLocalitiesByPriority(tt.localities)
   522  			if diff := cmp.Diff(gotPriorities, tt.wantPriorities); diff != "" {
   523  				t.Errorf("groupLocalitiesByPriority() diff(-got +want) %v", diff)
   524  			}
   525  			if diff := cmp.Diff(gotLocalities, tt.wantLocalities); diff != "" {
   526  				t.Errorf("groupLocalitiesByPriority() diff(-got +want) %v", diff)
   527  			}
   528  		})
   529  	}
   530  }
   531  
   532  func TestDedupSortedIntSlice(t *testing.T) {
   533  	tests := []struct {
   534  		name string
   535  		a    []int
   536  		want []int
   537  	}{
   538  		{
   539  			name: "empty",
   540  			a:    []int{},
   541  			want: []int{},
   542  		},
   543  		{
   544  			name: "no dup",
   545  			a:    []int{0, 1, 2, 3},
   546  			want: []int{0, 1, 2, 3},
   547  		},
   548  		{
   549  			name: "with dup",
   550  			a:    []int{0, 0, 1, 1, 1, 2, 3},
   551  			want: []int{0, 1, 2, 3},
   552  		},
   553  	}
   554  	for _, tt := range tests {
   555  		t.Run(tt.name, func(t *testing.T) {
   556  			if got := dedupSortedIntSlice(tt.a); !cmp.Equal(got, tt.want) {
   557  				t.Errorf("dedupSortedIntSlice() = %v, want %v, diff %v", got, tt.want, cmp.Diff(got, tt.want))
   558  			}
   559  		})
   560  	}
   561  }
   562  
   563  func TestPriorityLocalitiesToClusterImpl(t *testing.T) {
   564  	tests := []struct {
   565  		name         string
   566  		localities   []xdsresource.Locality
   567  		priorityName string
   568  		mechanism    DiscoveryMechanism
   569  		childPolicy  *internalserviceconfig.BalancerConfig
   570  		wantConfig   *clusterimpl.LBConfig
   571  		wantAddrs    []resolver.Address
   572  		wantErr      bool
   573  	}{{
   574  		name: "round robin as child, no LRS",
   575  		localities: []xdsresource.Locality{
   576  			{
   577  				Endpoints: []xdsresource.Endpoint{
   578  					{Address: "addr-1-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90},
   579  					{Address: "addr-1-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10},
   580  				},
   581  				ID:     internal.LocalityID{Zone: "test-zone-1"},
   582  				Weight: 20,
   583  			},
   584  			{
   585  				Endpoints: []xdsresource.Endpoint{
   586  					{Address: "addr-2-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90},
   587  					{Address: "addr-2-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10},
   588  				},
   589  				ID:     internal.LocalityID{Zone: "test-zone-2"},
   590  				Weight: 80,
   591  			},
   592  		},
   593  		priorityName: "test-priority",
   594  		childPolicy:  &internalserviceconfig.BalancerConfig{Name: rrName},
   595  		mechanism: DiscoveryMechanism{
   596  			Cluster:        testClusterName,
   597  			Type:           DiscoveryMechanismTypeEDS,
   598  			EDSServiceName: testEDSServcie,
   599  		},
   600  		// lrsServer is nil, so LRS policy will not be used.
   601  		wantConfig: &clusterimpl.LBConfig{
   602  			Cluster:        testClusterName,
   603  			EDSServiceName: testEDSServcie,
   604  			ChildPolicy: &internalserviceconfig.BalancerConfig{
   605  				Name: weightedtarget.Name,
   606  				Config: &weightedtarget.LBConfig{
   607  					Targets: map[string]weightedtarget.Target{
   608  						assertString(internal.LocalityID{Zone: "test-zone-1"}.ToString): {
   609  							Weight: 20,
   610  							ChildPolicy: &internalserviceconfig.BalancerConfig{
   611  								Name: roundrobin.Name,
   612  							},
   613  						},
   614  						assertString(internal.LocalityID{Zone: "test-zone-2"}.ToString): {
   615  							Weight: 80,
   616  							ChildPolicy: &internalserviceconfig.BalancerConfig{
   617  								Name: roundrobin.Name,
   618  							},
   619  						},
   620  					},
   621  				},
   622  			},
   623  		},
   624  		wantAddrs: []resolver.Address{
   625  			testAddrWithAttrs("addr-1-1", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-1"}),
   626  			testAddrWithAttrs("addr-1-2", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-1"}),
   627  			testAddrWithAttrs("addr-2-1", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-2"}),
   628  			testAddrWithAttrs("addr-2-2", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-2"}),
   629  		},
   630  	},
   631  		{
   632  			name: "ring_hash as child",
   633  			localities: []xdsresource.Locality{
   634  				{
   635  					Endpoints: []xdsresource.Endpoint{
   636  						{Address: "addr-1-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90},
   637  						{Address: "addr-1-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10},
   638  					},
   639  					ID:     internal.LocalityID{Zone: "test-zone-1"},
   640  					Weight: 20,
   641  				},
   642  				{
   643  					Endpoints: []xdsresource.Endpoint{
   644  						{Address: "addr-2-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90},
   645  						{Address: "addr-2-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10},
   646  					},
   647  					ID:     internal.LocalityID{Zone: "test-zone-2"},
   648  					Weight: 80,
   649  				},
   650  			},
   651  			priorityName: "test-priority",
   652  			childPolicy:  &internalserviceconfig.BalancerConfig{Name: rhName, Config: &ringhash.LBConfig{MinRingSize: 1, MaxRingSize: 2}},
   653  			// lrsServer is nil, so LRS policy will not be used.
   654  			wantConfig: &clusterimpl.LBConfig{
   655  				ChildPolicy: &internalserviceconfig.BalancerConfig{
   656  					Name:   ringhash.Name,
   657  					Config: &ringhash.LBConfig{MinRingSize: 1, MaxRingSize: 2},
   658  				},
   659  			},
   660  			wantAddrs: []resolver.Address{
   661  				testAddrWithAttrs("addr-1-1", newUint32(1800), "test-priority", &internal.LocalityID{Zone: "test-zone-1"}),
   662  				testAddrWithAttrs("addr-1-2", newUint32(200), "test-priority", &internal.LocalityID{Zone: "test-zone-1"}),
   663  				testAddrWithAttrs("addr-2-1", newUint32(7200), "test-priority", &internal.LocalityID{Zone: "test-zone-2"}),
   664  				testAddrWithAttrs("addr-2-2", newUint32(800), "test-priority", &internal.LocalityID{Zone: "test-zone-2"}),
   665  			},
   666  		},
   667  		{
   668  			name: "unsupported child",
   669  			localities: []xdsresource.Locality{{
   670  				Endpoints: []xdsresource.Endpoint{
   671  					{Address: "addr-1-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90},
   672  					{Address: "addr-1-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10},
   673  				},
   674  				ID:     internal.LocalityID{Zone: "test-zone-1"},
   675  				Weight: 20,
   676  			}},
   677  			priorityName: "test-priority",
   678  			childPolicy:  &internalserviceconfig.BalancerConfig{Name: "some-child"},
   679  			wantErr:      true,
   680  		},
   681  	}
   682  	for _, tt := range tests {
   683  		t.Run(tt.name, func(t *testing.T) {
   684  			got, got1, err := priorityLocalitiesToClusterImpl(tt.localities, tt.priorityName, tt.mechanism, nil, tt.childPolicy)
   685  			if (err != nil) != tt.wantErr {
   686  				t.Fatalf("priorityLocalitiesToClusterImpl() error = %v, wantErr %v", err, tt.wantErr)
   687  			}
   688  			if diff := cmp.Diff(got, tt.wantConfig); diff != "" {
   689  				t.Errorf("localitiesToWeightedTarget() diff (-got +want) %v", diff)
   690  			}
   691  			if diff := cmp.Diff(got1, tt.wantAddrs, cmp.AllowUnexported(attributes.Attributes{})); diff != "" {
   692  				t.Errorf("localitiesToWeightedTarget() diff (-got +want) %v", diff)
   693  			}
   694  		})
   695  	}
   696  }
   697  
   698  func TestLocalitiesToWeightedTarget(t *testing.T) {
   699  	tests := []struct {
   700  		name         string
   701  		localities   []xdsresource.Locality
   702  		priorityName string
   703  		childPolicy  *internalserviceconfig.BalancerConfig
   704  		lrsServer    *string
   705  		wantConfig   *weightedtarget.LBConfig
   706  		wantAddrs    []resolver.Address
   707  	}{
   708  		{
   709  			name: "roundrobin as child, with LRS",
   710  			localities: []xdsresource.Locality{
   711  				{
   712  					Endpoints: []xdsresource.Endpoint{
   713  						{Address: "addr-1-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy},
   714  						{Address: "addr-1-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy},
   715  					},
   716  					ID:     internal.LocalityID{Zone: "test-zone-1"},
   717  					Weight: 20,
   718  				},
   719  				{
   720  					Endpoints: []xdsresource.Endpoint{
   721  						{Address: "addr-2-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy},
   722  						{Address: "addr-2-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy},
   723  					},
   724  					ID:     internal.LocalityID{Zone: "test-zone-2"},
   725  					Weight: 80,
   726  				},
   727  			},
   728  			priorityName: "test-priority",
   729  			childPolicy:  &internalserviceconfig.BalancerConfig{Name: roundrobin.Name},
   730  			lrsServer:    newString("test-lrs-server"),
   731  			wantConfig: &weightedtarget.LBConfig{
   732  				Targets: map[string]weightedtarget.Target{
   733  					assertString(internal.LocalityID{Zone: "test-zone-1"}.ToString): {
   734  						Weight:      20,
   735  						ChildPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name},
   736  					},
   737  					assertString(internal.LocalityID{Zone: "test-zone-2"}.ToString): {
   738  						Weight:      80,
   739  						ChildPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name},
   740  					},
   741  				},
   742  			},
   743  			wantAddrs: []resolver.Address{
   744  				testAddrWithAttrs("addr-1-1", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-1"}),
   745  				testAddrWithAttrs("addr-1-2", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-1"}),
   746  				testAddrWithAttrs("addr-2-1", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-2"}),
   747  				testAddrWithAttrs("addr-2-2", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-2"}),
   748  			},
   749  		},
   750  		{
   751  			name: "roundrobin as child, no LRS",
   752  			localities: []xdsresource.Locality{
   753  				{
   754  					Endpoints: []xdsresource.Endpoint{
   755  						{Address: "addr-1-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy},
   756  						{Address: "addr-1-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy},
   757  					},
   758  					ID:     internal.LocalityID{Zone: "test-zone-1"},
   759  					Weight: 20,
   760  				},
   761  				{
   762  					Endpoints: []xdsresource.Endpoint{
   763  						{Address: "addr-2-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy},
   764  						{Address: "addr-2-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy},
   765  					},
   766  					ID:     internal.LocalityID{Zone: "test-zone-2"},
   767  					Weight: 80,
   768  				},
   769  			},
   770  			priorityName: "test-priority",
   771  			childPolicy:  &internalserviceconfig.BalancerConfig{Name: roundrobin.Name},
   772  			// lrsServer is nil, so LRS policy will not be used.
   773  			wantConfig: &weightedtarget.LBConfig{
   774  				Targets: map[string]weightedtarget.Target{
   775  					assertString(internal.LocalityID{Zone: "test-zone-1"}.ToString): {
   776  						Weight: 20,
   777  						ChildPolicy: &internalserviceconfig.BalancerConfig{
   778  							Name: roundrobin.Name,
   779  						},
   780  					},
   781  					assertString(internal.LocalityID{Zone: "test-zone-2"}.ToString): {
   782  						Weight: 80,
   783  						ChildPolicy: &internalserviceconfig.BalancerConfig{
   784  							Name: roundrobin.Name,
   785  						},
   786  					},
   787  				},
   788  			},
   789  			wantAddrs: []resolver.Address{
   790  				testAddrWithAttrs("addr-1-1", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-1"}),
   791  				testAddrWithAttrs("addr-1-2", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-1"}),
   792  				testAddrWithAttrs("addr-2-1", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-2"}),
   793  				testAddrWithAttrs("addr-2-2", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-2"}),
   794  			},
   795  		},
   796  		{
   797  			name: "weighted round robin as child, no LRS",
   798  			localities: []xdsresource.Locality{
   799  				{
   800  					Endpoints: []xdsresource.Endpoint{
   801  						{Address: "addr-1-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90},
   802  						{Address: "addr-1-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10},
   803  					},
   804  					ID:     internal.LocalityID{Zone: "test-zone-1"},
   805  					Weight: 20,
   806  				},
   807  				{
   808  					Endpoints: []xdsresource.Endpoint{
   809  						{Address: "addr-2-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90},
   810  						{Address: "addr-2-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10},
   811  					},
   812  					ID:     internal.LocalityID{Zone: "test-zone-2"},
   813  					Weight: 80,
   814  				},
   815  			},
   816  			priorityName: "test-priority",
   817  			childPolicy:  &internalserviceconfig.BalancerConfig{Name: weightedroundrobin.Name},
   818  			// lrsServer is nil, so LRS policy will not be used.
   819  			wantConfig: &weightedtarget.LBConfig{
   820  				Targets: map[string]weightedtarget.Target{
   821  					assertString(internal.LocalityID{Zone: "test-zone-1"}.ToString): {
   822  						Weight: 20,
   823  						ChildPolicy: &internalserviceconfig.BalancerConfig{
   824  							Name: weightedroundrobin.Name,
   825  						},
   826  					},
   827  					assertString(internal.LocalityID{Zone: "test-zone-2"}.ToString): {
   828  						Weight: 80,
   829  						ChildPolicy: &internalserviceconfig.BalancerConfig{
   830  							Name: weightedroundrobin.Name,
   831  						},
   832  					},
   833  				},
   834  			},
   835  			wantAddrs: []resolver.Address{
   836  				testAddrWithAttrs("addr-1-1", newUint32(90), "test-priority", &internal.LocalityID{Zone: "test-zone-1"}),
   837  				testAddrWithAttrs("addr-1-2", newUint32(10), "test-priority", &internal.LocalityID{Zone: "test-zone-1"}),
   838  				testAddrWithAttrs("addr-2-1", newUint32(90), "test-priority", &internal.LocalityID{Zone: "test-zone-2"}),
   839  				testAddrWithAttrs("addr-2-2", newUint32(10), "test-priority", &internal.LocalityID{Zone: "test-zone-2"}),
   840  			},
   841  		},
   842  	}
   843  	for _, tt := range tests {
   844  		t.Run(tt.name, func(t *testing.T) {
   845  			got, got1 := localitiesToWeightedTarget(tt.localities, tt.priorityName, tt.childPolicy)
   846  			if diff := cmp.Diff(got, tt.wantConfig); diff != "" {
   847  				t.Errorf("localitiesToWeightedTarget() diff (-got +want) %v", diff)
   848  			}
   849  			if diff := cmp.Diff(got1, tt.wantAddrs, cmp.AllowUnexported(attributes.Attributes{})); diff != "" {
   850  				t.Errorf("localitiesToWeightedTarget() diff (-got +want) %v", diff)
   851  			}
   852  		})
   853  	}
   854  }
   855  
   856  func TestLocalitiesToRingHash(t *testing.T) {
   857  	tests := []struct {
   858  		name         string
   859  		localities   []xdsresource.Locality
   860  		priorityName string
   861  		wantAddrs    []resolver.Address
   862  	}{
   863  		{
   864  			// Check that address weights are locality_weight * endpoint_weight.
   865  			name: "with locality and endpoint weight",
   866  			localities: []xdsresource.Locality{
   867  				{
   868  					Endpoints: []xdsresource.Endpoint{
   869  						{Address: "addr-1-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90},
   870  						{Address: "addr-1-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10},
   871  					},
   872  					ID:     internal.LocalityID{Zone: "test-zone-1"},
   873  					Weight: 20,
   874  				},
   875  				{
   876  					Endpoints: []xdsresource.Endpoint{
   877  						{Address: "addr-2-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90},
   878  						{Address: "addr-2-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10},
   879  					},
   880  					ID:     internal.LocalityID{Zone: "test-zone-2"},
   881  					Weight: 80,
   882  				},
   883  			},
   884  			priorityName: "test-priority",
   885  			wantAddrs: []resolver.Address{
   886  				testAddrWithAttrs("addr-1-1", newUint32(1800), "test-priority", &internal.LocalityID{Zone: "test-zone-1"}),
   887  				testAddrWithAttrs("addr-1-2", newUint32(200), "test-priority", &internal.LocalityID{Zone: "test-zone-1"}),
   888  				testAddrWithAttrs("addr-2-1", newUint32(7200), "test-priority", &internal.LocalityID{Zone: "test-zone-2"}),
   889  				testAddrWithAttrs("addr-2-2", newUint32(800), "test-priority", &internal.LocalityID{Zone: "test-zone-2"}),
   890  			},
   891  		},
   892  		{
   893  			// Check that endpoint_weight is 0, weight is the locality weight.
   894  			name: "locality weight only",
   895  			localities: []xdsresource.Locality{
   896  				{
   897  					Endpoints: []xdsresource.Endpoint{
   898  						{Address: "addr-1-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy},
   899  						{Address: "addr-1-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy},
   900  					},
   901  					ID:     internal.LocalityID{Zone: "test-zone-1"},
   902  					Weight: 20,
   903  				},
   904  				{
   905  					Endpoints: []xdsresource.Endpoint{
   906  						{Address: "addr-2-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy},
   907  						{Address: "addr-2-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy},
   908  					},
   909  					ID:     internal.LocalityID{Zone: "test-zone-2"},
   910  					Weight: 80,
   911  				},
   912  			},
   913  			priorityName: "test-priority",
   914  			wantAddrs: []resolver.Address{
   915  				testAddrWithAttrs("addr-1-1", newUint32(20), "test-priority", &internal.LocalityID{Zone: "test-zone-1"}),
   916  				testAddrWithAttrs("addr-1-2", newUint32(20), "test-priority", &internal.LocalityID{Zone: "test-zone-1"}),
   917  				testAddrWithAttrs("addr-2-1", newUint32(80), "test-priority", &internal.LocalityID{Zone: "test-zone-2"}),
   918  				testAddrWithAttrs("addr-2-2", newUint32(80), "test-priority", &internal.LocalityID{Zone: "test-zone-2"}),
   919  			},
   920  		},
   921  		{
   922  			// Check that locality_weight is 0, weight is the endpoint weight.
   923  			name: "endpoint weight only",
   924  			localities: []xdsresource.Locality{
   925  				{
   926  					Endpoints: []xdsresource.Endpoint{
   927  						{Address: "addr-1-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90},
   928  						{Address: "addr-1-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10},
   929  					},
   930  					ID: internal.LocalityID{Zone: "test-zone-1"},
   931  				},
   932  				{
   933  					Endpoints: []xdsresource.Endpoint{
   934  						{Address: "addr-2-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90},
   935  						{Address: "addr-2-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10},
   936  					},
   937  					ID: internal.LocalityID{Zone: "test-zone-2"},
   938  				},
   939  			},
   940  			priorityName: "test-priority",
   941  			wantAddrs: []resolver.Address{
   942  				testAddrWithAttrs("addr-1-1", newUint32(90), "test-priority", &internal.LocalityID{Zone: "test-zone-1"}),
   943  				testAddrWithAttrs("addr-1-2", newUint32(10), "test-priority", &internal.LocalityID{Zone: "test-zone-1"}),
   944  				testAddrWithAttrs("addr-2-1", newUint32(90), "test-priority", &internal.LocalityID{Zone: "test-zone-2"}),
   945  				testAddrWithAttrs("addr-2-2", newUint32(10), "test-priority", &internal.LocalityID{Zone: "test-zone-2"}),
   946  			},
   947  		},
   948  	}
   949  	for _, tt := range tests {
   950  		t.Run(tt.name, func(t *testing.T) {
   951  			got := localitiesToRingHash(tt.localities, tt.priorityName)
   952  			if diff := cmp.Diff(got, tt.wantAddrs, cmp.AllowUnexported(attributes.Attributes{})); diff != "" {
   953  				t.Errorf("localitiesToWeightedTarget() diff (-got +want) %v", diff)
   954  			}
   955  		})
   956  	}
   957  }
   958  
   959  func assertString(f func() (string, error)) string {
   960  	s, err := f()
   961  	if err != nil {
   962  		panic(err.Error())
   963  	}
   964  	return s
   965  }
   966  
   967  func testAddrWithAttrs(addrStr string, weight *uint32, priority string, lID *internal.LocalityID) resolver.Address {
   968  	addr := resolver.Address{Addr: addrStr}
   969  	if weight != nil {
   970  		addr = weightedroundrobin.SetAddrInfo(addr, weightedroundrobin.AddrInfo{Weight: *weight})
   971  	}
   972  	path := []string{priority}
   973  	if lID != nil {
   974  		path = append(path, assertString(lID.ToString))
   975  		addr = internal.SetLocalityID(addr, *lID)
   976  	}
   977  	addr = hierarchy.Set(addr, path)
   978  	return addr
   979  }