Skip to content

Commit 5e58734

Browse files
authored
xds: Add support for Custom LB Policies (#6224)
1 parent 5c4bee5 commit 5e58734

32 files changed

+955
-725
lines changed

attributes/attributes.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
// later release.
2626
package attributes
2727

28+
import (
29+
"fmt"
30+
"strings"
31+
)
32+
2833
// Attributes is an immutable struct for storing and retrieving generic
2934
// key/value pairs. Keys must be hashable, and users should define their own
3035
// types for keys. Values should not be modified after they are added to an
@@ -99,3 +104,27 @@ func (a *Attributes) Equal(o *Attributes) bool {
99104
}
100105
return true
101106
}
107+
108+
// String prints the attribute map. If any key or values throughout the map
109+
// implement fmt.Stringer, it calls that method and appends.
110+
func (a *Attributes) String() string {
111+
var sb strings.Builder
112+
sb.WriteString("{")
113+
first := true
114+
for k, v := range a.m {
115+
var key, val string
116+
if str, ok := k.(interface{ String() string }); ok {
117+
key = str.String()
118+
}
119+
if str, ok := v.(interface{ String() string }); ok {
120+
val = str.String()
121+
}
122+
if !first {
123+
sb.WriteString(", ")
124+
}
125+
sb.WriteString(fmt.Sprintf("%q: %q, ", key, val))
126+
first = false
127+
}
128+
sb.WriteString("}")
129+
return sb.String()
130+
}

balancer/weightedroundrobin/weightedroundrobin.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
package weightedroundrobin
2929

3030
import (
31+
"fmt"
32+
3133
"google.golang.org/grpc/resolver"
3234
)
3335

@@ -61,3 +63,7 @@ func GetAddrInfo(addr resolver.Address) AddrInfo {
6163
ai, _ := v.(AddrInfo)
6264
return ai
6365
}
66+
67+
func (a AddrInfo) String() string {
68+
return fmt.Sprintf("Weight: %d", a.Weight)
69+
}

balancer/weightedtarget/weightedaggregator/aggregator.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,14 @@ func (wbsa *Aggregator) ResumeStateUpdates() {
178178
}
179179
}
180180

181+
// NeedUpdateStateOnResume sets the UpdateStateOnResume bool to true, letting a
182+
// picker update be sent once ResumeStateUpdates is called.
183+
func (wbsa *Aggregator) NeedUpdateStateOnResume() {
184+
wbsa.mu.Lock()
185+
defer wbsa.mu.Unlock()
186+
wbsa.needUpdateStateOnResume = true
187+
}
188+
181189
// UpdateState is called to report a balancer state change from sub-balancer.
182190
// It's usually called by the balancer group.
183191
//

balancer/weightedtarget/weightedtarget.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,18 @@ func (b *weightedTargetBalancer) UpdateClientConnState(s balancer.ClientConnStat
143143

144144
b.targets = newConfig.Targets
145145

146+
// If the targets length is zero, it means we have removed all child
147+
// policies from the balancer group and aggregator.
148+
// At the start of this UpdateClientConnState() operation, a call to
149+
// b.stateAggregator.ResumeStateUpdates() is deferred. Thus, setting the
150+
// needUpdateStateOnResume bool to true here will ensure a new picker is
151+
// built as part of that deferred function. Since there are now no child
152+
// policies, the aggregated connectivity state reported form the Aggregator
153+
// will be TRANSIENT_FAILURE.
154+
if len(b.targets) == 0 {
155+
b.stateAggregator.NeedUpdateStateOnResume()
156+
}
157+
146158
return nil
147159
}
148160

