...

Source file src/google.golang.org/grpc/xds/internal/balancer/clusterresolver/configbuilder_test.go

Documentation: google.golang.org/grpc/xds/internal/balancer/clusterresolver

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

View as plain text