google.golang.org/grpc@v1.74.2/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  	"time"
    28  
    29  	"github.com/google/go-cmp/cmp"
    30  	"google.golang.org/grpc/attributes"
    31  	"google.golang.org/grpc/balancer"
    32  	"google.golang.org/grpc/balancer/ringhash"
    33  	"google.golang.org/grpc/balancer/roundrobin"
    34  	"google.golang.org/grpc/internal/balancer/weight"
    35  	"google.golang.org/grpc/internal/hierarchy"
    36  	iringhash "google.golang.org/grpc/internal/ringhash"
    37  	iserviceconfig "google.golang.org/grpc/internal/serviceconfig"
    38  	"google.golang.org/grpc/internal/xds/bootstrap"
    39  	"google.golang.org/grpc/resolver"
    40  	"google.golang.org/grpc/xds/internal"
    41  	"google.golang.org/grpc/xds/internal/balancer/clusterimpl"
    42  	"google.golang.org/grpc/xds/internal/balancer/outlierdetection"
    43  	"google.golang.org/grpc/xds/internal/balancer/priority"
    44  	"google.golang.org/grpc/xds/internal/balancer/wrrlocality"
    45  	"google.golang.org/grpc/xds/internal/clients"
    46  	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource"
    47  )
    48  
    49  const (
    50  	testLRSServer       = "test-lrs-server"
    51  	testMaxRequests     = 314
    52  	testEDSServiceName  = "service-name-from-parent"
    53  	testDropCategory    = "test-drops"
    54  	testDropOverMillion = 1
    55  
    56  	localityCount       = 5
    57  	endpointPerLocality = 2
    58  )
    59  
    60  var (
    61  	testLocalityIDs       []clients.Locality
    62  	testResolverEndpoints [][]resolver.Endpoint
    63  	testEndpoints         [][]xdsresource.Endpoint
    64  
    65  	testLocalitiesP0, testLocalitiesP1 []xdsresource.Locality
    66  
    67  	endpointCmpOpts = cmp.Options{
    68  		cmp.AllowUnexported(attributes.Attributes{}),
    69  		cmp.Transformer("SortEndpoints", func(in []resolver.Endpoint) []resolver.Endpoint {
    70  			out := append([]resolver.Endpoint(nil), in...) // Copy input to avoid mutating it
    71  			sort.Slice(out, func(i, j int) bool {
    72  				return out[i].Addresses[0].Addr < out[j].Addresses[0].Addr
    73  			})
    74  			return out
    75  		}),
    76  	}
    77  
    78  	noopODCfg = outlierdetection.LBConfig{
    79  		Interval:           iserviceconfig.Duration(10 * time.Second), // default interval
    80  		BaseEjectionTime:   iserviceconfig.Duration(30 * time.Second),
    81  		MaxEjectionTime:    iserviceconfig.Duration(300 * time.Second),
    82  		MaxEjectionPercent: 10,
    83  	}
    84  )
    85  
    86  func init() {
    87  	for i := 0; i < localityCount; i++ {
    88  		testLocalityIDs = append(testLocalityIDs, clients.Locality{Zone: fmt.Sprintf("test-zone-%d", i)})
    89  		var (
    90  			endpoints []resolver.Endpoint
    91  			ends      []xdsresource.Endpoint
    92  		)
    93  		for j := 0; j < endpointPerLocality; j++ {
    94  			addr := fmt.Sprintf("addr-%d-%d", i, j)
    95  			endpoints = append(endpoints, resolver.Endpoint{Addresses: []resolver.Address{{Addr: addr}}})
    96  			ends = append(ends, xdsresource.Endpoint{
    97  				HealthStatus: xdsresource.EndpointHealthStatusHealthy,
    98  				Addresses: []string{
    99  					addr,
   100  					fmt.Sprintf("addr-%d-%d-additional-1", i, j),
   101  					fmt.Sprintf("addr-%d-%d-additional-2", i, j),
   102  				},
   103  			})
   104  		}
   105  		testResolverEndpoints = append(testResolverEndpoints, endpoints)
   106  		testEndpoints = append(testEndpoints, ends)
   107  	}
   108  
   109  	testLocalitiesP0 = []xdsresource.Locality{
   110  		{
   111  			Endpoints: testEndpoints[0],
   112  			ID:        testLocalityIDs[0],
   113  			Weight:    20,
   114  			Priority:  0,
   115  		},
   116  		{
   117  			Endpoints: testEndpoints[1],
   118  			ID:        testLocalityIDs[1],
   119  			Weight:    80,
   120  			Priority:  0,
   121  		},
   122  	}
   123  	testLocalitiesP1 = []xdsresource.Locality{
   124  		{
   125  			Endpoints: testEndpoints[2],
   126  			ID:        testLocalityIDs[2],
   127  			Weight:    20,
   128  			Priority:  1,
   129  		},
   130  		{
   131  			Endpoints: testEndpoints[3],
   132  			ID:        testLocalityIDs[3],
   133  			Weight:    80,
   134  			Priority:  1,
   135  		},
   136  	}
   137  }
   138  
   139  // TestBuildPriorityConfigJSON is a sanity check that the built balancer config
   140  // can be parsed. The behavior test is covered by TestBuildPriorityConfig.
   141  func TestBuildPriorityConfigJSON(t *testing.T) {
   142  	testLRSServerConfig, err := bootstrap.ServerConfigForTesting(bootstrap.ServerConfigTestingOptions{
   143  		URI:          "trafficdirector.googleapis.com:443",
   144  		ChannelCreds: []bootstrap.ChannelCreds{{Type: "google_default"}},
   145  	})
   146  	if err != nil {
   147  		t.Fatalf("Failed to create LRS server config for testing: %v", err)
   148  	}
   149  
   150  	gotConfig, _, err := buildPriorityConfigJSON([]priorityConfig{
   151  		{
   152  			mechanism: DiscoveryMechanism{
   153  				Cluster:               testClusterName,
   154  				LoadReportingServer:   testLRSServerConfig,
   155  				MaxConcurrentRequests: newUint32(testMaxRequests),
   156  				Type:                  DiscoveryMechanismTypeEDS,
   157  				EDSServiceName:        testEDSServiceName,
   158  			},
   159  			edsResp: xdsresource.EndpointsUpdate{
   160  				Drops: []xdsresource.OverloadDropConfig{
   161  					{
   162  						Category:    testDropCategory,
   163  						Numerator:   testDropOverMillion,
   164  						Denominator: million,
   165  					},
   166  				},
   167  				Localities: []xdsresource.Locality{
   168  					testLocalitiesP0[0],
   169  					testLocalitiesP0[1],
   170  					testLocalitiesP1[0],
   171  					testLocalitiesP1[1],
   172  				},
   173  			},
   174  			childNameGen: newNameGenerator(0),
   175  		},
   176  		{
   177  			mechanism: DiscoveryMechanism{
   178  				Type: DiscoveryMechanismTypeLogicalDNS,
   179  			},
   180  			endpoints:    testResolverEndpoints[4],
   181  			childNameGen: newNameGenerator(1),
   182  		},
   183  	}, nil)
   184  	if err != nil {
   185  		t.Fatalf("buildPriorityConfigJSON(...) failed: %v", err)
   186  	}
   187  
   188  	var prettyGot bytes.Buffer
   189  	if err := json.Indent(&prettyGot, gotConfig, ">>> ", "  "); err != nil {
   190  		t.Fatalf("json.Indent() failed: %v", err)
   191  	}
   192  	// Print the indented json if this test fails.
   193  	t.Log(prettyGot.String())
   194  
   195  	priorityB := balancer.Get(priority.Name)
   196  	if _, err = priorityB.(balancer.ConfigParser).ParseConfig(gotConfig); err != nil {
   197  		t.Fatalf("ParseConfig(%+v) failed: %v", gotConfig, err)
   198  	}
   199  }
   200  
   201  // TestBuildPriorityConfig tests the priority config generation. Each top level
   202  // balancer per priority should be an Outlier Detection balancer, with a Cluster
   203  // Impl Balancer as a child.
   204  func TestBuildPriorityConfig(t *testing.T) {
   205  	gotConfig, _, _ := buildPriorityConfig([]priorityConfig{
   206  		{
   207  			// EDS - OD config should be the top level for both of the EDS
   208  			// priorities balancer This EDS priority will have multiple sub
   209  			// priorities. The Outlier Detection configuration specified in the
   210  			// Discovery Mechanism should be the top level for each sub
   211  			// priorities balancer.
   212  			mechanism: DiscoveryMechanism{
   213  				Cluster:          testClusterName,
   214  				Type:             DiscoveryMechanismTypeEDS,
   215  				EDSServiceName:   testEDSServiceName,
   216  				outlierDetection: noopODCfg,
   217  			},
   218  			edsResp: xdsresource.EndpointsUpdate{
   219  				Localities: []xdsresource.Locality{
   220  					testLocalitiesP0[0],
   221  					testLocalitiesP0[1],
   222  					testLocalitiesP1[0],
   223  					testLocalitiesP1[1],
   224  				},
   225  			},
   226  			childNameGen: newNameGenerator(0),
   227  		},
   228  		{
   229  			// This OD config should wrap the Logical DNS priorities balancer.
   230  			mechanism: DiscoveryMechanism{
   231  				Cluster:          testClusterName2,
   232  				Type:             DiscoveryMechanismTypeLogicalDNS,
   233  				outlierDetection: noopODCfg,
   234  			},
   235  			endpoints:    testResolverEndpoints[4],
   236  			childNameGen: newNameGenerator(1),
   237  		},
   238  	}, nil)
   239  
   240  	wantConfig := &priority.LBConfig{
   241  		Children: map[string]*priority.Child{
   242  			"priority-0-0": {
   243  				Config: &iserviceconfig.BalancerConfig{
   244  					Name: outlierdetection.Name,
   245  					Config: &outlierdetection.LBConfig{
   246  						Interval:           iserviceconfig.Duration(10 * time.Second), // default interval
   247  						BaseEjectionTime:   iserviceconfig.Duration(30 * time.Second),
   248  						MaxEjectionTime:    iserviceconfig.Duration(300 * time.Second),
   249  						MaxEjectionPercent: 10,
   250  						ChildPolicy: &iserviceconfig.BalancerConfig{
   251  							Name: clusterimpl.Name,
   252  							Config: &clusterimpl.LBConfig{
   253  								Cluster:        testClusterName,
   254  								EDSServiceName: testEDSServiceName,
   255  								DropCategories: []clusterimpl.DropConfig{},
   256  							},
   257  						},
   258  					},
   259  				},
   260  				IgnoreReresolutionRequests: true,
   261  			},
   262  			"priority-0-1": {
   263  				Config: &iserviceconfig.BalancerConfig{
   264  					Name: outlierdetection.Name,
   265  					Config: &outlierdetection.LBConfig{
   266  						Interval:           iserviceconfig.Duration(10 * time.Second), // default interval
   267  						BaseEjectionTime:   iserviceconfig.Duration(30 * time.Second),
   268  						MaxEjectionTime:    iserviceconfig.Duration(300 * time.Second),
   269  						MaxEjectionPercent: 10,
   270  						ChildPolicy: &iserviceconfig.BalancerConfig{
   271  							Name: clusterimpl.Name,
   272  							Config: &clusterimpl.LBConfig{
   273  								Cluster:        testClusterName,
   274  								EDSServiceName: testEDSServiceName,
   275  								DropCategories: []clusterimpl.DropConfig{},
   276  							},
   277  						},
   278  					},
   279  				},
   280  				IgnoreReresolutionRequests: true,
   281  			},
   282  			"priority-1": {
   283  				Config: &iserviceconfig.BalancerConfig{
   284  					Name: outlierdetection.Name,
   285  					Config: &outlierdetection.LBConfig{
   286  						Interval:           iserviceconfig.Duration(10 * time.Second), // default interval
   287  						BaseEjectionTime:   iserviceconfig.Duration(30 * time.Second),
   288  						MaxEjectionTime:    iserviceconfig.Duration(300 * time.Second),
   289  						MaxEjectionPercent: 10,
   290  						ChildPolicy: &iserviceconfig.BalancerConfig{
   291  							Name: clusterimpl.Name,
   292  							Config: &clusterimpl.LBConfig{
   293  								Cluster:     testClusterName2,
   294  								ChildPolicy: &iserviceconfig.BalancerConfig{Name: "pick_first"},
   295  							},
   296  						},
   297  					},
   298  				},
   299  				IgnoreReresolutionRequests: false,
   300  			},
   301  		},
   302  		Priorities: []string{"priority-0-0", "priority-0-1", "priority-1"},
   303  	}
   304  	if diff := cmp.Diff(gotConfig, wantConfig); diff != "" {
   305  		t.Errorf("buildPriorityConfig() diff (-got +want) %v", diff)
   306  	}
   307  }
   308  
   309  func TestBuildClusterImplConfigForDNS(t *testing.T) {
   310  	gotName, gotConfig, gotEndpoints := buildClusterImplConfigForDNS(newNameGenerator(3), testResolverEndpoints[0], DiscoveryMechanism{Cluster: testClusterName2, Type: DiscoveryMechanismTypeLogicalDNS})
   311  	wantName := "priority-3"
   312  	wantConfig := &clusterimpl.LBConfig{
   313  		Cluster: testClusterName2,
   314  		ChildPolicy: &iserviceconfig.BalancerConfig{
   315  			Name: "pick_first",
   316  		},
   317  	}
   318  	e1 := resolver.Endpoint{Addresses: []resolver.Address{{Addr: testEndpoints[0][0].Addresses[0]}}}
   319  	e2 := resolver.Endpoint{Addresses: []resolver.Address{{Addr: testEndpoints[0][1].Addresses[0]}}}
   320  	wantEndpoints := []resolver.Endpoint{
   321  		hierarchy.SetInEndpoint(e1, []string{"priority-3"}),
   322  		hierarchy.SetInEndpoint(e2, []string{"priority-3"}),
   323  	}
   324  
   325  	if diff := cmp.Diff(gotName, wantName); diff != "" {
   326  		t.Errorf("buildClusterImplConfigForDNS() diff (-got +want) %v", diff)
   327  	}
   328  	if diff := cmp.Diff(gotConfig, wantConfig); diff != "" {
   329  		t.Errorf("buildClusterImplConfigForDNS() diff (-got +want) %v", diff)
   330  	}
   331  	if diff := cmp.Diff(gotEndpoints, wantEndpoints, endpointCmpOpts); diff != "" {
   332  		t.Errorf("buildClusterImplConfigForDNS() diff (-got +want) %v", diff)
   333  	}
   334  }
   335  
   336  func TestBuildClusterImplConfigForEDS(t *testing.T) {
   337  	testLRSServerConfig, err := bootstrap.ServerConfigForTesting(bootstrap.ServerConfigTestingOptions{
   338  		URI:          "trafficdirector.googleapis.com:443",
   339  		ChannelCreds: []bootstrap.ChannelCreds{{Type: "google_default"}},
   340  	})
   341  	if err != nil {
   342  		t.Fatalf("Failed to create LRS server config for testing: %v", err)
   343  	}
   344  
   345  	gotNames, gotConfigs, gotEndpoints, _ := buildClusterImplConfigForEDS(
   346  		newNameGenerator(2),
   347  		xdsresource.EndpointsUpdate{
   348  			Drops: []xdsresource.OverloadDropConfig{
   349  				{
   350  					Category:    testDropCategory,
   351  					Numerator:   testDropOverMillion,
   352  					Denominator: million,
   353  				},
   354  			},
   355  			Localities: []xdsresource.Locality{
   356  				{
   357  					Endpoints: testEndpoints[3],
   358  					ID:        testLocalityIDs[3],
   359  					Weight:    80,
   360  					Priority:  1,
   361  				}, {
   362  					Endpoints: testEndpoints[1],
   363  					ID:        testLocalityIDs[1],
   364  					Weight:    80,
   365  					Priority:  0,
   366  				}, {
   367  					Endpoints: testEndpoints[2],
   368  					ID:        testLocalityIDs[2],
   369  					Weight:    20,
   370  					Priority:  1,
   371  				}, {
   372  					Endpoints: testEndpoints[0],
   373  					ID:        testLocalityIDs[0],
   374  					Weight:    20,
   375  					Priority:  0,
   376  				},
   377  			},
   378  		},
   379  		DiscoveryMechanism{
   380  			Cluster:               testClusterName,
   381  			MaxConcurrentRequests: newUint32(testMaxRequests),
   382  			LoadReportingServer:   testLRSServerConfig,
   383  			Type:                  DiscoveryMechanismTypeEDS,
   384  			EDSServiceName:        testEDSServiceName,
   385  		},
   386  		nil,
   387  	)
   388  
   389  	wantNames := []string{
   390  		fmt.Sprintf("priority-%v-%v", 2, 0),
   391  		fmt.Sprintf("priority-%v-%v", 2, 1),
   392  	}
   393  	wantConfigs := map[string]*clusterimpl.LBConfig{
   394  		"priority-2-0": {
   395  			Cluster:               testClusterName,
   396  			EDSServiceName:        testEDSServiceName,
   397  			LoadReportingServer:   testLRSServerConfig,
   398  			MaxConcurrentRequests: newUint32(testMaxRequests),
   399  			DropCategories: []clusterimpl.DropConfig{
   400  				{
   401  					Category:           testDropCategory,
   402  					RequestsPerMillion: testDropOverMillion,
   403  				},
   404  			},
   405  		},
   406  		"priority-2-1": {
   407  			Cluster:               testClusterName,
   408  			EDSServiceName:        testEDSServiceName,
   409  			LoadReportingServer:   testLRSServerConfig,
   410  			MaxConcurrentRequests: newUint32(testMaxRequests),
   411  			DropCategories: []clusterimpl.DropConfig{
   412  				{
   413  					Category:           testDropCategory,
   414  					RequestsPerMillion: testDropOverMillion,
   415  				},
   416  			},
   417  		},
   418  	}
   419  	wantEndpoints := []resolver.Endpoint{
   420  		testEndpointWithAttrs(testEndpoints[0][0].Addresses, 20, 1, "priority-2-0", &testLocalityIDs[0]),
   421  		testEndpointWithAttrs(testEndpoints[0][1].Addresses, 20, 1, "priority-2-0", &testLocalityIDs[0]),
   422  		testEndpointWithAttrs(testEndpoints[1][0].Addresses, 80, 1, "priority-2-0", &testLocalityIDs[1]),
   423  		testEndpointWithAttrs(testEndpoints[1][1].Addresses, 80, 1, "priority-2-0", &testLocalityIDs[1]),
   424  		testEndpointWithAttrs(testEndpoints[2][0].Addresses, 20, 1, "priority-2-1", &testLocalityIDs[2]),
   425  		testEndpointWithAttrs(testEndpoints[2][1].Addresses, 20, 1, "priority-2-1", &testLocalityIDs[2]),
   426  		testEndpointWithAttrs(testEndpoints[3][0].Addresses, 80, 1, "priority-2-1", &testLocalityIDs[3]),
   427  		testEndpointWithAttrs(testEndpoints[3][1].Addresses, 80, 1, "priority-2-1", &testLocalityIDs[3]),
   428  	}
   429  
   430  	if diff := cmp.Diff(gotNames, wantNames); diff != "" {
   431  		t.Errorf("buildClusterImplConfigForEDS() diff (-got +want) %v", diff)
   432  	}
   433  	if diff := cmp.Diff(gotConfigs, wantConfigs); diff != "" {
   434  		t.Errorf("buildClusterImplConfigForEDS() diff (-got +want) %v", diff)
   435  	}
   436  	if diff := cmp.Diff(gotEndpoints, wantEndpoints, endpointCmpOpts); diff != "" {
   437  		t.Errorf("buildClusterImplConfigForEDS() diff (-got +want) %v", diff)
   438  	}
   439  
   440  }
   441  
   442  func TestGroupLocalitiesByPriority(t *testing.T) {
   443  	tests := []struct {
   444  		name           string
   445  		localities     []xdsresource.Locality
   446  		wantLocalities [][]xdsresource.Locality
   447  	}{
   448  		{
   449  			name:       "1 locality 1 priority",
   450  			localities: []xdsresource.Locality{testLocalitiesP0[0]},
   451  			wantLocalities: [][]xdsresource.Locality{
   452  				{testLocalitiesP0[0]},
   453  			},
   454  		},
   455  		{
   456  			name:       "2 locality 1 priority",
   457  			localities: []xdsresource.Locality{testLocalitiesP0[0], testLocalitiesP0[1]},
   458  			wantLocalities: [][]xdsresource.Locality{
   459  				{testLocalitiesP0[0], testLocalitiesP0[1]},
   460  			},
   461  		},
   462  		{
   463  			name:       "1 locality in each",
   464  			localities: []xdsresource.Locality{testLocalitiesP0[0], testLocalitiesP1[0]},
   465  			wantLocalities: [][]xdsresource.Locality{
   466  				{testLocalitiesP0[0]},
   467  				{testLocalitiesP1[0]},
   468  			},
   469  		},
   470  		{
   471  			name: "2 localities in each sorted",
   472  			localities: []xdsresource.Locality{
   473  				testLocalitiesP0[0], testLocalitiesP0[1],
   474  				testLocalitiesP1[0], testLocalitiesP1[1]},
   475  			wantLocalities: [][]xdsresource.Locality{
   476  				{testLocalitiesP0[0], testLocalitiesP0[1]},
   477  				{testLocalitiesP1[0], testLocalitiesP1[1]},
   478  			},
   479  		},
   480  		{
   481  			// The localities are given in order [p1, p0, p1, p0], but the
   482  			// returned priority list must be sorted [p0, p1], because the list
   483  			// order is the priority order.
   484  			name: "2 localities in each needs to sort",
   485  			localities: []xdsresource.Locality{
   486  				testLocalitiesP1[1], testLocalitiesP0[1],
   487  				testLocalitiesP1[0], testLocalitiesP0[0]},
   488  			wantLocalities: [][]xdsresource.Locality{
   489  				{testLocalitiesP0[1], testLocalitiesP0[0]},
   490  				{testLocalitiesP1[1], testLocalitiesP1[0]},
   491  			},
   492  		},
   493  	}
   494  	for _, tt := range tests {
   495  		t.Run(tt.name, func(t *testing.T) {
   496  			gotLocalities := groupLocalitiesByPriority(tt.localities)
   497  			if diff := cmp.Diff(gotLocalities, tt.wantLocalities); diff != "" {
   498  				t.Errorf("groupLocalitiesByPriority() diff(-got +want) %v", diff)
   499  			}
   500  		})
   501  	}
   502  }
   503  
   504  func TestDedupSortedIntSlice(t *testing.T) {
   505  	tests := []struct {
   506  		name string
   507  		a    []int
   508  		want []int
   509  	}{
   510  		{
   511  			name: "empty",
   512  			a:    []int{},
   513  			want: []int{},
   514  		},
   515  		{
   516  			name: "no dup",
   517  			a:    []int{0, 1, 2, 3},
   518  			want: []int{0, 1, 2, 3},
   519  		},
   520  		{
   521  			name: "with dup",
   522  			a:    []int{0, 0, 1, 1, 1, 2, 3},
   523  			want: []int{0, 1, 2, 3},
   524  		},
   525  	}
   526  	for _, tt := range tests {
   527  		t.Run(tt.name, func(t *testing.T) {
   528  			if got := dedupSortedIntSlice(tt.a); !cmp.Equal(got, tt.want) {
   529  				t.Errorf("dedupSortedIntSlice() = %v, want %v, diff %v", got, tt.want, cmp.Diff(got, tt.want))
   530  			}
   531  		})
   532  	}
   533  }
   534  
   535  func TestPriorityLocalitiesToClusterImpl(t *testing.T) {
   536  	tests := []struct {
   537  		name          string
   538  		localities    []xdsresource.Locality
   539  		priorityName  string
   540  		mechanism     DiscoveryMechanism
   541  		childPolicy   *iserviceconfig.BalancerConfig
   542  		wantConfig    *clusterimpl.LBConfig
   543  		wantEndpoints []resolver.Endpoint
   544  		wantErr       bool
   545  	}{{
   546  		name: "round robin as child, no LRS",
   547  		localities: []xdsresource.Locality{
   548  			{
   549  				Endpoints: []xdsresource.Endpoint{
   550  					{Addresses: []string{"addr-1-1"}, HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90},
   551  					{Addresses: []string{"addr-1-2"}, HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10},
   552  				},
   553  				ID:     clients.Locality{Zone: "test-zone-1"},
   554  				Weight: 20,
   555  			},
   556  			{
   557  				Endpoints: []xdsresource.Endpoint{
   558  					{Addresses: []string{"addr-2-1"}, HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90},
   559  					{Addresses: []string{"addr-2-2"}, HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10},
   560  				},
   561  				ID:     clients.Locality{Zone: "test-zone-2"},
   562  				Weight: 80,
   563  			},
   564  		},
   565  		priorityName: "test-priority",
   566  		childPolicy:  &iserviceconfig.BalancerConfig{Name: roundrobin.Name},
   567  		mechanism: DiscoveryMechanism{
   568  			Cluster:        testClusterName,
   569  			Type:           DiscoveryMechanismTypeEDS,
   570  			EDSServiceName: testEDSService,
   571  		},
   572  		// lrsServer is nil, so LRS policy will not be used.
   573  		wantConfig: &clusterimpl.LBConfig{
   574  			Cluster:        testClusterName,
   575  			EDSServiceName: testEDSService,
   576  			ChildPolicy:    &iserviceconfig.BalancerConfig{Name: roundrobin.Name},
   577  		},
   578  		wantEndpoints: []resolver.Endpoint{
   579  			testEndpointWithAttrs([]string{"addr-1-1"}, 20, 90, "test-priority", &clients.Locality{Zone: "test-zone-1"}),
   580  			testEndpointWithAttrs([]string{"addr-1-2"}, 20, 10, "test-priority", &clients.Locality{Zone: "test-zone-1"}),
   581  			testEndpointWithAttrs([]string{"addr-2-1"}, 80, 90, "test-priority", &clients.Locality{Zone: "test-zone-2"}),
   582  			testEndpointWithAttrs([]string{"addr-2-2"}, 80, 10, "test-priority", &clients.Locality{Zone: "test-zone-2"}),
   583  		},
   584  	},
   585  		{
   586  			name: "ring_hash as child",
   587  			localities: []xdsresource.Locality{
   588  				{
   589  					Endpoints: []xdsresource.Endpoint{
   590  						{Addresses: []string{"addr-1-1"}, HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90},
   591  						{Addresses: []string{"addr-1-2"}, HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10},
   592  					},
   593  					ID:     clients.Locality{Zone: "test-zone-1"},
   594  					Weight: 20,
   595  				},
   596  				{
   597  					Endpoints: []xdsresource.Endpoint{
   598  						{Addresses: []string{"addr-2-1"}, HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90},
   599  						{Addresses: []string{"addr-2-2"}, HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10},
   600  					},
   601  					ID:     clients.Locality{Zone: "test-zone-2"},
   602  					Weight: 80,
   603  				},
   604  			},
   605  			priorityName: "test-priority",
   606  			childPolicy:  &iserviceconfig.BalancerConfig{Name: ringhash.Name, Config: &iringhash.LBConfig{MinRingSize: 1, MaxRingSize: 2}},
   607  			// lrsServer is nil, so LRS policy will not be used.
   608  			wantConfig: &clusterimpl.LBConfig{
   609  				ChildPolicy: &iserviceconfig.BalancerConfig{
   610  					Name:   ringhash.Name,
   611  					Config: &iringhash.LBConfig{MinRingSize: 1, MaxRingSize: 2},
   612  				},
   613  			},
   614  			wantEndpoints: []resolver.Endpoint{
   615  				testEndpointWithAttrs([]string{"addr-1-1"}, 20, 90, "test-priority", &clients.Locality{Zone: "test-zone-1"}),
   616  				testEndpointWithAttrs([]string{"addr-1-2"}, 20, 10, "test-priority", &clients.Locality{Zone: "test-zone-1"}),
   617  				testEndpointWithAttrs([]string{"addr-2-1"}, 80, 90, "test-priority", &clients.Locality{Zone: "test-zone-2"}),
   618  				testEndpointWithAttrs([]string{"addr-2-2"}, 80, 10, "test-priority", &clients.Locality{Zone: "test-zone-2"}),
   619  			},
   620  		},
   621  	}
   622  	for _, tt := range tests {
   623  		t.Run(tt.name, func(t *testing.T) {
   624  			got, got1, err := priorityLocalitiesToClusterImpl(tt.localities, tt.priorityName, tt.mechanism, nil, tt.childPolicy)
   625  			if (err != nil) != tt.wantErr {
   626  				t.Fatalf("priorityLocalitiesToClusterImpl() error = %v, wantErr %v", err, tt.wantErr)
   627  			}
   628  			if diff := cmp.Diff(got, tt.wantConfig); diff != "" {
   629  				t.Errorf("localitiesToWeightedTarget() diff (-got +want) %v", diff)
   630  			}
   631  			if diff := cmp.Diff(got1, tt.wantEndpoints, cmp.AllowUnexported(attributes.Attributes{})); diff != "" {
   632  				t.Errorf("localitiesToWeightedTarget() diff (-got +want) %v", diff)
   633  			}
   634  		})
   635  	}
   636  }
   637  
   638  func testEndpointWithAttrs(addrStrs []string, localityWeight, endpointWeight uint32, priority string, lID *clients.Locality) resolver.Endpoint {
   639  	endpoint := resolver.Endpoint{}
   640  	for _, a := range addrStrs {
   641  		endpoint.Addresses = append(endpoint.Addresses, resolver.Address{Addr: a})
   642  	}
   643  	path := []string{priority}
   644  	if lID != nil {
   645  		path = append(path, internal.LocalityString(*lID))
   646  		endpoint = internal.SetLocalityIDInEndpoint(endpoint, *lID)
   647  	}
   648  	endpoint = hierarchy.SetInEndpoint(endpoint, path)
   649  	endpoint = wrrlocality.SetAddrInfoInEndpoint(endpoint, wrrlocality.AddrInfo{LocalityWeight: localityWeight})
   650  	endpoint = weight.Set(endpoint, weight.EndpointInfo{Weight: localityWeight * endpointWeight})
   651  	return endpoint
   652  }
   653  
   654  func TestConvertClusterImplMapToOutlierDetection(t *testing.T) {
   655  	tests := []struct {
   656  		name       string
   657  		ciCfgsMap  map[string]*clusterimpl.LBConfig
   658  		odCfg      outlierdetection.LBConfig
   659  		wantODCfgs map[string]*outlierdetection.LBConfig
   660  	}{
   661  		{
   662  			name: "single-entry-noop",
   663  			ciCfgsMap: map[string]*clusterimpl.LBConfig{
   664  				"child1": {
   665  					Cluster: "cluster1",
   666  				},
   667  			},
   668  			odCfg: outlierdetection.LBConfig{
   669  				Interval: 1<<63 - 1,
   670  			},
   671  			wantODCfgs: map[string]*outlierdetection.LBConfig{
   672  				"child1": {
   673  					Interval: 1<<63 - 1,
   674  					ChildPolicy: &iserviceconfig.BalancerConfig{
   675  						Name: clusterimpl.Name,
   676  						Config: &clusterimpl.LBConfig{
   677  							Cluster: "cluster1",
   678  						},
   679  					},
   680  				},
   681  			},
   682  		},
   683  		{
   684  			name: "multiple-entries-noop",
   685  			ciCfgsMap: map[string]*clusterimpl.LBConfig{
   686  				"child1": {
   687  					Cluster: "cluster1",
   688  				},
   689  				"child2": {
   690  					Cluster: "cluster2",
   691  				},
   692  			},
   693  			odCfg: outlierdetection.LBConfig{
   694  				Interval: 1<<63 - 1,
   695  			},
   696  			wantODCfgs: map[string]*outlierdetection.LBConfig{
   697  				"child1": {
   698  					Interval: 1<<63 - 1,
   699  					ChildPolicy: &iserviceconfig.BalancerConfig{
   700  						Name: clusterimpl.Name,
   701  						Config: &clusterimpl.LBConfig{
   702  							Cluster: "cluster1",
   703  						},
   704  					},
   705  				},
   706  				"child2": {
   707  					Interval: 1<<63 - 1,
   708  					ChildPolicy: &iserviceconfig.BalancerConfig{
   709  						Name: clusterimpl.Name,
   710  						Config: &clusterimpl.LBConfig{
   711  							Cluster: "cluster2",
   712  						},
   713  					},
   714  				},
   715  			},
   716  		},
   717  	}
   718  	for _, test := range tests {
   719  		t.Run(test.name, func(t *testing.T) {
   720  			got := convertClusterImplMapToOutlierDetection(test.ciCfgsMap, test.odCfg)
   721  			if diff := cmp.Diff(got, test.wantODCfgs); diff != "" {
   722  				t.Fatalf("convertClusterImplMapToOutlierDetection() diff(-got +want) %v", diff)
   723  			}
   724  		})
   725  	}
   726  }