balancer/weightedtarget/weightedtarget_test.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,8 @@ func init() {
166166
// TestWeightedTarget covers the cases that a sub-balancer is added and a
167167
// sub-balancer is removed. It verifies that the addresses and balancer configs
168168
// are forwarded to the right sub-balancer. This test is intended to test the
169-
// glue code in weighted_target.
169+
// glue code in weighted_target. It also tests an empty target config update,
170+
// which should trigger a transient failure state update.
170171
func (s) TestWeightedTarget(t *testing.T) {
171172
cc := testutils.NewTestClientConn(t)
172173
wtb := wtbBuilder.Build(cc, balancer.BuildOptions{})
@@ -306,6 +307,24 @@ func (s) TestWeightedTarget(t *testing.T) {
306307
t.Fatalf("picker.Pick, got %v, want SubConn=%v", gotSCSt, sc3)
307308
}
308309
}
310+
// Update the Weighted Target Balancer with an empty address list and no
311+
// targets. This should cause a Transient Failure State update to the Client
312+
// Conn.
313+
emptyConfig, err := wtbParser.ParseConfig([]byte(`{}`))
314+
if err != nil {
315+
t.Fatalf("Failed to parse balancer config: %v", err)
316+
}
317+
if err := wtb.UpdateClientConnState(balancer.ClientConnState{
318+
ResolverState: resolver.State{},
319+
BalancerConfig: emptyConfig,
320+
}); err != nil {
321+
t.Fatalf("Failed to update ClientConn state: %v", err)
322+
}
323+
324+
state := <-cc.NewStateCh
325+
if state != connectivity.TransientFailure {
326+
t.Fatalf("Empty target update should have triggered a TF state update, got: %v", state)
327+
}
309328
}
310329

311330
// TestWeightedTarget_OneSubBalancer_AddRemoveBackend tests the case where we

internal/testutils/xds/e2e/clientresources.go

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,14 @@ func ClusterResourceWithOptions(opts ClusterOptions) *v3clusterpb.Cluster {
524524
return cluster
525525
}
526526

