/* * * Copyright 2021 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package clusterresolver import ( "bytes" "encoding/json" "fmt" "sort" "testing" "github.com/google/go-cmp/cmp" "google.golang.org/grpc/attributes" "google.golang.org/grpc/balancer" "google.golang.org/grpc/balancer/roundrobin" "google.golang.org/grpc/balancer/weightedroundrobin" "google.golang.org/grpc/balancer/weightedtarget" "google.golang.org/grpc/internal/hierarchy" internalserviceconfig "google.golang.org/grpc/internal/serviceconfig" "google.golang.org/grpc/resolver" "google.golang.org/grpc/xds/internal" "google.golang.org/grpc/xds/internal/balancer/clusterimpl" "google.golang.org/grpc/xds/internal/balancer/outlierdetection" "google.golang.org/grpc/xds/internal/balancer/priority" "google.golang.org/grpc/xds/internal/balancer/ringhash" "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" ) const ( testLRSServer = "test-lrs-server" testMaxRequests = 314 testEDSServiceName = "service-name-from-parent" testDropCategory = "test-drops" testDropOverMillion = 1 localityCount = 5 addressPerLocality = 2 ) var ( testLocalityIDs []internal.LocalityID testAddressStrs [][]string testEndpoints [][]xdsresource.Endpoint testLocalitiesP0, testLocalitiesP1 []xdsresource.Locality addrCmpOpts = cmp.Options{ cmp.AllowUnexported(attributes.Attributes{}), cmp.Transformer("SortAddrs", func(in []resolver.Address) []resolver.Address { out := append([]resolver.Address(nil), in...) // Copy input to avoid mutating it sort.Slice(out, func(i, j int) bool { return out[i].Addr < out[j].Addr }) return out })} noopODCfg = outlierdetection.LBConfig{ Interval: 1<<63 - 1, } ) func init() { for i := 0; i < localityCount; i++ { testLocalityIDs = append(testLocalityIDs, internal.LocalityID{Zone: fmt.Sprintf("test-zone-%d", i)}) var ( addrs []string ends []xdsresource.Endpoint ) for j := 0; j < addressPerLocality; j++ { addr := fmt.Sprintf("addr-%d-%d", i, j) addrs = append(addrs, addr) ends = append(ends, xdsresource.Endpoint{ Address: addr, HealthStatus: xdsresource.EndpointHealthStatusHealthy, }) } testAddressStrs = append(testAddressStrs, addrs) testEndpoints = append(testEndpoints, ends) } testLocalitiesP0 = []xdsresource.Locality{ { Endpoints: testEndpoints[0], ID: testLocalityIDs[0], Weight: 20, Priority: 0, }, { Endpoints: testEndpoints[1], ID: testLocalityIDs[1], Weight: 80, Priority: 0, }, } testLocalitiesP1 = []xdsresource.Locality{ { Endpoints: testEndpoints[2], ID: testLocalityIDs[2], Weight: 20, Priority: 1, }, { Endpoints: testEndpoints[3], ID: testLocalityIDs[3], Weight: 80, Priority: 1, }, } } // TestBuildPriorityConfigJSON is a sanity check that the built balancer config // can be parsed. The behavior test is covered by TestBuildPriorityConfig. func TestBuildPriorityConfigJSON(t *testing.T) { gotConfig, _, err := buildPriorityConfigJSON([]priorityConfig{ { mechanism: DiscoveryMechanism{ Cluster: testClusterName, LoadReportingServer: testLRSServerConfig, MaxConcurrentRequests: newUint32(testMaxRequests), Type: DiscoveryMechanismTypeEDS, EDSServiceName: testEDSServiceName, }, edsResp: xdsresource.EndpointsUpdate{ Drops: []xdsresource.OverloadDropConfig{ { Category: testDropCategory, Numerator: testDropOverMillion, Denominator: million, }, }, Localities: []xdsresource.Locality{ testLocalitiesP0[0], testLocalitiesP0[1], testLocalitiesP1[0], testLocalitiesP1[1], }, }, childNameGen: newNameGenerator(0), }, { mechanism: DiscoveryMechanism{ Type: DiscoveryMechanismTypeLogicalDNS, }, addresses: testAddressStrs[4], childNameGen: newNameGenerator(1), }, }, nil) if err != nil { t.Fatalf("buildPriorityConfigJSON(...) failed: %v", err) } var prettyGot bytes.Buffer if err := json.Indent(&prettyGot, gotConfig, ">>> ", " "); err != nil { t.Fatalf("json.Indent() failed: %v", err) } // Print the indented json if this test fails. t.Log(prettyGot.String()) priorityB := balancer.Get(priority.Name) if _, err = priorityB.(balancer.ConfigParser).ParseConfig(gotConfig); err != nil { t.Fatalf("ParseConfig(%+v) failed: %v", gotConfig, err) } } // TestBuildPriorityConfig tests the priority config generation. Each top level // balancer per priority should be an Outlier Detection balancer, with a Cluster // Impl Balancer as a child. func TestBuildPriorityConfig(t *testing.T) { gotConfig, _, _ := buildPriorityConfig([]priorityConfig{ { // EDS - OD config should be the top level for both of the EDS // priorities balancer This EDS priority will have multiple sub // priorities. The Outlier Detection configuration specified in the // Discovery Mechanism should be the top level for each sub // priorities balancer. mechanism: DiscoveryMechanism{ Cluster: testClusterName, Type: DiscoveryMechanismTypeEDS, EDSServiceName: testEDSServiceName, OutlierDetection: noopODCfg, }, edsResp: xdsresource.EndpointsUpdate{ Localities: []xdsresource.Locality{ testLocalitiesP0[0], testLocalitiesP0[1], testLocalitiesP1[0], testLocalitiesP1[1], }, }, childNameGen: newNameGenerator(0), }, { // This OD config should wrap the Logical DNS priorities balancer. mechanism: DiscoveryMechanism{ Cluster: testClusterName2, Type: DiscoveryMechanismTypeLogicalDNS, OutlierDetection: noopODCfg, }, addresses: testAddressStrs[4], childNameGen: newNameGenerator(1), }, }, nil) wantConfig := &priority.LBConfig{ Children: map[string]*priority.Child{ "priority-0-0": { Config: &internalserviceconfig.BalancerConfig{ Name: outlierdetection.Name, Config: &outlierdetection.LBConfig{ Interval: 1<<63 - 1, ChildPolicy: &internalserviceconfig.BalancerConfig{ Name: clusterimpl.Name, Config: &clusterimpl.LBConfig{ Cluster: testClusterName, EDSServiceName: testEDSServiceName, DropCategories: []clusterimpl.DropConfig{}, ChildPolicy: &internalserviceconfig.BalancerConfig{ Name: weightedtarget.Name, Config: &weightedtarget.LBConfig{ Targets: map[string]weightedtarget.Target{ assertString(testLocalityIDs[0].ToString): { Weight: 20, ChildPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name}, }, assertString(testLocalityIDs[1].ToString): { Weight: 80, ChildPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name}, }, }, }, }, }, }, }, }, IgnoreReresolutionRequests: true, }, "priority-0-1": { Config: &internalserviceconfig.BalancerConfig{ Name: outlierdetection.Name, Config: &outlierdetection.LBConfig{ Interval: 1<<63 - 1, ChildPolicy: &internalserviceconfig.BalancerConfig{ Name: clusterimpl.Name, Config: &clusterimpl.LBConfig{ Cluster: testClusterName, EDSServiceName: testEDSServiceName, DropCategories: []clusterimpl.DropConfig{}, ChildPolicy: &internalserviceconfig.BalancerConfig{ Name: weightedtarget.Name, Config: &weightedtarget.LBConfig{ Targets: map[string]weightedtarget.Target{ assertString(testLocalityIDs[2].ToString): { Weight: 20, ChildPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name}, }, assertString(testLocalityIDs[3].ToString): { Weight: 80, ChildPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name}, }, }, }, }, }, }, }, }, IgnoreReresolutionRequests: true, }, "priority-1": { Config: &internalserviceconfig.BalancerConfig{ Name: outlierdetection.Name, Config: &outlierdetection.LBConfig{ Interval: 1<<63 - 1, ChildPolicy: &internalserviceconfig.BalancerConfig{ Name: clusterimpl.Name, Config: &clusterimpl.LBConfig{ Cluster: testClusterName2, ChildPolicy: &internalserviceconfig.BalancerConfig{Name: "pick_first"}, }, }, }, }, IgnoreReresolutionRequests: false, }, }, Priorities: []string{"priority-0-0", "priority-0-1", "priority-1"}, } if diff := cmp.Diff(gotConfig, wantConfig); diff != "" { t.Errorf("buildPriorityConfig() diff (-got +want) %v", diff) } } func TestBuildClusterImplConfigForDNS(t *testing.T) { gotName, gotConfig, gotAddrs := buildClusterImplConfigForDNS(newNameGenerator(3), testAddressStrs[0], DiscoveryMechanism{Cluster: testClusterName2, Type: DiscoveryMechanismTypeLogicalDNS}) wantName := "priority-3" wantConfig := &clusterimpl.LBConfig{ Cluster: testClusterName2, ChildPolicy: &internalserviceconfig.BalancerConfig{ Name: "pick_first", }, } wantAddrs := []resolver.Address{ hierarchy.Set(resolver.Address{Addr: testAddressStrs[0][0]}, []string{"priority-3"}), hierarchy.Set(resolver.Address{Addr: testAddressStrs[0][1]}, []string{"priority-3"}), } if diff := cmp.Diff(gotName, wantName); diff != "" { t.Errorf("buildClusterImplConfigForDNS() diff (-got +want) %v", diff) } if diff := cmp.Diff(gotConfig, wantConfig); diff != "" { t.Errorf("buildClusterImplConfigForDNS() diff (-got +want) %v", diff) } if diff := cmp.Diff(gotAddrs, wantAddrs, addrCmpOpts); diff != "" { t.Errorf("buildClusterImplConfigForDNS() diff (-got +want) %v", diff) } } func TestBuildClusterImplConfigForEDS(t *testing.T) { gotNames, gotConfigs, gotAddrs, _ := buildClusterImplConfigForEDS( newNameGenerator(2), xdsresource.EndpointsUpdate{ Drops: []xdsresource.OverloadDropConfig{ { Category: testDropCategory, Numerator: testDropOverMillion, Denominator: million, }, }, Localities: []xdsresource.Locality{ { Endpoints: testEndpoints[3], ID: testLocalityIDs[3], Weight: 80, Priority: 1, }, { Endpoints: testEndpoints[1], ID: testLocalityIDs[1], Weight: 80, Priority: 0, }, { Endpoints: testEndpoints[2], ID: testLocalityIDs[2], Weight: 20, Priority: 1, }, { Endpoints: testEndpoints[0], ID: testLocalityIDs[0], Weight: 20, Priority: 0, }, }, }, DiscoveryMechanism{ Cluster: testClusterName, MaxConcurrentRequests: newUint32(testMaxRequests), LoadReportingServer: testLRSServerConfig, Type: DiscoveryMechanismTypeEDS, EDSServiceName: testEDSServiceName, }, nil, ) wantNames := []string{ fmt.Sprintf("priority-%v-%v", 2, 0), fmt.Sprintf("priority-%v-%v", 2, 1), } wantConfigs := map[string]*clusterimpl.LBConfig{ "priority-2-0": { Cluster: testClusterName, EDSServiceName: testEDSServiceName, LoadReportingServer: testLRSServerConfig, MaxConcurrentRequests: newUint32(testMaxRequests), DropCategories: []clusterimpl.DropConfig{ { Category: testDropCategory, RequestsPerMillion: testDropOverMillion, }, }, ChildPolicy: &internalserviceconfig.BalancerConfig{ Name: weightedtarget.Name, Config: &weightedtarget.LBConfig{ Targets: map[string]weightedtarget.Target{ assertString(testLocalityIDs[0].ToString): { Weight: 20, ChildPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name}, }, assertString(testLocalityIDs[1].ToString): { Weight: 80, ChildPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name}, }, }, }, }, }, "priority-2-1": { Cluster: testClusterName, EDSServiceName: testEDSServiceName, LoadReportingServer: testLRSServerConfig, MaxConcurrentRequests: newUint32(testMaxRequests), DropCategories: []clusterimpl.DropConfig{ { Category: testDropCategory, RequestsPerMillion: testDropOverMillion, }, }, ChildPolicy: &internalserviceconfig.BalancerConfig{ Name: weightedtarget.Name, Config: &weightedtarget.LBConfig{ Targets: map[string]weightedtarget.Target{ assertString(testLocalityIDs[2].ToString): { Weight: 20, ChildPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name}, }, assertString(testLocalityIDs[3].ToString): { Weight: 80, ChildPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name}, }, }, }, }, }, } wantAddrs := []resolver.Address{ testAddrWithAttrs(testAddressStrs[0][0], nil, "priority-2-0", &testLocalityIDs[0]), testAddrWithAttrs(testAddressStrs[0][1], nil, "priority-2-0", &testLocalityIDs[0]), testAddrWithAttrs(testAddressStrs[1][0], nil, "priority-2-0", &testLocalityIDs[1]), testAddrWithAttrs(testAddressStrs[1][1], nil, "priority-2-0", &testLocalityIDs[1]), testAddrWithAttrs(testAddressStrs[2][0], nil, "priority-2-1", &testLocalityIDs[2]), testAddrWithAttrs(testAddressStrs[2][1], nil, "priority-2-1", &testLocalityIDs[2]), testAddrWithAttrs(testAddressStrs[3][0], nil, "priority-2-1", &testLocalityIDs[3]), testAddrWithAttrs(testAddressStrs[3][1], nil, "priority-2-1", &testLocalityIDs[3]), } if diff := cmp.Diff(gotNames, wantNames); diff != "" { t.Errorf("buildClusterImplConfigForEDS() diff (-got +want) %v", diff) } if diff := cmp.Diff(gotConfigs, wantConfigs); diff != "" { t.Errorf("buildClusterImplConfigForEDS() diff (-got +want) %v", diff) } if diff := cmp.Diff(gotAddrs, wantAddrs, addrCmpOpts); diff != "" { t.Errorf("buildClusterImplConfigForEDS() diff (-got +want) %v", diff) } } func TestGroupLocalitiesByPriority(t *testing.T) { tests := []struct { name string localities []xdsresource.Locality wantLocalities [][]xdsresource.Locality }{ { name: "1 locality 1 priority", localities: []xdsresource.Locality{testLocalitiesP0[0]}, wantLocalities: [][]xdsresource.Locality{ {testLocalitiesP0[0]}, }, }, { name: "2 locality 1 priority", localities: []xdsresource.Locality{testLocalitiesP0[0], testLocalitiesP0[1]}, wantLocalities: [][]xdsresource.Locality{ {testLocalitiesP0[0], testLocalitiesP0[1]}, }, }, { name: "1 locality in each", localities: []xdsresource.Locality{testLocalitiesP0[0], testLocalitiesP1[0]}, wantLocalities: [][]xdsresource.Locality{ {testLocalitiesP0[0]}, {testLocalitiesP1[0]}, }, }, { name: "2 localities in each sorted", localities: []xdsresource.Locality{ testLocalitiesP0[0], testLocalitiesP0[1], testLocalitiesP1[0], testLocalitiesP1[1]}, wantLocalities: [][]xdsresource.Locality{ {testLocalitiesP0[0], testLocalitiesP0[1]}, {testLocalitiesP1[0], testLocalitiesP1[1]}, }, }, { // The localities are given in order [p1, p0, p1, p0], but the // returned priority list must be sorted [p0, p1], because the list // order is the priority order. name: "2 localities in each needs to sort", localities: []xdsresource.Locality{ testLocalitiesP1[1], testLocalitiesP0[1], testLocalitiesP1[0], testLocalitiesP0[0]}, wantLocalities: [][]xdsresource.Locality{ {testLocalitiesP0[1], testLocalitiesP0[0]}, {testLocalitiesP1[1], testLocalitiesP1[0]}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotLocalities := groupLocalitiesByPriority(tt.localities) if diff := cmp.Diff(gotLocalities, tt.wantLocalities); diff != "" { t.Errorf("groupLocalitiesByPriority() diff(-got +want) %v", diff) } }) } } func TestDedupSortedIntSlice(t *testing.T) { tests := []struct { name string a []int want []int }{ { name: "empty", a: []int{}, want: []int{}, }, { name: "no dup", a: []int{0, 1, 2, 3}, want: []int{0, 1, 2, 3}, }, { name: "with dup", a: []int{0, 0, 1, 1, 1, 2, 3}, want: []int{0, 1, 2, 3}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := dedupSortedIntSlice(tt.a); !cmp.Equal(got, tt.want) { t.Errorf("dedupSortedIntSlice() = %v, want %v, diff %v", got, tt.want, cmp.Diff(got, tt.want)) } }) } } func TestPriorityLocalitiesToClusterImpl(t *testing.T) { tests := []struct { name string localities []xdsresource.Locality priorityName string mechanism DiscoveryMechanism childPolicy *internalserviceconfig.BalancerConfig wantConfig *clusterimpl.LBConfig wantAddrs []resolver.Address wantErr bool }{{ name: "round robin as child, no LRS", localities: []xdsresource.Locality{ { Endpoints: []xdsresource.Endpoint{ {Address: "addr-1-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90}, {Address: "addr-1-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10}, }, ID: internal.LocalityID{Zone: "test-zone-1"}, Weight: 20, }, { Endpoints: []xdsresource.Endpoint{ {Address: "addr-2-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90}, {Address: "addr-2-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10}, }, ID: internal.LocalityID{Zone: "test-zone-2"}, Weight: 80, }, }, priorityName: "test-priority", childPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name}, mechanism: DiscoveryMechanism{ Cluster: testClusterName, Type: DiscoveryMechanismTypeEDS, EDSServiceName: testEDSService, }, // lrsServer is nil, so LRS policy will not be used. wantConfig: &clusterimpl.LBConfig{ Cluster: testClusterName, EDSServiceName: testEDSService, ChildPolicy: &internalserviceconfig.BalancerConfig{ Name: weightedtarget.Name, Config: &weightedtarget.LBConfig{ Targets: map[string]weightedtarget.Target{ assertString(internal.LocalityID{Zone: "test-zone-1"}.ToString): { Weight: 20, ChildPolicy: &internalserviceconfig.BalancerConfig{ Name: roundrobin.Name, }, }, assertString(internal.LocalityID{Zone: "test-zone-2"}.ToString): { Weight: 80, ChildPolicy: &internalserviceconfig.BalancerConfig{ Name: roundrobin.Name, }, }, }, }, }, }, wantAddrs: []resolver.Address{ testAddrWithAttrs("addr-1-1", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-1"}), testAddrWithAttrs("addr-1-2", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-1"}), testAddrWithAttrs("addr-2-1", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-2"}), testAddrWithAttrs("addr-2-2", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-2"}), }, }, { name: "ring_hash as child", localities: []xdsresource.Locality{ { Endpoints: []xdsresource.Endpoint{ {Address: "addr-1-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90}, {Address: "addr-1-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10}, }, ID: internal.LocalityID{Zone: "test-zone-1"}, Weight: 20, }, { Endpoints: []xdsresource.Endpoint{ {Address: "addr-2-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90}, {Address: "addr-2-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10}, }, ID: internal.LocalityID{Zone: "test-zone-2"}, Weight: 80, }, }, priorityName: "test-priority", childPolicy: &internalserviceconfig.BalancerConfig{Name: ringhash.Name, Config: &ringhash.LBConfig{MinRingSize: 1, MaxRingSize: 2}}, // lrsServer is nil, so LRS policy will not be used. wantConfig: &clusterimpl.LBConfig{ ChildPolicy: &internalserviceconfig.BalancerConfig{ Name: ringhash.Name, Config: &ringhash.LBConfig{MinRingSize: 1, MaxRingSize: 2}, }, }, wantAddrs: []resolver.Address{ testAddrWithAttrs("addr-1-1", newUint32(1800), "test-priority", &internal.LocalityID{Zone: "test-zone-1"}), testAddrWithAttrs("addr-1-2", newUint32(200), "test-priority", &internal.LocalityID{Zone: "test-zone-1"}), testAddrWithAttrs("addr-2-1", newUint32(7200), "test-priority", &internal.LocalityID{Zone: "test-zone-2"}), testAddrWithAttrs("addr-2-2", newUint32(800), "test-priority", &internal.LocalityID{Zone: "test-zone-2"}), }, }, { name: "unsupported child", localities: []xdsresource.Locality{{ Endpoints: []xdsresource.Endpoint{ {Address: "addr-1-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90}, {Address: "addr-1-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10}, }, ID: internal.LocalityID{Zone: "test-zone-1"}, Weight: 20, }}, priorityName: "test-priority", childPolicy: &internalserviceconfig.BalancerConfig{Name: "some-child"}, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, got1, err := priorityLocalitiesToClusterImpl(tt.localities, tt.priorityName, tt.mechanism, nil, tt.childPolicy) if (err != nil) != tt.wantErr { t.Fatalf("priorityLocalitiesToClusterImpl() error = %v, wantErr %v", err, tt.wantErr) } if diff := cmp.Diff(got, tt.wantConfig); diff != "" { t.Errorf("localitiesToWeightedTarget() diff (-got +want) %v", diff) } if diff := cmp.Diff(got1, tt.wantAddrs, cmp.AllowUnexported(attributes.Attributes{})); diff != "" { t.Errorf("localitiesToWeightedTarget() diff (-got +want) %v", diff) } }) } } func TestLocalitiesToWeightedTarget(t *testing.T) { tests := []struct { name string localities []xdsresource.Locality priorityName string childPolicy *internalserviceconfig.BalancerConfig lrsServer *string wantConfig *weightedtarget.LBConfig wantAddrs []resolver.Address }{ { name: "roundrobin as child, with LRS", localities: []xdsresource.Locality{ { Endpoints: []xdsresource.Endpoint{ {Address: "addr-1-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy}, {Address: "addr-1-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy}, }, ID: internal.LocalityID{Zone: "test-zone-1"}, Weight: 20, }, { Endpoints: []xdsresource.Endpoint{ {Address: "addr-2-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy}, {Address: "addr-2-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy}, }, ID: internal.LocalityID{Zone: "test-zone-2"}, Weight: 80, }, }, priorityName: "test-priority", childPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name}, lrsServer: newString("test-lrs-server"), wantConfig: &weightedtarget.LBConfig{ Targets: map[string]weightedtarget.Target{ assertString(internal.LocalityID{Zone: "test-zone-1"}.ToString): { Weight: 20, ChildPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name}, }, assertString(internal.LocalityID{Zone: "test-zone-2"}.ToString): { Weight: 80, ChildPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name}, }, }, }, wantAddrs: []resolver.Address{ testAddrWithAttrs("addr-1-1", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-1"}), testAddrWithAttrs("addr-1-2", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-1"}), testAddrWithAttrs("addr-2-1", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-2"}), testAddrWithAttrs("addr-2-2", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-2"}), }, }, { name: "roundrobin as child, no LRS", localities: []xdsresource.Locality{ { Endpoints: []xdsresource.Endpoint{ {Address: "addr-1-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy}, {Address: "addr-1-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy}, }, ID: internal.LocalityID{Zone: "test-zone-1"}, Weight: 20, }, { Endpoints: []xdsresource.Endpoint{ {Address: "addr-2-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy}, {Address: "addr-2-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy}, }, ID: internal.LocalityID{Zone: "test-zone-2"}, Weight: 80, }, }, priorityName: "test-priority", childPolicy: &internalserviceconfig.BalancerConfig{Name: roundrobin.Name}, // lrsServer is nil, so LRS policy will not be used. wantConfig: &weightedtarget.LBConfig{ Targets: map[string]weightedtarget.Target{ assertString(internal.LocalityID{Zone: "test-zone-1"}.ToString): { Weight: 20, ChildPolicy: &internalserviceconfig.BalancerConfig{ Name: roundrobin.Name, }, }, assertString(internal.LocalityID{Zone: "test-zone-2"}.ToString): { Weight: 80, ChildPolicy: &internalserviceconfig.BalancerConfig{ Name: roundrobin.Name, }, }, }, }, wantAddrs: []resolver.Address{ testAddrWithAttrs("addr-1-1", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-1"}), testAddrWithAttrs("addr-1-2", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-1"}), testAddrWithAttrs("addr-2-1", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-2"}), testAddrWithAttrs("addr-2-2", nil, "test-priority", &internal.LocalityID{Zone: "test-zone-2"}), }, }, { name: "weighted round robin as child, no LRS", localities: []xdsresource.Locality{ { Endpoints: []xdsresource.Endpoint{ {Address: "addr-1-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90}, {Address: "addr-1-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10}, }, ID: internal.LocalityID{Zone: "test-zone-1"}, Weight: 20, }, { Endpoints: []xdsresource.Endpoint{ {Address: "addr-2-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90}, {Address: "addr-2-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10}, }, ID: internal.LocalityID{Zone: "test-zone-2"}, Weight: 80, }, }, priorityName: "test-priority", childPolicy: &internalserviceconfig.BalancerConfig{Name: weightedroundrobin.Name}, // lrsServer is nil, so LRS policy will not be used. wantConfig: &weightedtarget.LBConfig{ Targets: map[string]weightedtarget.Target{ assertString(internal.LocalityID{Zone: "test-zone-1"}.ToString): { Weight: 20, ChildPolicy: &internalserviceconfig.BalancerConfig{ Name: weightedroundrobin.Name, }, }, assertString(internal.LocalityID{Zone: "test-zone-2"}.ToString): { Weight: 80, ChildPolicy: &internalserviceconfig.BalancerConfig{ Name: weightedroundrobin.Name, }, }, }, }, wantAddrs: []resolver.Address{ testAddrWithAttrs("addr-1-1", newUint32(90), "test-priority", &internal.LocalityID{Zone: "test-zone-1"}), testAddrWithAttrs("addr-1-2", newUint32(10), "test-priority", &internal.LocalityID{Zone: "test-zone-1"}), testAddrWithAttrs("addr-2-1", newUint32(90), "test-priority", &internal.LocalityID{Zone: "test-zone-2"}), testAddrWithAttrs("addr-2-2", newUint32(10), "test-priority", &internal.LocalityID{Zone: "test-zone-2"}), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, got1 := localitiesToWeightedTarget(tt.localities, tt.priorityName, tt.childPolicy) if diff := cmp.Diff(got, tt.wantConfig); diff != "" { t.Errorf("localitiesToWeightedTarget() diff (-got +want) %v", diff) } if diff := cmp.Diff(got1, tt.wantAddrs, cmp.AllowUnexported(attributes.Attributes{})); diff != "" { t.Errorf("localitiesToWeightedTarget() diff (-got +want) %v", diff) } }) } } func TestLocalitiesToRingHash(t *testing.T) { tests := []struct { name string localities []xdsresource.Locality priorityName string wantAddrs []resolver.Address }{ { // Check that address weights are locality_weight * endpoint_weight. name: "with locality and endpoint weight", localities: []xdsresource.Locality{ { Endpoints: []xdsresource.Endpoint{ {Address: "addr-1-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90}, {Address: "addr-1-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10}, }, ID: internal.LocalityID{Zone: "test-zone-1"}, Weight: 20, }, { Endpoints: []xdsresource.Endpoint{ {Address: "addr-2-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90}, {Address: "addr-2-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10}, }, ID: internal.LocalityID{Zone: "test-zone-2"}, Weight: 80, }, }, priorityName: "test-priority", wantAddrs: []resolver.Address{ testAddrWithAttrs("addr-1-1", newUint32(1800), "test-priority", &internal.LocalityID{Zone: "test-zone-1"}), testAddrWithAttrs("addr-1-2", newUint32(200), "test-priority", &internal.LocalityID{Zone: "test-zone-1"}), testAddrWithAttrs("addr-2-1", newUint32(7200), "test-priority", &internal.LocalityID{Zone: "test-zone-2"}), testAddrWithAttrs("addr-2-2", newUint32(800), "test-priority", &internal.LocalityID{Zone: "test-zone-2"}), }, }, { // Check that endpoint_weight is 0, weight is the locality weight. name: "locality weight only", localities: []xdsresource.Locality{ { Endpoints: []xdsresource.Endpoint{ {Address: "addr-1-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy}, {Address: "addr-1-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy}, }, ID: internal.LocalityID{Zone: "test-zone-1"}, Weight: 20, }, { Endpoints: []xdsresource.Endpoint{ {Address: "addr-2-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy}, {Address: "addr-2-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy}, }, ID: internal.LocalityID{Zone: "test-zone-2"}, Weight: 80, }, }, priorityName: "test-priority", wantAddrs: []resolver.Address{ testAddrWithAttrs("addr-1-1", newUint32(20), "test-priority", &internal.LocalityID{Zone: "test-zone-1"}), testAddrWithAttrs("addr-1-2", newUint32(20), "test-priority", &internal.LocalityID{Zone: "test-zone-1"}), testAddrWithAttrs("addr-2-1", newUint32(80), "test-priority", &internal.LocalityID{Zone: "test-zone-2"}), testAddrWithAttrs("addr-2-2", newUint32(80), "test-priority", &internal.LocalityID{Zone: "test-zone-2"}), }, }, { // Check that locality_weight is 0, weight is the endpoint weight. name: "endpoint weight only", localities: []xdsresource.Locality{ { Endpoints: []xdsresource.Endpoint{ {Address: "addr-1-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90}, {Address: "addr-1-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10}, }, ID: internal.LocalityID{Zone: "test-zone-1"}, }, { Endpoints: []xdsresource.Endpoint{ {Address: "addr-2-1", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 90}, {Address: "addr-2-2", HealthStatus: xdsresource.EndpointHealthStatusHealthy, Weight: 10}, }, ID: internal.LocalityID{Zone: "test-zone-2"}, }, }, priorityName: "test-priority", wantAddrs: []resolver.Address{ testAddrWithAttrs("addr-1-1", newUint32(90), "test-priority", &internal.LocalityID{Zone: "test-zone-1"}), testAddrWithAttrs("addr-1-2", newUint32(10), "test-priority", &internal.LocalityID{Zone: "test-zone-1"}), testAddrWithAttrs("addr-2-1", newUint32(90), "test-priority", &internal.LocalityID{Zone: "test-zone-2"}), testAddrWithAttrs("addr-2-2", newUint32(10), "test-priority", &internal.LocalityID{Zone: "test-zone-2"}), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := localitiesToRingHash(tt.localities, tt.priorityName) if diff := cmp.Diff(got, tt.wantAddrs, cmp.AllowUnexported(attributes.Attributes{})); diff != "" { t.Errorf("localitiesToWeightedTarget() diff (-got +want) %v", diff) } }) } } func assertString(f func() (string, error)) string { s, err := f() if err != nil { panic(err.Error()) } return s } func testAddrWithAttrs(addrStr string, weight *uint32, priority string, lID *internal.LocalityID) resolver.Address { addr := resolver.Address{Addr: addrStr} if weight != nil { addr = weightedroundrobin.SetAddrInfo(addr, weightedroundrobin.AddrInfo{Weight: *weight}) } path := []string{priority} if lID != nil { path = append(path, assertString(lID.ToString)) addr = internal.SetLocalityID(addr, *lID) } addr = hierarchy.Set(addr, path) return addr } func TestConvertClusterImplMapToOutlierDetection(t *testing.T) { tests := []struct { name string ciCfgsMap map[string]*clusterimpl.LBConfig odCfg outlierdetection.LBConfig wantODCfgs map[string]*outlierdetection.LBConfig }{ { name: "single-entry-noop", ciCfgsMap: map[string]*clusterimpl.LBConfig{ "child1": { Cluster: "cluster1", }, }, odCfg: outlierdetection.LBConfig{ Interval: 1<<63 - 1, }, wantODCfgs: map[string]*outlierdetection.LBConfig{ "child1": { Interval: 1<<63 - 1, ChildPolicy: &internalserviceconfig.BalancerConfig{ Name: clusterimpl.Name, Config: &clusterimpl.LBConfig{ Cluster: "cluster1", }, }, }, }, }, { name: "multiple-entries-noop", ciCfgsMap: map[string]*clusterimpl.LBConfig{ "child1": { Cluster: "cluster1", }, "child2": { Cluster: "cluster2", }, }, odCfg: outlierdetection.LBConfig{ Interval: 1<<63 - 1, }, wantODCfgs: map[string]*outlierdetection.LBConfig{ "child1": { Interval: 1<<63 - 1, ChildPolicy: &internalserviceconfig.BalancerConfig{ Name: clusterimpl.Name, Config: &clusterimpl.LBConfig{ Cluster: "cluster1", }, }, }, "child2": { Interval: 1<<63 - 1, ChildPolicy: &internalserviceconfig.BalancerConfig{ Name: clusterimpl.Name, Config: &clusterimpl.LBConfig{ Cluster: "cluster2", }, }, }, }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { got := convertClusterImplMapToOutlierDetection(test.ciCfgsMap, test.odCfg) if diff := cmp.Diff(got, test.wantODCfgs); diff != "" { t.Fatalf("convertClusterImplMapToOutlierDetection() diff(-got +want) %v", diff) } }) } }