Skip to content

Commit faf9964

Browse files
authored
gracefulswitch: add ParseConfig and make UpdateClientConnState call SwitchTo if needed (#7035)
1 parent 800a8e0 commit faf9964

11 files changed

+206
-115
lines changed

balancer/balancer.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,14 @@ var (
5454
// an init() function), and is not thread-safe. If multiple Balancers are
5555
// registered with the same name, the one registered last will take effect.
5656
func Register(b Builder) {
57-
if strings.ToLower(b.Name()) != b.Name() {
57+
name := strings.ToLower(b.Name())
58+
if name != b.Name() {
5859
// TODO: Skip the use of strings.ToLower() to index the map after v1.59
5960
// is released to switch to case sensitive balancer registry. Also,
6061
// remove this warning and update the docstrings for Register and Get.
6162
logger.Warningf("Balancer registered with name %q. grpc-go will be switching to case sensitive balancer registries soon", b.Name())
6263
}
63-
m[strings.ToLower(b.Name())] = b
64+
m[name] = b
6465
}
6566

6667
// unregisterForTesting deletes the balancer with the given name from the

balancer/rls/config_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ func (s) TestParseConfigErrors(t *testing.T) {
322322
"childPolicy": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}],
323323
"childPolicyConfigTargetFieldName": "serviceName"
324324
}`),
325-
wantErr: "invalid loadBalancingConfig: no supported policies found",
325+
wantErr: "no supported policies found in config",
326326
},
327327
{
328328
desc: "no child policy",

balancer_wrapper.go

+7-50
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ package grpc
2121
import (
2222
"context"
2323
"fmt"
24-
"strings"
2524
"sync"
2625

2726
"google.golang.org/grpc/balancer"
@@ -66,7 +65,8 @@ type ccBalancerWrapper struct {
6665
}
6766

6867
// newCCBalancerWrapper creates a new balancer wrapper in idle state. The
69-
// underlying balancer is not created until the switchTo() method is invoked.
68+
// underlying balancer is not created until the updateClientConnState() method
69+
// is invoked.
7070
func newCCBalancerWrapper(cc *ClientConn) *ccBalancerWrapper {
7171
ctx, cancel := context.WithCancel(cc.ctx)
7272
ccb := &ccBalancerWrapper{
@@ -97,6 +97,11 @@ func (ccb *ccBalancerWrapper) updateClientConnState(ccs *balancer.ClientConnStat
9797
if ctx.Err() != nil || ccb.balancer == nil {
9898
return
9999
}
100+
name := gracefulswitch.ChildName(ccs.BalancerConfig)
101+
if ccb.curBalancerName != name {
102+
ccb.curBalancerName = name
103+
channelz.Infof(logger, ccb.cc.channelz, "Channel switches to new LB policy %q", name)
104+
}
100105
err := ccb.balancer.UpdateClientConnState(*ccs)
101106
if logger.V(2) && err != nil {
102107
logger.Infof("error from balancer.UpdateClientConnState: %v", err)
@@ -120,54 +125,6 @@ func (ccb *ccBalancerWrapper) resolverError(err error) {
120125
})
121126
}
122127

123-
// switchTo is invoked by grpc to instruct the balancer wrapper to switch to the
124-
// LB policy identified by name.
125-
//
126-
// ClientConn calls newCCBalancerWrapper() at creation time. Upon receipt of the
127-
// first good update from the name resolver, it determines the LB policy to use
128-
// and invokes the switchTo() method. Upon receipt of every subsequent update
129-
// from the name resolver, it invokes this method.
130-
//
131-
// the ccBalancerWrapper keeps track of the current LB policy name, and skips
132-
// the graceful balancer switching process if the name does not change.
133-
func (ccb *ccBalancerWrapper) switchTo(name string) {
134-
ccb.serializer.Schedule(func(ctx context.Context) {
135-
if ctx.Err() != nil || ccb.balancer == nil {
136-
return
137-
}
138-
// TODO: Other languages use case-sensitive balancer registries. We should
139-
// switch as well. See: https://github.com/grpc/grpc-go/issues/5288.
140-
if strings.EqualFold(ccb.curBalancerName, name) {
141-
return
142-
}
143-
ccb.buildLoadBalancingPolicy(name)
144-
})
145-
}
146-
147-
// buildLoadBalancingPolicy performs the following:
148-
// - retrieve a balancer builder for the given name. Use the default LB
149-
// policy, pick_first, if no LB policy with name is found in the registry.
150-
// - instruct the gracefulswitch balancer to switch to the above builder. This
151-
// will actually build the new balancer.
152-
// - update the `curBalancerName` field
153-
//
154-
// Must be called from a serializer callback.
155-
func (ccb *ccBalancerWrapper) buildLoadBalancingPolicy(name string) {
156-
builder := balancer.Get(name)
157-
if builder == nil {
158-
channelz.Warningf(logger, ccb.cc.channelz, "Channel switches to new LB policy %q, since the specified LB policy %q was not registered", PickFirstBalancerName, name)
159-
builder = newPickfirstBuilder()
160-
} else {
161-
channelz.Infof(logger, ccb.cc.channelz, "Channel switches to new LB policy %q", name)
162-
}
163-
164-
if err := ccb.balancer.SwitchTo(builder); err != nil {
165-
channelz.Errorf(logger, ccb.cc.channelz, "Channel failed to build new LB policy %q: %v", name, err)
166-
return
167-
}
168-
ccb.curBalancerName = builder.Name()
169-
}
170-
171128
// close initiates async shutdown of the wrapper. cc.mu must be held when
172129
// calling this function. To determine the wrapper has finished shutting down,
173130
// the channel should block on ccb.serializer.Done() without cc.mu held.

clientconn.go

+2-12
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,7 @@ func (cc *ClientConn) waitForResolvedAddrs(ctx context.Context) error {
692692
var emptyServiceConfig *ServiceConfig
693693

694694
func init() {
695+
balancer.Register(pickfirstBuilder{})
695696
cfg := parseServiceConfig("{}")
696697
if cfg.Err != nil {
697698
panic(fmt.Sprintf("impossible error parsing empty service config: %v", cfg.Err))
@@ -777,7 +778,7 @@ func (cc *ClientConn) updateResolverStateAndUnlock(s resolver.State, err error)
777778

778779
var balCfg serviceconfig.LoadBalancingConfig
779780
if cc.sc != nil && cc.sc.lbConfig != nil {
780-
balCfg = cc.sc.lbConfig.cfg
781+
balCfg = cc.sc.lbConfig
781782
}
782783
bw := cc.balancerWrapper
783784
cc.mu.Unlock()
@@ -1074,17 +1075,6 @@ func (cc *ClientConn) applyServiceConfigAndBalancer(sc *ServiceConfig, configSel
10741075
} else {
10751076
cc.retryThrottler.Store((*retryThrottler)(nil))
10761077
}
1077-
1078-
var newBalancerName string
1079-
if cc.sc == nil || (cc.sc.lbConfig == nil && cc.sc.LB == nil) {
1080-
// No service config or no LB policy specified in config.
1081-
newBalancerName = PickFirstBalancerName
1082-
} else if cc.sc.lbConfig != nil {
1083-
newBalancerName = cc.sc.lbConfig.name
1084-
} else { // cc.sc.LB != nil
1085-
newBalancerName = *cc.sc.LB
1086-
}
1087-
cc.balancerWrapper.switchTo(newBalancerName)
10881078
}
10891079

10901080
func (cc *ClientConn) resolveNow(o resolver.ResolveNowOptions) {
+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
*
3+
* Copyright 2024 gRPC authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package gracefulswitch
20+
21+
import (
22+
"encoding/json"
23+
"fmt"
24+
25+
"google.golang.org/grpc/balancer"
26+
"google.golang.org/grpc/serviceconfig"
27+
)
28+
29+
type lbConfig struct {
30+
serviceconfig.LoadBalancingConfig
31+
32+
childBuilder balancer.Builder
33+
childConfig serviceconfig.LoadBalancingConfig
34+
}
35+
36+
func ChildName(l serviceconfig.LoadBalancingConfig) string {
37+
return l.(*lbConfig).childBuilder.Name()
38+
}
39+
40+
// ParseConfig parses a child config list and returns a LB config for the
41+
// gracefulswitch Balancer.
42+
//
43+
// cfg is expected to be a json.RawMessage containing a JSON array of LB policy
44+
// names + configs as the format of the "loadBalancingConfig" field in
45+
// ServiceConfig. It returns a type that should be passed to
46+
// UpdateClientConnState in the BalancerConfig field.
47+
func ParseConfig(cfg json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
48+
var lbCfg []map[string]json.RawMessage
49+
if err := json.Unmarshal(cfg, &lbCfg); err != nil {
50+
return nil, err
51+
}
52+
for i, e := range lbCfg {
53+
if len(e) != 1 {
54+
return nil, fmt.Errorf("expected a JSON struct with one entry; received entry %v at index %d", e, i)
55+
}
56+
57+
var name string
58+
var jsonCfg json.RawMessage
59+
for name, jsonCfg = range e {
60+
}
61+
62+
builder := balancer.Get(name)
63+
if builder == nil {
64+
// Skip unregistered balancer names.
65+
continue
66+
}
67+
68+
parser, ok := builder.(balancer.ConfigParser)
69+
if !ok {
70+
// This is a valid child with no config.
71+
return &lbConfig{childBuilder: builder}, nil
72+
}
73+
74+
cfg, err := parser.ParseConfig(jsonCfg)
75+
if err != nil {
76+
return nil, fmt.Errorf("error parsing config for policy %q: %v", name, err)
77+
}
78+
79+
return &lbConfig{childBuilder: builder, childConfig: cfg}, nil
80+
}
81+
82+
return nil, fmt.Errorf("no supported policies found in config: %v", string(cfg))
83+
}

internal/balancer/gracefulswitch/gracefulswitch.go

+40-5
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,23 @@ func (gsb *Balancer) balancerCurrentOrPending(bw *balancerWrapper) bool {
9494
// process is not complete when this method returns. This method must be called
9595
// synchronously alongside the rest of the balancer.Balancer methods this
9696
// Graceful Switch Balancer implements.
97+
//
98+
// Deprecated: use ParseConfig and pass a parsed config to UpdateClientConnState
99+
// to cause the Balancer to automatically change to the new child when necessary.
97100
func (gsb *Balancer) SwitchTo(builder balancer.Builder) error {
101+
_, err := gsb.switchTo(builder)
102+
return err
103+
}
104+
105+
func (gsb *Balancer) switchTo(builder balancer.Builder) (*balancerWrapper, error) {
98106
gsb.mu.Lock()
99107
if gsb.closed {
100108
gsb.mu.Unlock()
101-
return errBalancerClosed
109+
return nil, errBalancerClosed
102110
}
103111
bw := &balancerWrapper{
104-
gsb: gsb,
112+
builder: builder,
113+
gsb: gsb,
105114
lastState: balancer.State{
106115
ConnectivityState: connectivity.Connecting,
107116
Picker: base.NewErrPicker(balancer.ErrNoSubConnAvailable),
@@ -129,7 +138,7 @@ func (gsb *Balancer) SwitchTo(builder balancer.Builder) error {
129138
gsb.balancerCurrent = nil
130139
}
131140
gsb.mu.Unlock()
132-
return balancer.ErrBadResolverState
141+
return nil, balancer.ErrBadResolverState
133142
}
134143

135144
// This write doesn't need to take gsb.mu because this field never gets read
@@ -138,7 +147,7 @@ func (gsb *Balancer) SwitchTo(builder balancer.Builder) error {
138147
// bw.Balancer field will never be forwarded to until this SwitchTo()
139148
// function returns.
140149
bw.Balancer = newBalancer
141-
return nil
150+
return bw, nil
142151
}
143152

144153
// Returns nil if the graceful switch balancer is closed.
@@ -152,12 +161,33 @@ func (gsb *Balancer) latestBalancer() *balancerWrapper {
152161
}
153162

154163
// UpdateClientConnState forwards the update to the latest balancer created.
164+
//
165+
// If the state's BalancerConfig is the config returned by a call to
166+
// gracefulswitch.ParseConfig, then this function will automatically SwitchTo
167+
// the balancer indicated by the config before forwarding its config to it, if
168+
// necessary.
155169
func (gsb *Balancer) UpdateClientConnState(state balancer.ClientConnState) error {
156170
// The resolver data is only relevant to the most recent LB Policy.
157171
balToUpdate := gsb.latestBalancer()
172+
173+
gsbCfg, ok := state.BalancerConfig.(*lbConfig)
174+
if ok {
175+
// Switch to the child in the config unless it is already active.
176+
if balToUpdate == nil || gsbCfg.childBuilder.Name() != balToUpdate.builder.Name() {
177+
var err error
178+
balToUpdate, err = gsb.switchTo(gsbCfg.childBuilder)
179+
if err != nil {
180+
return fmt.Errorf("could not switch to new child balancer: %w", err)
181+
}
182+
}
183+
// Unwrap the child balancer's config.
184+
state.BalancerConfig = gsbCfg.childConfig
185+
}
186+
158187
if balToUpdate == nil {
159188
return errBalancerClosed
160189
}
190+
161191
// Perform this call without gsb.mu to prevent deadlocks if the child calls
162192
// back into the channel. The latest balancer can never be closed during a
163193
// call from the channel, even without gsb.mu held.
@@ -169,6 +199,10 @@ func (gsb *Balancer) ResolverError(err error) {
169199
// The resolver data is only relevant to the most recent LB Policy.
170200
balToUpdate := gsb.latestBalancer()
171201
if balToUpdate == nil {
202+
gsb.cc.UpdateState(balancer.State{
203+
ConnectivityState: connectivity.TransientFailure,
204+
Picker: base.NewErrPicker(err),
205+
})
172206
return
173207
}
174208
// Perform this call without gsb.mu to prevent deadlocks if the child calls
@@ -261,7 +295,8 @@ func (gsb *Balancer) Close() {
261295
// graceful switch logic.
262296
type balancerWrapper struct {
263297
balancer.Balancer
264-
gsb *Balancer
298+
gsb *Balancer
299+
builder balancer.Builder
265300

266301
lastState balancer.State
267302
subconns map[balancer.SubConn]bool // subconns created by this balancer

pickfirst.go

+3-11
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,15 @@ const (
3838
logPrefix = "[pick-first-lb %p] "
3939
)
4040

41-
func newPickfirstBuilder() balancer.Builder {
42-
return &pickfirstBuilder{}
43-
}
44-
4541
type pickfirstBuilder struct{}
4642

47-
func (*pickfirstBuilder) Build(cc balancer.ClientConn, opt balancer.BuildOptions) balancer.Balancer {
43+
func (pickfirstBuilder) Build(cc balancer.ClientConn, opt balancer.BuildOptions) balancer.Balancer {
4844
b := &pickfirstBalancer{cc: cc}
4945
b.logger = internalgrpclog.NewPrefixLogger(logger, fmt.Sprintf(logPrefix, b))
5046
return b
5147
}
5248

53-
func (*pickfirstBuilder) Name() string {
49+
func (pickfirstBuilder) Name() string {
5450
return PickFirstBalancerName
5551
}
5652

@@ -63,7 +59,7 @@ type pfConfig struct {
6359
ShuffleAddressList bool `json:"shuffleAddressList"`
6460
}
6561

66-
func (*pickfirstBuilder) ParseConfig(js json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
62+
func (pickfirstBuilder) ParseConfig(js json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
6763
var cfg pfConfig
6864
if err := json.Unmarshal(js, &cfg); err != nil {
6965
return nil, fmt.Errorf("pickfirst: unable to unmarshal LB policy config: %s, error: %v", string(js), err)
@@ -243,7 +239,3 @@ func (i *idlePicker) Pick(balancer.PickInfo) (balancer.PickResult, error) {
243239
i.subConn.Connect()
244240
return balancer.PickResult{}, balancer.ErrNoSubConnAvailable
245241
}
246-
247-
func init() {
248-
balancer.Register(newPickfirstBuilder())
249-
}

0 commit comments

Comments
 (0)