527+
// LocalityOptions contains options to configure a Locality.
528+
type LocalityOptions struct {
529+
// Ports is a set of ports on "localhost" belonging to this locality.
530+
Ports []uint32
531+
// Weight is the weight of the locality, used for load balancing.
532+
Weight uint32
533+
}
534+
527535
// EndpointOptions contains options to configure an Endpoint (or
528536
// ClusterLoadAssignment) resource.
529537
type EndpointOptions struct {
@@ -533,9 +541,8 @@ type EndpointOptions struct {
533541
// Host is the hostname of the endpoints. In our e2e tests, hostname must
534542
// always be "localhost".
535543
Host string
536-
// Ports is a set of ports on "localhost" where the endpoints corresponding
537-
// to this resource reside.
538-
Ports []uint32
544+
// Localities is a set of localities belonging to this resource.
545+
Localities []LocalityOptions
539546
// DropPercents is a map from drop category to a drop percentage. If unset,
540547
// no drops are configured.
541548
DropPercents map[string]int
@@ -546,34 +553,50 @@ func DefaultEndpoint(clusterName string, host string, ports []uint32) *v3endpoin
546553
return EndpointResourceWithOptions(EndpointOptions{
547554
ClusterName: clusterName,
548555
Host: host,
549-
Ports: ports,
556+
Localities: []LocalityOptions{
557+
{
558+
Ports: ports,
559+
Weight: 1,
560+
},
561+
},
550562
})
551563
}
552564

553565
// EndpointResourceWithOptions returns an xds Endpoint resource configured with
554566
// the provided options.
555567
func EndpointResourceWithOptions(opts EndpointOptions) *v3endpointpb.ClusterLoadAssignment {
556-
var lbEndpoints []*v3endpointpb.LbEndpoint
557-
for _, port := range opts.Ports {
558-
lbEndpoints = append(lbEndpoints, &v3endpointpb.LbEndpoint{
559-
HostIdentifier: &v3endpointpb.LbEndpoint_Endpoint{Endpoint: &v3endpointpb.Endpoint{
560-
Address: &v3corepb.Address{Address: &v3corepb.Address_SocketAddress{
561-
SocketAddress: &v3corepb.SocketAddress{
562-
Protocol: v3corepb.SocketAddress_TCP,
563-
Address: opts.Host,
564-
PortSpecifier: &v3corepb.SocketAddress_PortValue{PortValue: port}},
568+
var endpoints []*v3endpointpb.LocalityLbEndpoints
569+
for i, locality := range opts.Localities {
570+
var lbEndpoints []*v3endpointpb.LbEndpoint
571+
for _, port := range locality.Ports {
572+
lbEndpoints = append(lbEndpoints, &v3endpointpb.LbEndpoint{
573+
HostIdentifier: &v3endpointpb.LbEndpoint_Endpoint{Endpoint: &v3endpointpb.Endpoint{
574+
Address: &v3corepb.Address{Address: &v3corepb.Address_SocketAddress{
575+
SocketAddress: &v3corepb.SocketAddress{
576+
Protocol: v3corepb.SocketAddress_TCP,
577+
Address: opts.Host,
578+
PortSpecifier: &v3corepb.SocketAddress_PortValue{PortValue: port}},
579+
}},
565580
}},
566-
}},
581+
LoadBalancingWeight: &wrapperspb.UInt32Value{Value: 1},
582+
})
583+
}
584+
585+
endpoints = append(endpoints, &v3endpointpb.LocalityLbEndpoints{
586+
Locality: &v3corepb.Locality{
587+
Region: fmt.Sprintf("region-%d", i+1),
588+
Zone: fmt.Sprintf("zone-%d", i+1),
589+
SubZone: fmt.Sprintf("subzone-%d", i+1),
590+
},
591+
LbEndpoints: lbEndpoints,
592+
LoadBalancingWeight: &wrapperspb.UInt32Value{Value: locality.Weight},
593+
Priority: 0,
567594
})
568595
}
596+
569597
cla := &v3endpointpb.ClusterLoadAssignment{
570598
ClusterName: opts.ClusterName,
571-
Endpoints: []*v3endpointpb.LocalityLbEndpoints{{
572-
Locality: &v3corepb.Locality{SubZone: "subzone"},
573-
LbEndpoints: lbEndpoints,
574-
LoadBalancingWeight: &wrapperspb.UInt32Value{Value: 1},
575-
Priority: 0,
576-
}},
599+
Endpoints: endpoints,
577600
}
578601

579602
var drops []*v3endpointpb.ClusterLoadAssignment_Policy_DropOverload

resolver/resolver.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ package resolver
2222

2323
import (
2424
"context"
25+
"fmt"
2526
"net"
2627
"net/url"
2728
"strings"
2829

2930
"google.golang.org/grpc/attributes"
3031
"google.golang.org/grpc/credentials"
31-
"google.golang.org/grpc/internal/pretty"
3232
"google.golang.org/grpc/serviceconfig"
3333
)
3434

@@ -124,7 +124,7 @@ type Address struct {
124124
Attributes *attributes.Attributes
125125

126126
// BalancerAttributes contains arbitrary data about this address intended
127-
// for consumption by the LB policy. These attribes do not affect SubConn
127+
// for consumption by the LB policy. These attributes do not affect SubConn
128128
// creation, connection establishment, handshaking, etc.
129129
BalancerAttributes *attributes.Attributes
130130

@@ -151,7 +151,17 @@ func (a Address) Equal(o Address) bool {
151151

152152
// String returns JSON formatted string representation of the address.
153153
func (a Address) String() string {
154-
return pretty.ToJSON(a)
154+
var sb strings.Builder
155+
sb.WriteString(fmt.Sprintf("{Addr: %q, ", a.Addr))
156+
sb.WriteString(fmt.Sprintf("ServerName: %q, ", a.ServerName))
157+
if a.Attributes != nil {
158+
sb.WriteString(fmt.Sprintf("Attributes: %v, ", a.Attributes.String()))
159+
}
160+
if a.BalancerAttributes != nil {
161+
sb.WriteString(fmt.Sprintf("BalancerAttributes: %v", a.BalancerAttributes.String()))
162+
}
163+
sb.WriteString("}")
164+
return sb.String()
155165
}
156166

157167
// BuildOptions includes additional information for the builder to create

0 commit comments

Comments
 (0)