Skip to content

Commit a6c8a32

Browse files
townbaprintchard
authored andcommitted
xds: use locality from the connected address for load reporting (grpc#7378)
1 parent e21a4c0 commit a6c8a32

File tree

9 files changed

+91
-44
lines changed

9 files changed

+91
-44
lines changed

balancer/balancer.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,21 @@ func unregisterForTesting(name string) {
7272
delete(m, name)
7373
}
7474

75+
// connectedAddress returns the connected address for a SubConnState. The
76+
// address is only valid if the state is READY.
77+
func connectedAddress(scs SubConnState) resolver.Address {
78+
return scs.connectedAddress
79+
}
80+
81+
// setConnectedAddress sets the connected address for a SubConnState.
82+
func setConnectedAddress(scs *SubConnState, addr resolver.Address) {
83+
scs.connectedAddress = addr
84+
}
85+
7586
func init() {
7687
internal.BalancerUnregister = unregisterForTesting
88+
internal.ConnectedAddress = connectedAddress
89+
internal.SetConnectedAddress = setConnectedAddress
7790
}
7891

7992
// Get returns the resolver builder registered with the given name.
@@ -410,6 +423,9 @@ type SubConnState struct {
410423
// ConnectionError is set if the ConnectivityState is TransientFailure,
411424
// describing the reason the SubConn failed. Otherwise, it is nil.
412425
ConnectionError error
426+
// connectedAddr contains the connected address when ConnectivityState is
427+
// Ready. Otherwise, it is indeterminate.
428+
connectedAddress resolver.Address
413429
}
414430

415431
// ClientConnState describes the state of a ClientConn relevant to the

balancer_wrapper.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,15 @@ import (
2525

2626
"google.golang.org/grpc/balancer"
2727
"google.golang.org/grpc/connectivity"
28+
"google.golang.org/grpc/internal"
2829
"google.golang.org/grpc/internal/balancer/gracefulswitch"
2930
"google.golang.org/grpc/internal/channelz"
3031
"google.golang.org/grpc/internal/grpcsync"
3132
"google.golang.org/grpc/resolver"
3233
)
3334

35+
var setConnectedAddress = internal.SetConnectedAddress.(func(*balancer.SubConnState, resolver.Address))
36+
3437
// ccBalancerWrapper sits between the ClientConn and the Balancer.
3538
//
3639
// ccBalancerWrapper implements methods corresponding to the ones on the
@@ -252,15 +255,19 @@ type acBalancerWrapper struct {
252255

253256
// updateState is invoked by grpc to push a subConn state update to the
254257
// underlying balancer.
255-
func (acbw *acBalancerWrapper) updateState(s connectivity.State, err error) {
258+
func (acbw *acBalancerWrapper) updateState(s connectivity.State, curAddr resolver.Address, err error) {
256259
acbw.ccb.serializer.Schedule(func(ctx context.Context) {
257260
if ctx.Err() != nil || acbw.ccb.balancer == nil {
258261
return
259262
}
260263
// Even though it is optional for balancers, gracefulswitch ensures
261264
// opts.StateListener is set, so this cannot ever be nil.
262265
// TODO: delete this comment when UpdateSubConnState is removed.
263-
acbw.stateListener(balancer.SubConnState{ConnectivityState: s, ConnectionError: err})
266+
scs := balancer.SubConnState{ConnectivityState: s, ConnectionError: err}
267+
if s == connectivity.Ready {
268+
setConnectedAddress(&scs, curAddr)
269+
}
270+
acbw.stateListener(scs)
264271
})
265272
}
266273

clientconn.go

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"fmt"
2525
"math"
2626
"net/url"
27+
"slices"
2728
"strings"
2829
"sync"
2930
"sync/atomic"
@@ -812,17 +813,11 @@ func (cc *ClientConn) applyFailingLBLocked(sc *serviceconfig.ParseResult) {
812813
cc.csMgr.updateState(connectivity.TransientFailure)
813814
}
814815

815-
// Makes a copy of the input addresses slice and clears out the balancer
816-
// attributes field. Addresses are passed during subconn creation and address
817-
// update operations. In both cases, we will clear the balancer attributes by
818-
// calling this function, and therefore we will be able to use the Equal method
819-
// provided by the resolver.Address type for comparison.
820-
func copyAddressesWithoutBalancerAttributes(in []resolver.Address) []resolver.Address {
816+
// Makes a copy of the input addresses slice. Addresses are passed during
817+
// subconn creation and address update operations.
818+
func copyAddresses(in []resolver.Address) []resolver.Address {
821819
out := make([]resolver.Address, len(in))
822-
for i := range in {
823-
out[i] = in[i]
824-
out[i].BalancerAttributes = nil
825-
}
820+
copy(out, in)
826821
return out
827822
}
828823

@@ -837,7 +832,7 @@ func (cc *ClientConn) newAddrConnLocked(addrs []resolver.Address, opts balancer.
837832
ac := &addrConn{
838833
state: connectivity.Idle,
839834
cc: cc,
840-
addrs: copyAddressesWithoutBalancerAttributes(addrs),
835+
addrs: copyAddresses(addrs),
841836
scopts: opts,
842837
dopts: cc.dopts,
843838
channelz: channelz.RegisterSubChannel(cc.channelz, ""),
@@ -923,30 +918,32 @@ func (ac *addrConn) connect() error {
923918
return nil
924919
}
925920

926-
func equalAddresses(a, b []resolver.Address) bool {
927-
if len(a) != len(b) {
928-
return false
929-
}
930-
for i, v := range a {
931-
if !v.Equal(b[i]) {
932-
return false
933-
}
934-
}
935-
return true
921+
// equalAddressIgnoringBalAttributes returns true is a and b are considered equal.
922+
// This is different from the Equal method on the resolver.Address type which
923+
// considers all fields to determine equality. Here, we only consider fields
924+
// that are meaningful to the subConn.
925+
func equalAddressIgnoringBalAttributes(a, b *resolver.Address) bool {
926+
return a.Addr == b.Addr && a.ServerName == b.ServerName &&
927+
a.Attributes.Equal(b.Attributes) &&
928+
a.Metadata == b.Metadata
929+
}
930+
931+
func equalAddressesIgnoringBalAttributes(a, b []resolver.Address) bool {
932+
return slices.EqualFunc(a, b, func(a, b resolver.Address) bool { return equalAddressIgnoringBalAttributes(&a, &b) })
936933
}
937934

938935
// updateAddrs updates ac.addrs with the new addresses list and handles active
939936
// connections or connection attempts.
940937
func (ac *addrConn) updateAddrs(addrs []resolver.Address) {
941-
addrs = copyAddressesWithoutBalancerAttributes(addrs)
938+
addrs = copyAddresses(addrs)
942939
limit := len(addrs)
943940
if limit > 5 {
944941
limit = 5
945942
}
946943
channelz.Infof(logger, ac.channelz, "addrConn: updateAddrs addrs (%d of %d): %v", limit, len(addrs), addrs[:limit])
947944

948945
ac.mu.Lock()
949-
if equalAddresses(ac.addrs, addrs) {
946+
if equalAddressesIgnoringBalAttributes(ac.addrs, addrs) {
950947
ac.mu.Unlock()
951948
return
952949
}
@@ -965,7 +962,7 @@ func (ac *addrConn) updateAddrs(addrs []resolver.Address) {
965962
// Try to find the connected address.
966963
for _, a := range addrs {
967964
a.ServerName = ac.cc.getServerName(a)
968-
if a.Equal(ac.curAddr) {
965+
if equalAddressIgnoringBalAttributes(&a, &ac.curAddr) {
969966
// We are connected to a valid address, so do nothing but
970967
// update the addresses.
971968
ac.mu.Unlock()
@@ -1211,7 +1208,7 @@ func (ac *addrConn) updateConnectivityState(s connectivity.State, lastErr error)
12111208
} else {
12121209
channelz.Infof(logger, ac.channelz, "Subchannel Connectivity change to %v, last error: %s", s, lastErr)
12131210
}
1214-
ac.acbw.updateState(s, lastErr)
1211+
ac.acbw.updateState(s, ac.curAddr, lastErr)
12151212
}
12161213

12171214
// adjustParams updates parameters used to create transports upon

internal/internal.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,13 @@ var (
208208
// ShuffleAddressListForTesting pseudo-randomizes the order of addresses. n
209209
// is the number of elements. swap swaps the elements with indexes i and j.
210210
ShuffleAddressListForTesting any // func(n int, swap func(i, j int))
211+
212+
// ConnectedAddress returns the connected address for a SubConnState. The
213+
// address is only valid if the state is READY.
214+
ConnectedAddress any // func (scs SubConnState) resolver.Address
215+
216+
// SetConnectedAddress sets the connected address for a SubConnState.
217+
SetConnectedAddress any // func(scs *SubConnState, addr resolver.Address)
211218
)
212219

213220
// HealthChecker defines the signature of the client-side LB channel health

xds/internal/balancer/clusterimpl/balancer_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"google.golang.org/grpc/balancer/base"
3333
"google.golang.org/grpc/balancer/roundrobin"
3434
"google.golang.org/grpc/connectivity"
35+
"google.golang.org/grpc/internal"
3536
"google.golang.org/grpc/internal/balancer/stub"
3637
"google.golang.org/grpc/internal/grpctest"
3738
internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
@@ -637,7 +638,10 @@ func (s) TestLoadReporting(t *testing.T) {
637638
t.Fatal(err.Error())
638639
}
639640

640-
sc1.UpdateState(balancer.SubConnState{ConnectivityState: connectivity.Ready})
641+
scs := balancer.SubConnState{ConnectivityState: connectivity.Ready}
642+
sca := internal.SetConnectedAddress.(func(*balancer.SubConnState, resolver.Address))
643+
sca(&scs, addrs[0])
644+
sc1.UpdateState(scs)
641645
// Test pick with one backend.
642646
const successCount = 5
643647
const errorCount = 5

xds/internal/balancer/clusterimpl/clusterimpl.go

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131

3232
"google.golang.org/grpc/balancer"
3333
"google.golang.org/grpc/connectivity"
34+
"google.golang.org/grpc/internal"
3435
"google.golang.org/grpc/internal/balancer/gracefulswitch"
3536
"google.golang.org/grpc/internal/buffer"
3637
"google.golang.org/grpc/internal/grpclog"
@@ -52,6 +53,8 @@ const (
5253
defaultRequestCountMax = 1024
5354
)
5455

56+
var connectedAddress = internal.ConnectedAddress.(func(balancer.SubConnState) resolver.Address)
57+
5558
func init() {
5659
balancer.Register(bb{})
5760
}
@@ -360,22 +363,35 @@ func (scw *scWrapper) localityID() xdsinternal.LocalityID {
360363
func (b *clusterImplBalancer) NewSubConn(addrs []resolver.Address, opts balancer.NewSubConnOptions) (balancer.SubConn, error) {
361364
clusterName := b.getClusterName()
362365
newAddrs := make([]resolver.Address, len(addrs))
363-
var lID xdsinternal.LocalityID
364366
for i, addr := range addrs {
365367
newAddrs[i] = xds.SetXDSHandshakeClusterName(addr, clusterName)
366-
lID = xdsinternal.GetLocalityID(newAddrs[i])
367368
}
368369
var sc balancer.SubConn
370+
scw := &scWrapper{}
369371
oldListener := opts.StateListener
370-
opts.StateListener = func(state balancer.SubConnState) { b.updateSubConnState(sc, state, oldListener) }
372+
opts.StateListener = func(state balancer.SubConnState) {
373+
b.updateSubConnState(sc, state, oldListener)
374+
if state.ConnectivityState != connectivity.Ready {
375+
return
376+
}
377+
// Read connected address and call updateLocalityID() based on the connected
378+
// address's locality. https://github.com/grpc/grpc-go/issues/7339
379+
addr := connectedAddress(state)
380+
lID := xdsinternal.GetLocalityID(addr)
381+
if lID.Empty() {
382+
if b.logger.V(2) {
383+
b.logger.Infof("Locality ID for %s unexpectedly empty", addr)
384+
}
385+
return
386+
}
387+
scw.updateLocalityID(lID)
388+
}
371389
sc, err := b.ClientConn.NewSubConn(newAddrs, opts)
372390
if err != nil {
373391
return nil, err
374392
}
375-
// Wrap this SubConn in a wrapper, and add it to the map.
376-
ret := &scWrapper{SubConn: sc}
377-
ret.updateLocalityID(lID)
378-
return ret, nil
393+
scw.SubConn = sc
394+
return scw, nil
379395
}
380396

381397
func (b *clusterImplBalancer) RemoveSubConn(sc balancer.SubConn) {

xds/internal/balancer/clusterimpl/tests/balancer_test.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -310,14 +310,9 @@ func (s) TestLoadReportingPickFirstMultiLocality(t *testing.T) {
310310
}
311311
mgmtServer.LRSServer.LRSResponseChan <- &resp
312312

313-
// Wait for load to be reported for locality of server 2.
314-
// We (incorrectly) wait for load report for region-2 because presently
315-
// pickfirst always reports load for the locality of the last address in the
316-
// subconn. This will be fixed by ensuring there is only one address per
317-
// subconn.
318-
// TODO(#7339): Change region to region-1 once fixed.
319-
if err := waitForSuccessfulLoadReport(ctx, mgmtServer.LRSServer, "region-2"); err != nil {
320-
t.Fatalf("region-2 did not receive load due to error: %v", err)
313+
// Wait for load to be reported for locality of server 1.
314+
if err := waitForSuccessfulLoadReport(ctx, mgmtServer.LRSServer, "region-1"); err != nil {
315+
t.Fatalf("Server 1 did not receive load due to error: %v", err)
321316
}
322317

323318
// Stop server 1 and send one more rpc. Now the request should go to server 2.

xds/internal/balancer/outlierdetection/balancer_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -852,7 +852,7 @@ func (s) TestUpdateAddresses(t *testing.T) {
852852
}
853853

854854
func scwsEqual(gotSCWS subConnWithState, wantSCWS subConnWithState) error {
855-
if gotSCWS.sc != wantSCWS.sc || !cmp.Equal(gotSCWS.state, wantSCWS.state, cmp.AllowUnexported(subConnWrapper{}, addressInfo{}), cmpopts.IgnoreFields(subConnWrapper{}, "scUpdateCh")) {
855+
if gotSCWS.sc != wantSCWS.sc || !cmp.Equal(gotSCWS.state, wantSCWS.state, cmp.AllowUnexported(subConnWrapper{}, addressInfo{}, balancer.SubConnState{}), cmpopts.IgnoreFields(subConnWrapper{}, "scUpdateCh")) {
856856
return fmt.Errorf("received SubConnState: %+v, want %+v", gotSCWS, wantSCWS)
857857
}
858858
return nil

xds/internal/internal.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ func (l LocalityID) Equal(o any) bool {
5555
return l.Region == ol.Region && l.Zone == ol.Zone && l.SubZone == ol.SubZone
5656
}
5757

58+
// Empty returns whether or not the locality ID is empty.
59+
func (l LocalityID) Empty() bool {
60+
return l.Region == "" && l.Zone == "" && l.SubZone == ""
61+
}
62+
5863
// LocalityIDFromString converts a json representation of locality, into a
5964
// LocalityID struct.
6065
func LocalityIDFromString(s string) (ret LocalityID, _ error) {

0 commit comments

Comments
 (0)