mirror of https://github.com/grpc/grpc-go.git
gracefulswitch: add ParseConfig and make UpdateClientConnState call SwitchTo if needed (#7035)
This commit is contained in:
parent
800a8e02b5
commit
faf9964afe
|
|
@ -54,13 +54,14 @@ var (
|
||||||
// an init() function), and is not thread-safe. If multiple Balancers are
|
// an init() function), and is not thread-safe. If multiple Balancers are
|
||||||
// registered with the same name, the one registered last will take effect.
|
// registered with the same name, the one registered last will take effect.
|
||||||
func Register(b Builder) {
|
func Register(b Builder) {
|
||||||
if strings.ToLower(b.Name()) != b.Name() {
|
name := strings.ToLower(b.Name())
|
||||||
|
if name != b.Name() {
|
||||||
// TODO: Skip the use of strings.ToLower() to index the map after v1.59
|
// TODO: Skip the use of strings.ToLower() to index the map after v1.59
|
||||||
// is released to switch to case sensitive balancer registry. Also,
|
// is released to switch to case sensitive balancer registry. Also,
|
||||||
// remove this warning and update the docstrings for Register and Get.
|
// remove this warning and update the docstrings for Register and Get.
|
||||||
logger.Warningf("Balancer registered with name %q. grpc-go will be switching to case sensitive balancer registries soon", b.Name())
|
logger.Warningf("Balancer registered with name %q. grpc-go will be switching to case sensitive balancer registries soon", b.Name())
|
||||||
}
|
}
|
||||||
m[strings.ToLower(b.Name())] = b
|
m[name] = b
|
||||||
}
|
}
|
||||||
|
|
||||||
// unregisterForTesting deletes the balancer with the given name from the
|
// unregisterForTesting deletes the balancer with the given name from the
|
||||||
|
|
|
||||||
|
|
@ -322,7 +322,7 @@ func (s) TestParseConfigErrors(t *testing.T) {
|
||||||
"childPolicy": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}],
|
"childPolicy": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}],
|
||||||
"childPolicyConfigTargetFieldName": "serviceName"
|
"childPolicyConfigTargetFieldName": "serviceName"
|
||||||
}`),
|
}`),
|
||||||
wantErr: "invalid loadBalancingConfig: no supported policies found",
|
wantErr: "no supported policies found in config",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "no child policy",
|
desc: "no child policy",
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ package grpc
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"google.golang.org/grpc/balancer"
|
"google.golang.org/grpc/balancer"
|
||||||
|
|
@ -66,7 +65,8 @@ type ccBalancerWrapper struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// newCCBalancerWrapper creates a new balancer wrapper in idle state. The
|
// newCCBalancerWrapper creates a new balancer wrapper in idle state. The
|
||||||
// underlying balancer is not created until the switchTo() method is invoked.
|
// underlying balancer is not created until the updateClientConnState() method
|
||||||
|
// is invoked.
|
||||||
func newCCBalancerWrapper(cc *ClientConn) *ccBalancerWrapper {
|
func newCCBalancerWrapper(cc *ClientConn) *ccBalancerWrapper {
|
||||||
ctx, cancel := context.WithCancel(cc.ctx)
|
ctx, cancel := context.WithCancel(cc.ctx)
|
||||||
ccb := &ccBalancerWrapper{
|
ccb := &ccBalancerWrapper{
|
||||||
|
|
@ -97,6 +97,11 @@ func (ccb *ccBalancerWrapper) updateClientConnState(ccs *balancer.ClientConnStat
|
||||||
if ctx.Err() != nil || ccb.balancer == nil {
|
if ctx.Err() != nil || ccb.balancer == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
name := gracefulswitch.ChildName(ccs.BalancerConfig)
|
||||||
|
if ccb.curBalancerName != name {
|
||||||
|
ccb.curBalancerName = name
|
||||||
|
channelz.Infof(logger, ccb.cc.channelz, "Channel switches to new LB policy %q", name)
|
||||||
|
}
|
||||||
err := ccb.balancer.UpdateClientConnState(*ccs)
|
err := ccb.balancer.UpdateClientConnState(*ccs)
|
||||||
if logger.V(2) && err != nil {
|
if logger.V(2) && err != nil {
|
||||||
logger.Infof("error from balancer.UpdateClientConnState: %v", err)
|
logger.Infof("error from balancer.UpdateClientConnState: %v", err)
|
||||||
|
|
@ -120,54 +125,6 @@ func (ccb *ccBalancerWrapper) resolverError(err error) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// switchTo is invoked by grpc to instruct the balancer wrapper to switch to the
|
|
||||||
// LB policy identified by name.
|
|
||||||
//
|
|
||||||
// ClientConn calls newCCBalancerWrapper() at creation time. Upon receipt of the
|
|
||||||
// first good update from the name resolver, it determines the LB policy to use
|
|
||||||
// and invokes the switchTo() method. Upon receipt of every subsequent update
|
|
||||||
// from the name resolver, it invokes this method.
|
|
||||||
//
|
|
||||||
// the ccBalancerWrapper keeps track of the current LB policy name, and skips
|
|
||||||
// the graceful balancer switching process if the name does not change.
|
|
||||||
func (ccb *ccBalancerWrapper) switchTo(name string) {
|
|
||||||
ccb.serializer.Schedule(func(ctx context.Context) {
|
|
||||||
if ctx.Err() != nil || ccb.balancer == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// TODO: Other languages use case-sensitive balancer registries. We should
|
|
||||||
// switch as well. See: https://github.com/grpc/grpc-go/issues/5288.
|
|
||||||
if strings.EqualFold(ccb.curBalancerName, name) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ccb.buildLoadBalancingPolicy(name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildLoadBalancingPolicy performs the following:
|
|
||||||
// - retrieve a balancer builder for the given name. Use the default LB
|
|
||||||
// policy, pick_first, if no LB policy with name is found in the registry.
|
|
||||||
// - instruct the gracefulswitch balancer to switch to the above builder. This
|
|
||||||
// will actually build the new balancer.
|
|
||||||
// - update the `curBalancerName` field
|
|
||||||
//
|
|
||||||
// Must be called from a serializer callback.
|
|
||||||
func (ccb *ccBalancerWrapper) buildLoadBalancingPolicy(name string) {
|
|
||||||
builder := balancer.Get(name)
|
|
||||||
if builder == nil {
|
|
||||||
channelz.Warningf(logger, ccb.cc.channelz, "Channel switches to new LB policy %q, since the specified LB policy %q was not registered", PickFirstBalancerName, name)
|
|
||||||
builder = newPickfirstBuilder()
|
|
||||||
} else {
|
|
||||||
channelz.Infof(logger, ccb.cc.channelz, "Channel switches to new LB policy %q", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ccb.balancer.SwitchTo(builder); err != nil {
|
|
||||||
channelz.Errorf(logger, ccb.cc.channelz, "Channel failed to build new LB policy %q: %v", name, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ccb.curBalancerName = builder.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
// close initiates async shutdown of the wrapper. cc.mu must be held when
|
// close initiates async shutdown of the wrapper. cc.mu must be held when
|
||||||
// calling this function. To determine the wrapper has finished shutting down,
|
// calling this function. To determine the wrapper has finished shutting down,
|
||||||
// the channel should block on ccb.serializer.Done() without cc.mu held.
|
// the channel should block on ccb.serializer.Done() without cc.mu held.
|
||||||
|
|
|
||||||
|
|
@ -692,6 +692,7 @@ func (cc *ClientConn) waitForResolvedAddrs(ctx context.Context) error {
|
||||||
var emptyServiceConfig *ServiceConfig
|
var emptyServiceConfig *ServiceConfig
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
balancer.Register(pickfirstBuilder{})
|
||||||
cfg := parseServiceConfig("{}")
|
cfg := parseServiceConfig("{}")
|
||||||
if cfg.Err != nil {
|
if cfg.Err != nil {
|
||||||
panic(fmt.Sprintf("impossible error parsing empty service config: %v", cfg.Err))
|
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)
|
||||||
|
|
||||||
var balCfg serviceconfig.LoadBalancingConfig
|
var balCfg serviceconfig.LoadBalancingConfig
|
||||||
if cc.sc != nil && cc.sc.lbConfig != nil {
|
if cc.sc != nil && cc.sc.lbConfig != nil {
|
||||||
balCfg = cc.sc.lbConfig.cfg
|
balCfg = cc.sc.lbConfig
|
||||||
}
|
}
|
||||||
bw := cc.balancerWrapper
|
bw := cc.balancerWrapper
|
||||||
cc.mu.Unlock()
|
cc.mu.Unlock()
|
||||||
|
|
@ -1074,17 +1075,6 @@ func (cc *ClientConn) applyServiceConfigAndBalancer(sc *ServiceConfig, configSel
|
||||||
} else {
|
} else {
|
||||||
cc.retryThrottler.Store((*retryThrottler)(nil))
|
cc.retryThrottler.Store((*retryThrottler)(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
var newBalancerName string
|
|
||||||
if cc.sc == nil || (cc.sc.lbConfig == nil && cc.sc.LB == nil) {
|
|
||||||
// No service config or no LB policy specified in config.
|
|
||||||
newBalancerName = PickFirstBalancerName
|
|
||||||
} else if cc.sc.lbConfig != nil {
|
|
||||||
newBalancerName = cc.sc.lbConfig.name
|
|
||||||
} else { // cc.sc.LB != nil
|
|
||||||
newBalancerName = *cc.sc.LB
|
|
||||||
}
|
|
||||||
cc.balancerWrapper.switchTo(newBalancerName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *ClientConn) resolveNow(o resolver.ResolveNowOptions) {
|
func (cc *ClientConn) resolveNow(o resolver.ResolveNowOptions) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2024 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 gracefulswitch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/balancer"
|
||||||
|
"google.golang.org/grpc/serviceconfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
type lbConfig struct {
|
||||||
|
serviceconfig.LoadBalancingConfig
|
||||||
|
|
||||||
|
childBuilder balancer.Builder
|
||||||
|
childConfig serviceconfig.LoadBalancingConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChildName(l serviceconfig.LoadBalancingConfig) string {
|
||||||
|
return l.(*lbConfig).childBuilder.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseConfig parses a child config list and returns a LB config for the
|
||||||
|
// gracefulswitch Balancer.
|
||||||
|
//
|
||||||
|
// cfg is expected to be a json.RawMessage containing a JSON array of LB policy
|
||||||
|
// names + configs as the format of the "loadBalancingConfig" field in
|
||||||
|
// ServiceConfig. It returns a type that should be passed to
|
||||||
|
// UpdateClientConnState in the BalancerConfig field.
|
||||||
|
func ParseConfig(cfg json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
|
||||||
|
var lbCfg []map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(cfg, &lbCfg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i, e := range lbCfg {
|
||||||
|
if len(e) != 1 {
|
||||||
|
return nil, fmt.Errorf("expected a JSON struct with one entry; received entry %v at index %d", e, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
var name string
|
||||||
|
var jsonCfg json.RawMessage
|
||||||
|
for name, jsonCfg = range e {
|
||||||
|
}
|
||||||
|
|
||||||
|
builder := balancer.Get(name)
|
||||||
|
if builder == nil {
|
||||||
|
// Skip unregistered balancer names.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parser, ok := builder.(balancer.ConfigParser)
|
||||||
|
if !ok {
|
||||||
|
// This is a valid child with no config.
|
||||||
|
return &lbConfig{childBuilder: builder}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := parser.ParseConfig(jsonCfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing config for policy %q: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &lbConfig{childBuilder: builder, childConfig: cfg}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("no supported policies found in config: %v", string(cfg))
|
||||||
|
}
|
||||||
|
|
@ -94,14 +94,23 @@ func (gsb *Balancer) balancerCurrentOrPending(bw *balancerWrapper) bool {
|
||||||
// process is not complete when this method returns. This method must be called
|
// process is not complete when this method returns. This method must be called
|
||||||
// synchronously alongside the rest of the balancer.Balancer methods this
|
// synchronously alongside the rest of the balancer.Balancer methods this
|
||||||
// Graceful Switch Balancer implements.
|
// Graceful Switch Balancer implements.
|
||||||
|
//
|
||||||
|
// Deprecated: use ParseConfig and pass a parsed config to UpdateClientConnState
|
||||||
|
// to cause the Balancer to automatically change to the new child when necessary.
|
||||||
func (gsb *Balancer) SwitchTo(builder balancer.Builder) error {
|
func (gsb *Balancer) SwitchTo(builder balancer.Builder) error {
|
||||||
|
_, err := gsb.switchTo(builder)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gsb *Balancer) switchTo(builder balancer.Builder) (*balancerWrapper, error) {
|
||||||
gsb.mu.Lock()
|
gsb.mu.Lock()
|
||||||
if gsb.closed {
|
if gsb.closed {
|
||||||
gsb.mu.Unlock()
|
gsb.mu.Unlock()
|
||||||
return errBalancerClosed
|
return nil, errBalancerClosed
|
||||||
}
|
}
|
||||||
bw := &balancerWrapper{
|
bw := &balancerWrapper{
|
||||||
gsb: gsb,
|
builder: builder,
|
||||||
|
gsb: gsb,
|
||||||
lastState: balancer.State{
|
lastState: balancer.State{
|
||||||
ConnectivityState: connectivity.Connecting,
|
ConnectivityState: connectivity.Connecting,
|
||||||
Picker: base.NewErrPicker(balancer.ErrNoSubConnAvailable),
|
Picker: base.NewErrPicker(balancer.ErrNoSubConnAvailable),
|
||||||
|
|
@ -129,7 +138,7 @@ func (gsb *Balancer) SwitchTo(builder balancer.Builder) error {
|
||||||
gsb.balancerCurrent = nil
|
gsb.balancerCurrent = nil
|
||||||
}
|
}
|
||||||
gsb.mu.Unlock()
|
gsb.mu.Unlock()
|
||||||
return balancer.ErrBadResolverState
|
return nil, balancer.ErrBadResolverState
|
||||||
}
|
}
|
||||||
|
|
||||||
// This write doesn't need to take gsb.mu because this field never gets read
|
// 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 {
|
||||||
// bw.Balancer field will never be forwarded to until this SwitchTo()
|
// bw.Balancer field will never be forwarded to until this SwitchTo()
|
||||||
// function returns.
|
// function returns.
|
||||||
bw.Balancer = newBalancer
|
bw.Balancer = newBalancer
|
||||||
return nil
|
return bw, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns nil if the graceful switch balancer is closed.
|
// Returns nil if the graceful switch balancer is closed.
|
||||||
|
|
@ -152,12 +161,33 @@ func (gsb *Balancer) latestBalancer() *balancerWrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateClientConnState forwards the update to the latest balancer created.
|
// UpdateClientConnState forwards the update to the latest balancer created.
|
||||||
|
//
|
||||||
|
// If the state's BalancerConfig is the config returned by a call to
|
||||||
|
// gracefulswitch.ParseConfig, then this function will automatically SwitchTo
|
||||||
|
// the balancer indicated by the config before forwarding its config to it, if
|
||||||
|
// necessary.
|
||||||
func (gsb *Balancer) UpdateClientConnState(state balancer.ClientConnState) error {
|
func (gsb *Balancer) UpdateClientConnState(state balancer.ClientConnState) error {
|
||||||
// The resolver data is only relevant to the most recent LB Policy.
|
// The resolver data is only relevant to the most recent LB Policy.
|
||||||
balToUpdate := gsb.latestBalancer()
|
balToUpdate := gsb.latestBalancer()
|
||||||
|
|
||||||
|
gsbCfg, ok := state.BalancerConfig.(*lbConfig)
|
||||||
|
if ok {
|
||||||
|
// Switch to the child in the config unless it is already active.
|
||||||
|
if balToUpdate == nil || gsbCfg.childBuilder.Name() != balToUpdate.builder.Name() {
|
||||||
|
var err error
|
||||||
|
balToUpdate, err = gsb.switchTo(gsbCfg.childBuilder)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not switch to new child balancer: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Unwrap the child balancer's config.
|
||||||
|
state.BalancerConfig = gsbCfg.childConfig
|
||||||
|
}
|
||||||
|
|
||||||
if balToUpdate == nil {
|
if balToUpdate == nil {
|
||||||
return errBalancerClosed
|
return errBalancerClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform this call without gsb.mu to prevent deadlocks if the child calls
|
// Perform this call without gsb.mu to prevent deadlocks if the child calls
|
||||||
// back into the channel. The latest balancer can never be closed during a
|
// back into the channel. The latest balancer can never be closed during a
|
||||||
// call from the channel, even without gsb.mu held.
|
// call from the channel, even without gsb.mu held.
|
||||||
|
|
@ -169,6 +199,10 @@ func (gsb *Balancer) ResolverError(err error) {
|
||||||
// The resolver data is only relevant to the most recent LB Policy.
|
// The resolver data is only relevant to the most recent LB Policy.
|
||||||
balToUpdate := gsb.latestBalancer()
|
balToUpdate := gsb.latestBalancer()
|
||||||
if balToUpdate == nil {
|
if balToUpdate == nil {
|
||||||
|
gsb.cc.UpdateState(balancer.State{
|
||||||
|
ConnectivityState: connectivity.TransientFailure,
|
||||||
|
Picker: base.NewErrPicker(err),
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Perform this call without gsb.mu to prevent deadlocks if the child calls
|
// Perform this call without gsb.mu to prevent deadlocks if the child calls
|
||||||
|
|
@ -261,7 +295,8 @@ func (gsb *Balancer) Close() {
|
||||||
// graceful switch logic.
|
// graceful switch logic.
|
||||||
type balancerWrapper struct {
|
type balancerWrapper struct {
|
||||||
balancer.Balancer
|
balancer.Balancer
|
||||||
gsb *Balancer
|
gsb *Balancer
|
||||||
|
builder balancer.Builder
|
||||||
|
|
||||||
lastState balancer.State
|
lastState balancer.State
|
||||||
subconns map[balancer.SubConn]bool // subconns created by this balancer
|
subconns map[balancer.SubConn]bool // subconns created by this balancer
|
||||||
|
|
|
||||||
14
pickfirst.go
14
pickfirst.go
|
|
@ -38,19 +38,15 @@ const (
|
||||||
logPrefix = "[pick-first-lb %p] "
|
logPrefix = "[pick-first-lb %p] "
|
||||||
)
|
)
|
||||||
|
|
||||||
func newPickfirstBuilder() balancer.Builder {
|
|
||||||
return &pickfirstBuilder{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type pickfirstBuilder struct{}
|
type pickfirstBuilder struct{}
|
||||||
|
|
||||||
func (*pickfirstBuilder) Build(cc balancer.ClientConn, opt balancer.BuildOptions) balancer.Balancer {
|
func (pickfirstBuilder) Build(cc balancer.ClientConn, opt balancer.BuildOptions) balancer.Balancer {
|
||||||
b := &pickfirstBalancer{cc: cc}
|
b := &pickfirstBalancer{cc: cc}
|
||||||
b.logger = internalgrpclog.NewPrefixLogger(logger, fmt.Sprintf(logPrefix, b))
|
b.logger = internalgrpclog.NewPrefixLogger(logger, fmt.Sprintf(logPrefix, b))
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*pickfirstBuilder) Name() string {
|
func (pickfirstBuilder) Name() string {
|
||||||
return PickFirstBalancerName
|
return PickFirstBalancerName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,7 +59,7 @@ type pfConfig struct {
|
||||||
ShuffleAddressList bool `json:"shuffleAddressList"`
|
ShuffleAddressList bool `json:"shuffleAddressList"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*pickfirstBuilder) ParseConfig(js json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
|
func (pickfirstBuilder) ParseConfig(js json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
|
||||||
var cfg pfConfig
|
var cfg pfConfig
|
||||||
if err := json.Unmarshal(js, &cfg); err != nil {
|
if err := json.Unmarshal(js, &cfg); err != nil {
|
||||||
return nil, fmt.Errorf("pickfirst: unable to unmarshal LB policy config: %s, error: %v", string(js), err)
|
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) {
|
||||||
i.subConn.Connect()
|
i.subConn.Connect()
|
||||||
return balancer.PickResult{}, balancer.ErrNoSubConnAvailable
|
return balancer.PickResult{}, balancer.ErrNoSubConnAvailable
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
balancer.Register(newPickfirstBuilder())
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,10 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/balancer"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/internal"
|
"google.golang.org/grpc/internal"
|
||||||
|
"google.golang.org/grpc/internal/balancer/gracefulswitch"
|
||||||
internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
|
internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
|
||||||
"google.golang.org/grpc/serviceconfig"
|
"google.golang.org/grpc/serviceconfig"
|
||||||
)
|
)
|
||||||
|
|
@ -41,11 +43,6 @@ const maxInt = int(^uint(0) >> 1)
|
||||||
// https://github.com/grpc/grpc/blob/master/doc/service_config.md
|
// https://github.com/grpc/grpc/blob/master/doc/service_config.md
|
||||||
type MethodConfig = internalserviceconfig.MethodConfig
|
type MethodConfig = internalserviceconfig.MethodConfig
|
||||||
|
|
||||||
type lbConfig struct {
|
|
||||||
name string
|
|
||||||
cfg serviceconfig.LoadBalancingConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceConfig is provided by the service provider and contains parameters for how
|
// ServiceConfig is provided by the service provider and contains parameters for how
|
||||||
// clients that connect to the service should behave.
|
// clients that connect to the service should behave.
|
||||||
//
|
//
|
||||||
|
|
@ -55,14 +52,9 @@ type lbConfig struct {
|
||||||
type ServiceConfig struct {
|
type ServiceConfig struct {
|
||||||
serviceconfig.Config
|
serviceconfig.Config
|
||||||
|
|
||||||
// LB is the load balancer the service providers recommends. This is
|
|
||||||
// deprecated; lbConfigs is preferred. If lbConfig and LB are both present,
|
|
||||||
// lbConfig will be used.
|
|
||||||
LB *string
|
|
||||||
|
|
||||||
// lbConfig is the service config's load balancing configuration. If
|
// lbConfig is the service config's load balancing configuration. If
|
||||||
// lbConfig and LB are both present, lbConfig will be used.
|
// lbConfig and LB are both present, lbConfig will be used.
|
||||||
lbConfig *lbConfig
|
lbConfig serviceconfig.LoadBalancingConfig
|
||||||
|
|
||||||
// Methods contains a map for the methods in this service. If there is an
|
// Methods contains a map for the methods in this service. If there is an
|
||||||
// exact match for a method (i.e. /service/method) in the map, use the
|
// exact match for a method (i.e. /service/method) in the map, use the
|
||||||
|
|
@ -164,7 +156,7 @@ type jsonMC struct {
|
||||||
// TODO(lyuxuan): delete this struct after cleaning up old service config implementation.
|
// TODO(lyuxuan): delete this struct after cleaning up old service config implementation.
|
||||||
type jsonSC struct {
|
type jsonSC struct {
|
||||||
LoadBalancingPolicy *string
|
LoadBalancingPolicy *string
|
||||||
LoadBalancingConfig *internalserviceconfig.BalancerConfig
|
LoadBalancingConfig *json.RawMessage
|
||||||
MethodConfig *[]jsonMC
|
MethodConfig *[]jsonMC
|
||||||
RetryThrottling *retryThrottlingPolicy
|
RetryThrottling *retryThrottlingPolicy
|
||||||
HealthCheckConfig *healthCheckConfig
|
HealthCheckConfig *healthCheckConfig
|
||||||
|
|
@ -184,18 +176,33 @@ func parseServiceConfig(js string) *serviceconfig.ParseResult {
|
||||||
return &serviceconfig.ParseResult{Err: err}
|
return &serviceconfig.ParseResult{Err: err}
|
||||||
}
|
}
|
||||||
sc := ServiceConfig{
|
sc := ServiceConfig{
|
||||||
LB: rsc.LoadBalancingPolicy,
|
|
||||||
Methods: make(map[string]MethodConfig),
|
Methods: make(map[string]MethodConfig),
|
||||||
retryThrottling: rsc.RetryThrottling,
|
retryThrottling: rsc.RetryThrottling,
|
||||||
healthCheckConfig: rsc.HealthCheckConfig,
|
healthCheckConfig: rsc.HealthCheckConfig,
|
||||||
rawJSONString: js,
|
rawJSONString: js,
|
||||||
}
|
}
|
||||||
if c := rsc.LoadBalancingConfig; c != nil {
|
c := rsc.LoadBalancingConfig
|
||||||
sc.lbConfig = &lbConfig{
|
if c == nil {
|
||||||
name: c.Name,
|
name := PickFirstBalancerName
|
||||||
cfg: c.Config,
|
if rsc.LoadBalancingPolicy != nil {
|
||||||
|
name = *rsc.LoadBalancingPolicy
|
||||||
}
|
}
|
||||||
|
if balancer.Get(name) == nil {
|
||||||
|
name = PickFirstBalancerName
|
||||||
|
}
|
||||||
|
cfg := []map[string]any{{name: struct{}{}}}
|
||||||
|
strCfg, err := json.Marshal(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return &serviceconfig.ParseResult{Err: fmt.Errorf("unexpected error marshaling simple LB config: %w", err)}
|
||||||
|
}
|
||||||
|
r := json.RawMessage(strCfg)
|
||||||
|
c = &r
|
||||||
}
|
}
|
||||||
|
cfg, err := gracefulswitch.ParseConfig(*c)
|
||||||
|
if err != nil {
|
||||||
|
return &serviceconfig.ParseResult{Err: err}
|
||||||
|
}
|
||||||
|
sc.lbConfig = cfg
|
||||||
|
|
||||||
if rsc.MethodConfig == nil {
|
if rsc.MethodConfig == nil {
|
||||||
return &serviceconfig.ParseResult{Config: &sc}
|
return &serviceconfig.ParseResult{Config: &sc}
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,13 @@ package grpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"google.golang.org/grpc/balancer"
|
"google.golang.org/grpc/balancer"
|
||||||
|
"google.golang.org/grpc/internal/balancer/gracefulswitch"
|
||||||
"google.golang.org/grpc/serviceconfig"
|
"google.golang.org/grpc/serviceconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -34,18 +36,40 @@ type parseTestCase struct {
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func lbConfigFor(t *testing.T, name string, cfg serviceconfig.LoadBalancingConfig) serviceconfig.LoadBalancingConfig {
|
||||||
|
if name == "" {
|
||||||
|
name = "pick_first"
|
||||||
|
cfg = struct {
|
||||||
|
serviceconfig.LoadBalancingConfig
|
||||||
|
}{}
|
||||||
|
}
|
||||||
|
d := []map[string]any{{name: cfg}}
|
||||||
|
strCfg, err := json.Marshal(d)
|
||||||
|
t.Logf("strCfg = %v", string(strCfg))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error parsing config: %v", err)
|
||||||
|
}
|
||||||
|
parsedCfg, err := gracefulswitch.ParseConfig(strCfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error parsing config: %v", err)
|
||||||
|
}
|
||||||
|
return parsedCfg
|
||||||
|
}
|
||||||
|
|
||||||
func runParseTests(t *testing.T, testCases []parseTestCase) {
|
func runParseTests(t *testing.T, testCases []parseTestCase) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
for _, c := range testCases {
|
for i, c := range testCases {
|
||||||
scpr := parseServiceConfig(c.scjs)
|
t.Run(fmt.Sprint(i), func(t *testing.T) {
|
||||||
var sc *ServiceConfig
|
scpr := parseServiceConfig(c.scjs)
|
||||||
sc, _ = scpr.Config.(*ServiceConfig)
|
var sc *ServiceConfig
|
||||||
if !c.wantErr {
|
sc, _ = scpr.Config.(*ServiceConfig)
|
||||||
c.wantSC.rawJSONString = c.scjs
|
if !c.wantErr {
|
||||||
}
|
c.wantSC.rawJSONString = c.scjs
|
||||||
if c.wantErr != (scpr.Err != nil) || !reflect.DeepEqual(sc, c.wantSC) {
|
}
|
||||||
t.Fatalf("parseServiceConfig(%s) = %+v, %v, want %+v, %v", c.scjs, sc, scpr.Err, c.wantSC, c.wantErr)
|
if c.wantErr != (scpr.Err != nil) || !reflect.DeepEqual(sc, c.wantSC) {
|
||||||
}
|
t.Fatalf("parseServiceConfig(%s) = %+v, %v, want %+v, %v", c.scjs, sc, scpr.Err, c.wantSC, c.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,7 +109,7 @@ func (s) TestParseLBConfig(t *testing.T) {
|
||||||
}`,
|
}`,
|
||||||
&ServiceConfig{
|
&ServiceConfig{
|
||||||
Methods: make(map[string]MethodConfig),
|
Methods: make(map[string]MethodConfig),
|
||||||
lbConfig: &lbConfig{name: "pbb", cfg: pbbData{Foo: "hi"}},
|
lbConfig: lbConfigFor(t, "pbb", pbbData{Foo: "hi"}),
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
|
@ -128,12 +152,12 @@ func (s) TestParseLoadBalancer(t *testing.T) {
|
||||||
]
|
]
|
||||||
}`,
|
}`,
|
||||||
&ServiceConfig{
|
&ServiceConfig{
|
||||||
LB: newString("round_robin"),
|
|
||||||
Methods: map[string]MethodConfig{
|
Methods: map[string]MethodConfig{
|
||||||
"/foo/Bar": {
|
"/foo/Bar": {
|
||||||
WaitForReady: newBool(true),
|
WaitForReady: newBool(true),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
lbConfig: lbConfigFor(t, "round_robin", nil),
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
|
@ -181,6 +205,7 @@ func (s) TestParseWaitForReady(t *testing.T) {
|
||||||
WaitForReady: newBool(true),
|
WaitForReady: newBool(true),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
lbConfig: lbConfigFor(t, "", nil),
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
|
@ -204,6 +229,7 @@ func (s) TestParseWaitForReady(t *testing.T) {
|
||||||
WaitForReady: newBool(false),
|
WaitForReady: newBool(false),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
lbConfig: lbConfigFor(t, "", nil),
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
|
@ -260,6 +286,7 @@ func (s) TestParseTimeOut(t *testing.T) {
|
||||||
Timeout: newDuration(time.Second),
|
Timeout: newDuration(time.Second),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
lbConfig: lbConfigFor(t, "", nil),
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
|
@ -335,6 +362,7 @@ func (s) TestParseMsgSize(t *testing.T) {
|
||||||
MaxRespSize: newInt(2048),
|
MaxRespSize: newInt(2048),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
lbConfig: lbConfigFor(t, "", nil),
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
|
@ -375,6 +403,7 @@ func (s) TestParseDefaultMethodConfig(t *testing.T) {
|
||||||
Methods: map[string]MethodConfig{
|
Methods: map[string]MethodConfig{
|
||||||
"": {WaitForReady: newBool(true)},
|
"": {WaitForReady: newBool(true)},
|
||||||
},
|
},
|
||||||
|
lbConfig: lbConfigFor(t, "", nil),
|
||||||
}
|
}
|
||||||
|
|
||||||
runParseTests(t, []parseTestCase{
|
runParseTests(t, []parseTestCase{
|
||||||
|
|
@ -454,7 +483,3 @@ func newBool(b bool) *bool {
|
||||||
func newDuration(b time.Duration) *time.Duration {
|
func newDuration(b time.Duration) *time.Duration {
|
||||||
return &b
|
return &b
|
||||||
}
|
}
|
||||||
|
|
||||||
func newString(b string) *string {
|
|
||||||
return &b
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -308,7 +308,7 @@ func (s) TestBalancerSwitch_RoundRobinToGRPCLB(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestBalancerSwitch_grpclbNotRegistered tests the scenario where the grpclb
|
// TestBalancerSwitch_grpclbNotRegistered tests the scenario where the grpclb
|
||||||
// balancer is not registered. Verifies that the ClientConn fallbacks to the
|
// balancer is not registered. Verifies that the ClientConn falls back to the
|
||||||
// default LB policy or the LB policy specified in the service config, and that
|
// default LB policy or the LB policy specified in the service config, and that
|
||||||
// addresses of type "grpclb" are filtered out.
|
// addresses of type "grpclb" are filtered out.
|
||||||
func (s) TestBalancerSwitch_grpclbNotRegistered(t *testing.T) {
|
func (s) TestBalancerSwitch_grpclbNotRegistered(t *testing.T) {
|
||||||
|
|
|
||||||
1
vet.sh
1
vet.sh
|
|
@ -176,6 +176,7 @@ UpdateAddresses is deprecated:
|
||||||
UpdateSubConnState is deprecated:
|
UpdateSubConnState is deprecated:
|
||||||
balancer.ErrTransientFailure is deprecated:
|
balancer.ErrTransientFailure is deprecated:
|
||||||
grpc/reflection/v1alpha/reflection.proto
|
grpc/reflection/v1alpha/reflection.proto
|
||||||
|
SwitchTo is deprecated:
|
||||||
XXXXX xDS deprecated fields we support
|
XXXXX xDS deprecated fields we support
|
||||||
.ExactMatch
|
.ExactMatch
|
||||||
.PrefixMatch
|
.PrefixMatch
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue