blob: 5fbb0b95e33938d76a6dc021ac3f46e5a3fe8c62 [file] [log] [blame]
/*
*
* 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)
}
})
}
}