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