Skip to content

Commit

Permalink
outlierdetection: Support health listener for ejection updates (#7908)
Browse files Browse the repository at this point in the history
  • Loading branch information
arjan-bal authored Dec 23, 2024
1 parent bce0535 commit 10c7e13
Show file tree
Hide file tree
Showing 8 changed files with 487 additions and 169 deletions.
8 changes: 1 addition & 7 deletions balancer/endpointsharding/endpointsharding.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,9 @@ import (

"google.golang.org/grpc/balancer"
"google.golang.org/grpc/balancer/base"
"google.golang.org/grpc/balancer/pickfirst"
"google.golang.org/grpc/balancer/pickfirst/pickfirstleaf"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/internal/balancer/gracefulswitch"
"google.golang.org/grpc/internal/envconfig"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/serviceconfig"
)
Expand All @@ -48,11 +46,7 @@ import (
var PickFirstConfig string

func init() {
name := pickfirst.Name
if !envconfig.NewPickFirstEnabled {
name = pickfirstleaf.Name
}
PickFirstConfig = fmt.Sprintf("[{%q: {}}]", name)
PickFirstConfig = fmt.Sprintf("[{%q: {}}]", pickfirstleaf.Name)
}

// ChildState is the balancer state of a child along with the endpoint which
Expand Down
27 changes: 24 additions & 3 deletions balancer/pickfirst/pickfirstleaf/pickfirstleaf.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,18 @@ func init() {
balancer.Register(pickfirstBuilder{})
}

// enableHealthListenerKeyType is a unique key type used in resolver attributes
// to indicate whether the health listener usage is enabled.
type enableHealthListenerKeyType struct{}
type (
// enableHealthListenerKeyType is a unique key type used in resolver
// attributes to indicate whether the health listener usage is enabled.
enableHealthListenerKeyType struct{}
// managedByPickfirstKeyType is an attribute key type to inform Outlier
// Detection that the generic health listener is being used.
// TODO: https://github.com/grpc/grpc-go/issues/7915 - Remove this when
// implementing the dualstack design. This is a hack. Once Dualstack is
// completed, outlier detection will stop sending ejection updates through
// the connectivity listener.
managedByPickfirstKeyType struct{}
)

var (
logger = grpclog.Component("pick-first-leaf-lb")
Expand Down Expand Up @@ -140,6 +149,17 @@ func EnableHealthListener(state resolver.State) resolver.State {
return state
}

// IsManagedByPickfirst returns whether an address belongs to a SubConn
// managed by the pickfirst LB policy.
// TODO: https://github.com/grpc/grpc-go/issues/7915 - This is a hack to disable
// outlier_detection via the with connectivity listener when using pick_first.
// Once Dualstack changes are complete, all SubConns will be created by
// pick_first and outlier detection will only use the health listener for
// ejection. This hack can then be removed.
func IsManagedByPickfirst(addr resolver.Address) bool {
return addr.BalancerAttributes.Value(managedByPickfirstKeyType{}) != nil
}

type pfConfig struct {
serviceconfig.LoadBalancingConfig `json:"-"`

Expand All @@ -166,6 +186,7 @@ type scData struct {
}

func (b *pickfirstBalancer) newSCData(addr resolver.Address) (*scData, error) {
addr.BalancerAttributes = addr.BalancerAttributes.WithValue(managedByPickfirstKeyType{}, true)
sd := &scData{
rawConnectivityState: connectivity.Idle,
effectiveState: connectivity.Idle,
Expand Down
52 changes: 29 additions & 23 deletions balancer/pickfirst/pickfirstleaf/pickfirstleaf_ext_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,13 @@ const (
stateStoringBalancerName = "state_storing"
)

var stateStoringServiceConfig = fmt.Sprintf(`{"loadBalancingConfig": [{"%s":{}}]}`, stateStoringBalancerName)
var (
stateStoringServiceConfig = fmt.Sprintf(`{"loadBalancingConfig": [{"%s":{}}]}`, stateStoringBalancerName)
ignoreBalAttributesOpt = cmp.Transformer("IgnoreBalancerAttributes", func(a resolver.Address) resolver.Address {
a.BalancerAttributes = nil
return a
})
)

type s struct {
grpctest.Tester
Expand Down Expand Up @@ -177,7 +183,7 @@ func (s) TestPickFirstLeaf_SimpleResolverUpdate_FirstServerReady(t *testing.T) {
wantSCStates := []scState{
{Addrs: []resolver.Address{addrs[0]}, State: connectivity.Ready},
}
if diff := cmp.Diff(wantSCStates, bal.subConnStates()); diff != "" {
if diff := cmp.Diff(wantSCStates, bal.subConnStates(), ignoreBalAttributesOpt); diff != "" {
t.Errorf("SubConn states mismatch (-want +got):\n%s", diff)
}

Expand Down Expand Up @@ -219,7 +225,7 @@ func (s) TestPickFirstLeaf_SimpleResolverUpdate_FirstServerUnReady(t *testing.T)
{Addrs: []resolver.Address{addrs[0]}, State: connectivity.Shutdown},
{Addrs: []resolver.Address{addrs[1]}, State: connectivity.Ready},
}
if diff := cmp.Diff(wantSCStates, bal.subConnStates()); diff != "" {
if diff := cmp.Diff(wantSCStates, bal.subConnStates(), ignoreBalAttributesOpt); diff != "" {
t.Errorf("SubConn states mismatch (-want +got):\n%s", diff)
}

Expand Down Expand Up @@ -264,7 +270,7 @@ func (s) TestPickFirstLeaf_SimpleResolverUpdate_DuplicateAddrs(t *testing.T) {
{Addrs: []resolver.Address{addrs[0]}, State: connectivity.Shutdown},
{Addrs: []resolver.Address{addrs[1]}, State: connectivity.Ready},
}
if diff := cmp.Diff(wantSCStates, bal.subConnStates()); diff != "" {
if diff := cmp.Diff(wantSCStates, bal.subConnStates(), ignoreBalAttributesOpt); diff != "" {
t.Errorf("SubConn states mismatch (-want +got):\n%s", diff)
}

Expand Down Expand Up @@ -317,7 +323,7 @@ func (s) TestPickFirstLeaf_ResolverUpdates_DisjointLists(t *testing.T) {
{Addrs: []resolver.Address{addrs[1]}, State: connectivity.Ready},
}

if diff := cmp.Diff(wantSCStates, bal.subConnStates()); diff != "" {
if diff := cmp.Diff(wantSCStates, bal.subConnStates(), ignoreBalAttributesOpt); diff != "" {
t.Errorf("SubConn states mismatch (-want +got):\n%s", diff)
}

Expand All @@ -334,7 +340,7 @@ func (s) TestPickFirstLeaf_ResolverUpdates_DisjointLists(t *testing.T) {
{Addrs: []resolver.Address{addrs[3]}, State: connectivity.Ready},
}

if diff := cmp.Diff(wantSCStates, bal.subConnStates()); diff != "" {
if diff := cmp.Diff(wantSCStates, bal.subConnStates(), ignoreBalAttributesOpt); diff != "" {
t.Errorf("SubConn states mismatch (-want +got):\n%s", diff)
}

Expand Down Expand Up @@ -378,7 +384,7 @@ func (s) TestPickFirstLeaf_ResolverUpdates_ActiveBackendInUpdatedList(t *testing
{Addrs: []resolver.Address{addrs[1]}, State: connectivity.Ready},
}

if diff := cmp.Diff(wantSCStates, bal.subConnStates()); diff != "" {
if diff := cmp.Diff(wantSCStates, bal.subConnStates(), ignoreBalAttributesOpt); diff != "" {
t.Errorf("SubConn states mismatch (-want +got):\n%s", diff)
}

Expand All @@ -398,7 +404,7 @@ func (s) TestPickFirstLeaf_ResolverUpdates_ActiveBackendInUpdatedList(t *testing
{Addrs: []resolver.Address{addrs[1]}, State: connectivity.Ready},
}

if diff := cmp.Diff(wantSCStates, bal.subConnStates()); diff != "" {
if diff := cmp.Diff(wantSCStates, bal.subConnStates(), ignoreBalAttributesOpt); diff != "" {
t.Errorf("SubConn states mismatch (-want +got):\n%s", diff)
}

Expand Down Expand Up @@ -440,7 +446,7 @@ func (s) TestPickFirstLeaf_ResolverUpdates_InActiveBackendInUpdatedList(t *testi
{Addrs: []resolver.Address{addrs[1]}, State: connectivity.Ready},
}

if diff := cmp.Diff(wantSCStates, bal.subConnStates()); diff != "" {
if diff := cmp.Diff(wantSCStates, bal.subConnStates(), ignoreBalAttributesOpt); diff != "" {
t.Errorf("SubConn states mismatch (-want +got):\n%s", diff)
}

Expand All @@ -458,7 +464,7 @@ func (s) TestPickFirstLeaf_ResolverUpdates_InActiveBackendInUpdatedList(t *testi
{Addrs: []resolver.Address{addrs[0]}, State: connectivity.Ready},
}

if diff := cmp.Diff(wantSCStates, bal.subConnStates()); diff != "" {
if diff := cmp.Diff(wantSCStates, bal.subConnStates(), ignoreBalAttributesOpt); diff != "" {
t.Errorf("SubConn states mismatch (-want +got):\n%s", diff)
}

Expand Down Expand Up @@ -502,7 +508,7 @@ func (s) TestPickFirstLeaf_ResolverUpdates_IdenticalLists(t *testing.T) {
{Addrs: []resolver.Address{addrs[1]}, State: connectivity.Ready},
}

if diff := cmp.Diff(wantSCStates, bal.subConnStates()); diff != "" {
if diff := cmp.Diff(wantSCStates, bal.subConnStates(), ignoreBalAttributesOpt); diff != "" {
t.Errorf("SubConn states mismatch (-want +got):\n%s", diff)
}

Expand All @@ -521,7 +527,7 @@ func (s) TestPickFirstLeaf_ResolverUpdates_IdenticalLists(t *testing.T) {
{Addrs: []resolver.Address{addrs[1]}, State: connectivity.Ready},
}

if diff := cmp.Diff(wantSCStates, bal.subConnStates()); diff != "" {
if diff := cmp.Diff(wantSCStates, bal.subConnStates(), ignoreBalAttributesOpt); diff != "" {
t.Errorf("SubConn states mismatch (-want +got):\n%s", diff)
}

Expand Down Expand Up @@ -576,7 +582,7 @@ func (s) TestPickFirstLeaf_StopConnectedServer_FirstServerRestart(t *testing.T)
{Addrs: []resolver.Address{addrs[0]}, State: connectivity.Ready},
}

if diff := cmp.Diff(wantSCStates, bal.subConnStates()); diff != "" {
if diff := cmp.Diff(wantSCStates, bal.subConnStates(), ignoreBalAttributesOpt); diff != "" {
t.Errorf("SubConn states mismatch (-want +got):\n%s", diff)
}

Expand All @@ -591,7 +597,7 @@ func (s) TestPickFirstLeaf_StopConnectedServer_FirstServerRestart(t *testing.T)
t.Fatal(err)
}

if diff := cmp.Diff(wantSCStates, bal.subConnStates()); diff != "" {
if diff := cmp.Diff(wantSCStates, bal.subConnStates(), ignoreBalAttributesOpt); diff != "" {
t.Errorf("SubConn states mismatch (-want +got):\n%s", diff)
}

Expand Down Expand Up @@ -639,7 +645,7 @@ func (s) TestPickFirstLeaf_StopConnectedServer_SecondServerRestart(t *testing.T)
{Addrs: []resolver.Address{addrs[1]}, State: connectivity.Ready},
}

if diff := cmp.Diff(wantSCStates, bal.subConnStates()); diff != "" {
if diff := cmp.Diff(wantSCStates, bal.subConnStates(), ignoreBalAttributesOpt); diff != "" {
t.Errorf("SubConn states mismatch (-want +got):\n%s", diff)
}

Expand All @@ -660,7 +666,7 @@ func (s) TestPickFirstLeaf_StopConnectedServer_SecondServerRestart(t *testing.T)
{Addrs: []resolver.Address{addrs[0]}, State: connectivity.Shutdown},
}

if diff := cmp.Diff(wantSCStates, bal.subConnStates()); diff != "" {
if diff := cmp.Diff(wantSCStates, bal.subConnStates(), ignoreBalAttributesOpt); diff != "" {
t.Errorf("SubConn states mismatch (-want +got):\n%s", diff)
}

Expand Down Expand Up @@ -708,7 +714,7 @@ func (s) TestPickFirstLeaf_StopConnectedServer_SecondServerToFirst(t *testing.T)
{Addrs: []resolver.Address{addrs[1]}, State: connectivity.Ready},
}

if diff := cmp.Diff(wantSCStates, bal.subConnStates()); diff != "" {
if diff := cmp.Diff(wantSCStates, bal.subConnStates(), ignoreBalAttributesOpt); diff != "" {
t.Errorf("SubConn states mismatch (-want +got):\n%s", diff)
}

Expand All @@ -729,7 +735,7 @@ func (s) TestPickFirstLeaf_StopConnectedServer_SecondServerToFirst(t *testing.T)
{Addrs: []resolver.Address{addrs[0]}, State: connectivity.Ready},
}

if diff := cmp.Diff(wantSCStates, bal.subConnStates()); diff != "" {
if diff := cmp.Diff(wantSCStates, bal.subConnStates(), ignoreBalAttributesOpt); diff != "" {
t.Errorf("SubConn states mismatch (-want +got):\n%s", diff)
}

Expand Down Expand Up @@ -776,7 +782,7 @@ func (s) TestPickFirstLeaf_StopConnectedServer_FirstServerToSecond(t *testing.T)
{Addrs: []resolver.Address{addrs[0]}, State: connectivity.Ready},
}

if diff := cmp.Diff(wantSCStates, bal.subConnStates()); diff != "" {
if diff := cmp.Diff(wantSCStates, bal.subConnStates(), ignoreBalAttributesOpt); diff != "" {
t.Errorf("SubConn states mismatch (-want +got):\n%s", diff)
}

Expand All @@ -796,7 +802,7 @@ func (s) TestPickFirstLeaf_StopConnectedServer_FirstServerToSecond(t *testing.T)
{Addrs: []resolver.Address{addrs[1]}, State: connectivity.Ready},
}

if diff := cmp.Diff(wantSCStates, bal.subConnStates()); diff != "" {
if diff := cmp.Diff(wantSCStates, bal.subConnStates(), ignoreBalAttributesOpt); diff != "" {
t.Errorf("SubConn states mismatch (-want +got):\n%s", diff)
}

Expand Down Expand Up @@ -1130,7 +1136,7 @@ func (s) TestPickFirstLeaf_InterleavingIPV4Preffered(t *testing.T) {
if err != nil {
t.Fatalf("%v", err)
}
if diff := cmp.Diff(wantAddrs, gotAddrs); diff != "" {
if diff := cmp.Diff(wantAddrs, gotAddrs, ignoreBalAttributesOpt); diff != "" {
t.Errorf("SubConn creation order mismatch (-want +got):\n%s", diff)
}
}
Expand Down Expand Up @@ -1174,7 +1180,7 @@ func (s) TestPickFirstLeaf_InterleavingIPv6Preffered(t *testing.T) {
if err != nil {
t.Fatalf("%v", err)
}
if diff := cmp.Diff(wantAddrs, gotAddrs); diff != "" {
if diff := cmp.Diff(wantAddrs, gotAddrs, ignoreBalAttributesOpt); diff != "" {
t.Errorf("SubConn creation order mismatch (-want +got):\n%s", diff)
}
}
Expand Down Expand Up @@ -1220,7 +1226,7 @@ func (s) TestPickFirstLeaf_InterleavingUnknownPreffered(t *testing.T) {
if err != nil {
t.Fatalf("%v", err)
}
if diff := cmp.Diff(wantAddrs, gotAddrs); diff != "" {
if diff := cmp.Diff(wantAddrs, gotAddrs, ignoreBalAttributesOpt); diff != "" {
t.Errorf("SubConn creation order mismatch (-want +got):\n%s", diff)
}
}
Expand Down
8 changes: 7 additions & 1 deletion balancer/weightedroundrobin/balancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (

"google.golang.org/grpc/balancer"
"google.golang.org/grpc/balancer/endpointsharding"
"google.golang.org/grpc/balancer/pickfirst/pickfirstleaf"
"google.golang.org/grpc/balancer/weightedroundrobin/internal"
"google.golang.org/grpc/balancer/weightedtarget"
"google.golang.org/grpc/connectivity"
Expand Down Expand Up @@ -218,7 +219,9 @@ type wrrBalancer struct {
}

func (b *wrrBalancer) UpdateClientConnState(ccs balancer.ClientConnState) error {
b.logger.Infof("UpdateCCS: %v", ccs)
if b.logger.V(2) {
b.logger.Infof("UpdateCCS: %v", ccs)
}
cfg, ok := ccs.BalancerConfig.(*lbConfig)
if !ok {
return fmt.Errorf("wrr: received nil or illegal BalancerConfig (type %T): %v", ccs.BalancerConfig, ccs.BalancerConfig)
Expand All @@ -232,6 +235,9 @@ func (b *wrrBalancer) UpdateClientConnState(ccs balancer.ClientConnState) error
b.updateEndpointsLocked(ccs.ResolverState.Endpoints)
b.mu.Unlock()

// Make pickfirst children use health listeners for outlier detection to
// work.
ccs.ResolverState = pickfirstleaf.EnableHealthListener(ccs.ResolverState)
// This causes child to update picker inline and will thus cause inline
// picker update.
return b.child.UpdateClientConnState(balancer.ClientConnState{
Expand Down
Loading

0 comments on commit 10c7e13

Please sign in to comment.