snapshot of https://github.com/grpc/grpc-go.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Stale bot / stale (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-15 20:40:15 +05:00
commit e241b33f26
1365 changed files with 339570 additions and 0 deletions

View File

@@ -0,0 +1,292 @@
/*
*
* Copyright 2021 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 priority implements the priority balancer.
//
// This balancer will be kept in internal until we use it in the xds balancers,
// and are confident its functionalities are stable. It will then be exported
// for more users.
package priority
import (
"encoding/json"
"fmt"
"sync"
"time"
"google.golang.org/grpc/balancer"
"google.golang.org/grpc/balancer/base"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/internal/balancergroup"
"google.golang.org/grpc/internal/buffer"
"google.golang.org/grpc/internal/envconfig"
"google.golang.org/grpc/internal/grpclog"
"google.golang.org/grpc/internal/grpcsync"
"google.golang.org/grpc/internal/hierarchy"
"google.golang.org/grpc/internal/pretty"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/serviceconfig"
)
// Name is the name of the priority balancer.
const Name = "priority_experimental"
// DefaultSubBalancerCloseTimeout is defined as a variable instead of const for
// testing.
var DefaultSubBalancerCloseTimeout = 15 * time.Minute
func init() {
balancer.Register(bb{})
}
type bb struct{}
func (bb) Build(cc balancer.ClientConn, bOpts balancer.BuildOptions) balancer.Balancer {
b := &priorityBalancer{
cc: cc,
done: grpcsync.NewEvent(),
children: make(map[string]*childBalancer),
childBalancerStateUpdate: buffer.NewUnbounded[any](),
}
b.logger = prefixLogger(b)
b.bg = balancergroup.New(balancergroup.Options{
CC: cc,
BuildOpts: bOpts,
StateAggregator: b,
Logger: b.logger,
SubBalancerCloseTimeout: DefaultSubBalancerCloseTimeout,
})
go b.run()
b.logger.Infof("Created")
return b
}
func (b bb) ParseConfig(s json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
return parseConfig(s)
}
func (bb) Name() string {
return Name
}
// timerWrapper wraps a timer with a boolean. So that when a race happens
// between AfterFunc and Stop, the func is guaranteed to not execute.
type timerWrapper struct {
stopped bool
timer *time.Timer
}
type priorityBalancer struct {
logger *grpclog.PrefixLogger
cc balancer.ClientConn
bg *balancergroup.BalancerGroup
done *grpcsync.Event
childBalancerStateUpdate *buffer.Unbounded[any]
mu sync.Mutex
childInUse string
// priorities is a list of child names from higher to lower priority.
priorities []string
// children is a map from child name to sub-balancers.
children map[string]*childBalancer
// Set during UpdateClientConnState when calling into sub-balancers.
// Prevents child updates from recomputing the active priority or sending
// an update of the aggregated picker to the parent. Cleared after all
// sub-balancers have finished UpdateClientConnState, after which
// syncPriority is called manually.
inhibitPickerUpdates bool
}
func (b *priorityBalancer) UpdateClientConnState(s balancer.ClientConnState) error {
if b.logger.V(2) {
b.logger.Infof("Received an update with balancer config: %+v", pretty.ToJSON(s.BalancerConfig))
}
newConfig, ok := s.BalancerConfig.(*LBConfig)
if !ok {
return fmt.Errorf("unexpected balancer config with type: %T", s.BalancerConfig)
}
endpointsSplit := hierarchy.Group(s.ResolverState.Endpoints)
b.mu.Lock()
// Create and remove children, since we know all children from the config
// are used by some priority.
for name, newSubConfig := range newConfig.Children {
bb := balancer.Get(newSubConfig.Config.Name)
if bb == nil {
b.logger.Errorf("balancer name %v from config is not registered", newSubConfig.Config.Name)
continue
}
currentChild, ok := b.children[name]
if !ok {
// This is a new child, add it to the children list. But note that
// the balancer isn't built, because this child can be a low
// priority. If necessary, it will be built when syncing priorities.
cb := newChildBalancer(name, b, bb.Name(), b.cc)
cb.updateConfig(newSubConfig, resolver.State{
Endpoints: endpointsSplit[name],
ServiceConfig: s.ResolverState.ServiceConfig,
Attributes: s.ResolverState.Attributes,
})
b.children[name] = cb
continue
}
// This is not a new child. But the config/addresses could change.
// The balancing policy name is changed, close the old child. But don't
// rebuild, rebuild will happen when syncing priorities.
if currentChild.balancerName != bb.Name() {
currentChild.stop(true)
currentChild.updateBalancerName(bb.Name())
}
// Update config and address, but note that this doesn't send the
// updates to non-started child balancers (the child balancer might not
// be built, if it's a low priority).
currentChild.updateConfig(newSubConfig, resolver.State{
Endpoints: endpointsSplit[name],
ServiceConfig: s.ResolverState.ServiceConfig,
Attributes: s.ResolverState.Attributes,
})
}
// Cleanup resources used by children removed from the config.
for name, oldChild := range b.children {
if _, ok := newConfig.Children[name]; !ok {
oldChild.stop(!envconfig.EnablePriorityLBChildPolicyCache)
delete(b.children, name)
}
}
// Update priorities and handle priority changes.
b.priorities = newConfig.Priorities
// Everything was removed by the update.
if len(b.priorities) == 0 {
b.childInUse = ""
b.cc.UpdateState(balancer.State{
ConnectivityState: connectivity.TransientFailure,
Picker: base.NewErrPicker(ErrAllPrioritiesRemoved),
})
b.mu.Unlock()
return nil
}
// This will sync the states of all children to the new updated
// priorities. Includes starting/stopping child balancers when necessary.
// Block picker updates until all children have had a chance to call
// UpdateState to prevent races where, e.g., the active priority reports
// transient failure but a higher priority may have reported something that
// made it active, and if the transient failure update is handled first,
// RPCs could fail.
b.inhibitPickerUpdates = true
// Add an item to queue to notify us when the current items in the queue
// are done and syncPriority has been called.
done := make(chan struct{})
b.childBalancerStateUpdate.Put(resumePickerUpdates{done: done})
b.mu.Unlock()
<-done
return nil
}
func (b *priorityBalancer) ResolverError(err error) {
if b.logger.V(2) {
b.logger.Infof("Received error from the resolver: %v", err)
}
b.bg.ResolverError(err)
}
func (b *priorityBalancer) UpdateSubConnState(sc balancer.SubConn, state balancer.SubConnState) {
b.logger.Errorf("UpdateSubConnState(%v, %+v) called unexpectedly", sc, state)
}
func (b *priorityBalancer) Close() {
b.bg.Close()
b.childBalancerStateUpdate.Close()
b.mu.Lock()
defer b.mu.Unlock()
b.done.Fire()
// Clear states of the current child in use, so if there's a race in picker
// update, it will be dropped.
b.childInUse = ""
// Stop the child policies, this is necessary to stop the init timers in the
// children.
for _, child := range b.children {
child.stop(true)
}
}
func (b *priorityBalancer) ExitIdle() {
b.bg.ExitIdle()
}
// UpdateState implements balancergroup.BalancerStateAggregator interface. The
// balancer group sends new connectivity state and picker here.
func (b *priorityBalancer) UpdateState(childName string, state balancer.State) {
b.childBalancerStateUpdate.Put(childBalancerState{
name: childName,
s: state,
})
}
type childBalancerState struct {
name string
s balancer.State
}
type resumePickerUpdates struct {
done chan struct{}
}
// run handles child update in a separate goroutine, so if the child sends
// updates inline (when called by parent), it won't cause deadlocks (by trying
// to hold the same mutex).
func (b *priorityBalancer) run() {
for {
select {
case u, ok := <-b.childBalancerStateUpdate.Get():
if !ok {
return
}
b.childBalancerStateUpdate.Load()
// Needs to handle state update in a goroutine, because each state
// update needs to start/close child policy, could result in
// deadlock.
b.mu.Lock()
if b.done.HasFired() {
b.mu.Unlock()
return
}
switch s := u.(type) {
case childBalancerState:
b.handleChildStateUpdate(s.name, s.s)
case resumePickerUpdates:
b.inhibitPickerUpdates = false
b.syncPriority(b.childInUse)
close(s.done)
}
b.mu.Unlock()
case <-b.done.Done():
return
}
}
}

View File

@@ -0,0 +1,186 @@
/*
*
* Copyright 2021 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 priority
import (
"time"
"google.golang.org/grpc/balancer"
"google.golang.org/grpc/balancer/base"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/serviceconfig"
)
var timeAfterFunc = time.AfterFunc
type childBalancer struct {
name string
parent *priorityBalancer
parentCC balancer.ClientConn
balancerName string
cc *ignoreResolveNowClientConn
ignoreReresolutionRequests bool
config serviceconfig.LoadBalancingConfig
rState resolver.State
started bool
// This is set when the child reports TransientFailure, and unset when it
// reports Ready or Idle. It is used to decide whether the failover timer
// should start when the child is transitioning into Connecting. The timer
// will be restarted if the child has not reported TF more recently than it
// reported Ready or Idle.
reportedTF bool
// The latest state the child balancer provided.
state balancer.State
// The timer to give a priority some time to connect. And if the priority
// doesn't go into Ready/Failure, the next priority will be started.
initTimer *timerWrapper
}
// newChildBalancer creates a child balancer place holder, but doesn't
// build/start the child balancer.
func newChildBalancer(name string, parent *priorityBalancer, balancerName string, cc balancer.ClientConn) *childBalancer {
return &childBalancer{
name: name,
parent: parent,
parentCC: cc,
balancerName: balancerName,
cc: newIgnoreResolveNowClientConn(cc, false),
started: false,
// Start with the connecting state and picker with re-pick error, so
// that when a priority switch causes this child picked before it's
// balancing policy is created, a re-pick will happen.
state: balancer.State{
ConnectivityState: connectivity.Connecting,
Picker: base.NewErrPicker(balancer.ErrNoSubConnAvailable),
},
}
}
// updateBalancerName updates balancer name for the child, but doesn't build a
// new one. The parent priority LB always closes the child policy before
// updating the balancer name, and the new balancer is built when it gets added
// to the balancergroup as part of start().
func (cb *childBalancer) updateBalancerName(balancerName string) {
cb.balancerName = balancerName
cb.cc = newIgnoreResolveNowClientConn(cb.parentCC, cb.ignoreReresolutionRequests)
}
// updateConfig sets childBalancer's config and state, but doesn't send update to
// the child balancer unless it is started.
func (cb *childBalancer) updateConfig(child *Child, rState resolver.State) {
cb.ignoreReresolutionRequests = child.IgnoreReresolutionRequests
cb.config = child.Config.Config
cb.rState = rState
if cb.started {
cb.sendUpdate()
}
}
// start builds the child balancer if it's not already started.
//
// It doesn't do it directly. It asks the balancer group to build it.
func (cb *childBalancer) start() {
if cb.started {
return
}
cb.started = true
cb.parent.bg.AddWithClientConn(cb.name, cb.balancerName, cb.cc)
cb.startInitTimer()
cb.sendUpdate()
}
// sendUpdate sends the addresses and config to the child balancer.
func (cb *childBalancer) sendUpdate() {
cb.cc.updateIgnoreResolveNow(cb.ignoreReresolutionRequests)
// TODO: return and aggregate the returned error in the parent.
err := cb.parent.bg.UpdateClientConnState(cb.name, balancer.ClientConnState{
ResolverState: cb.rState,
BalancerConfig: cb.config,
})
// Report TF if update to the child fails.
if err != nil {
cb.parent.logger.Warningf("Failed to update state for child policy %q: %v", cb.name, err)
cb.reportedTF = true
cb.state = balancer.State{
ConnectivityState: connectivity.TransientFailure,
Picker: base.NewErrPicker(err),
}
cb.parent.handleChildStateUpdate(cb.name, cb.state)
}
}
// stop stops the child balancer and resets the state.
//
// It doesn't do it directly. It asks the balancer group to remove it.
//
// Note that the underlying balancer group could keep the child in a cache.
func (cb *childBalancer) stop(immediate bool) {
if !cb.started {
return
}
cb.stopInitTimer()
if immediate {
cb.parent.bg.RemoveImmediately(cb.name)
} else {
cb.parent.bg.Remove(cb.name)
}
cb.started = false
cb.state = balancer.State{
ConnectivityState: connectivity.Connecting,
Picker: base.NewErrPicker(balancer.ErrNoSubConnAvailable),
}
// Clear child.reportedTF, so that if this child is started later, it will
// be given time to connect.
cb.reportedTF = false
}
func (cb *childBalancer) startInitTimer() {
if cb.initTimer != nil {
return
}
// Need this local variable to capture timerW in the AfterFunc closure
// to check the stopped boolean.
timerW := &timerWrapper{}
cb.initTimer = timerW
timerW.timer = timeAfterFunc(DefaultPriorityInitTimeout, func() {
cb.parent.mu.Lock()
defer cb.parent.mu.Unlock()
if timerW.stopped {
return
}
cb.initTimer = nil
// Re-sync the priority. This will switch to the next priority if
// there's any. Note that it's important sync() is called after setting
// initTimer to nil.
cb.parent.syncPriority("")
})
}
func (cb *childBalancer) stopInitTimer() {
timerW := cb.initTimer
if timerW == nil {
return
}
cb.initTimer = nil
timerW.stopped = true
timerW.timer.Stop()
}

View File

@@ -0,0 +1,223 @@
/*
*
* Copyright 2021 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 priority
import (
"errors"
"time"
"google.golang.org/grpc/balancer"
"google.golang.org/grpc/connectivity"
)
var (
// ErrAllPrioritiesRemoved is returned by the picker when there's no priority available.
ErrAllPrioritiesRemoved = errors.New("no priority is provided, all priorities are removed")
// DefaultPriorityInitTimeout is the timeout after which if a priority is
// not READY, the next will be started. It's exported to be overridden by
// tests.
DefaultPriorityInitTimeout = 10 * time.Second
)
// syncPriority handles priority after a config update or a child balancer
// connectivity state update. It makes sure the balancer state (started or not)
// is in sync with the priorities (even in tricky cases where a child is moved
// from a priority to another).
//
// It's guaranteed that after this function returns:
//
// If some child is READY, it is childInUse, and all lower priorities are
// closed.
//
// If some child is newly started(in Connecting for the first time), it is
// childInUse, and all lower priorities are closed.
//
// Otherwise, the lowest priority is childInUse (none of the children is
// ready, and the overall state is not ready).
//
// Steps:
//
// If all priorities were deleted, unset childInUse (to an empty string), and
// set parent ClientConn to TransientFailure
//
// Otherwise, Scan all children from p0, and check balancer stats:
//
// For any of the following cases:
//
// If balancer is not started (not built), this is either a new child with
// high priority, or a new builder for an existing child.
//
// If balancer is Connecting and has non-nil initTimer (meaning it
// transitioned from Ready or Idle to connecting, not from TF, so we
// should give it init-time to connect).
//
// If balancer is READY or IDLE
//
// If this is the lowest priority
//
// do the following:
//
// if this is not the old childInUse, override picker so old picker is no
// longer used.
//
// switch to it (because all higher priorities are neither new or Ready)
//
// forward the new addresses and config
//
// Caller must hold b.mu.
func (b *priorityBalancer) syncPriority(childUpdating string) {
if b.inhibitPickerUpdates {
if b.logger.V(2) {
b.logger.Infof("Skipping update from child policy %q", childUpdating)
}
return
}
for p, name := range b.priorities {
child, ok := b.children[name]
if !ok {
b.logger.Warningf("Priority name %q is not found in list of child policies", name)
continue
}
if !child.started ||
child.state.ConnectivityState == connectivity.Ready ||
child.state.ConnectivityState == connectivity.Idle ||
(child.state.ConnectivityState == connectivity.Connecting && child.initTimer != nil) ||
p == len(b.priorities)-1 {
if b.childInUse != child.name || child.name == childUpdating {
if b.logger.V(2) {
b.logger.Infof("childInUse, childUpdating: %q, %q", b.childInUse, child.name)
}
// If we switch children or the child in use just updated its
// picker, push the child's picker to the parent.
b.cc.UpdateState(child.state)
}
if b.logger.V(2) {
b.logger.Infof("Switching to (%q, %v) in syncPriority", child.name, p)
}
b.switchToChild(child, p)
break
}
}
}
// Stop priorities [p+1, lowest].
//
// Caller must hold b.mu.
func (b *priorityBalancer) stopSubBalancersLowerThanPriority(p int) {
for i := p + 1; i < len(b.priorities); i++ {
name := b.priorities[i]
child, ok := b.children[name]
if !ok {
b.logger.Warningf("Priority name %q is not found in list of child policies", name)
continue
}
child.stop(false)
}
}
// switchToChild does the following:
// - stop all child with lower priorities
// - if childInUse is not this child
// - set childInUse to this child
// - if this child is not started, start it
//
// Note that it does NOT send the current child state (picker) to the parent
// ClientConn. The caller needs to send it if necessary.
//
// this can be called when
// 1. first update, start p0
// 2. an update moves a READY child from a lower priority to higher
// 2. a different builder is updated for this child
// 3. a high priority goes Failure, start next
// 4. a high priority init timeout, start next
//
// Caller must hold b.mu.
func (b *priorityBalancer) switchToChild(child *childBalancer, priority int) {
// Stop lower priorities even if childInUse is same as this child. It's
// possible this child was moved from a priority to another.
b.stopSubBalancersLowerThanPriority(priority)
// If this child is already in use, do nothing.
//
// This can happen:
// - all priorities are not READY, an config update always triggers switch
// to the lowest. In this case, the lowest child could still be connecting,
// so we don't stop the init timer.
// - a high priority is READY, an config update always triggers switch to
// it.
if b.childInUse == child.name && child.started {
return
}
b.childInUse = child.name
if !child.started {
child.start()
}
}
// handleChildStateUpdate start/close priorities based on the connectivity
// state.
func (b *priorityBalancer) handleChildStateUpdate(childName string, newState balancer.State) {
// Update state in child. The updated picker will be sent to parent later if
// necessary.
child, ok := b.children[childName]
if !ok {
b.logger.Warningf("Child policy not found for %q", childName)
return
}
if !child.started {
b.logger.Warningf("Ignoring update from child policy %q which is not in started state: %+v", childName, newState)
return
}
oldState := child.state
child.state = newState
// We start/stop the init timer of this child based on the new connectivity
// state. syncPriority() later will need the init timer (to check if it's
// nil or not) to decide which child to switch to.
switch newState.ConnectivityState {
case connectivity.Ready, connectivity.Idle:
child.reportedTF = false
child.stopInitTimer()
case connectivity.TransientFailure:
child.reportedTF = true
child.stopInitTimer()
case connectivity.Connecting:
// The init timer is created when the child is created and is reset when
// it reports Ready or Idle. Most child policies start off in
// Connecting, but ring_hash starts off in Idle and moves to Connecting
// when a request comes in. To support such cases, we restart the init
// timer when we see Connecting, but only if the child has not reported
// TransientFailure more recently than it reported Ready or Idle. See
// gRFC A42 for details on why ring_hash is special and what provisions
// are required to make it work as a child of the priority LB policy.
//
// We don't want to restart the timer if the child was already in
// Connecting, because we want failover to happen once the timer elapses
// even when the child is still in Connecting.
if !child.reportedTF && oldState.ConnectivityState != connectivity.Connecting {
child.startInitTimer()
}
default:
// New state is Shutdown, should never happen. Don't forward.
}
child.parent.syncPriority(childName)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,67 @@
/*
*
* Copyright 2020 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 priority
import (
"encoding/json"
"fmt"
internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
"google.golang.org/grpc/serviceconfig"
)
// Child is a child of priority balancer.
type Child struct {
Config *internalserviceconfig.BalancerConfig `json:"config,omitempty"`
IgnoreReresolutionRequests bool `json:"ignoreReresolutionRequests,omitempty"`
}
// LBConfig represents priority balancer's config.
type LBConfig struct {
serviceconfig.LoadBalancingConfig `json:"-"`
// Children is a map from the child balancer names to their configs. Child
// names can be found in field Priorities.
Children map[string]*Child `json:"children,omitempty"`
// Priorities is a list of child balancer names. They are sorted from
// highest priority to low. The type/config for each child can be found in
// field Children, with the balancer name as the key.
Priorities []string `json:"priorities,omitempty"`
}
func parseConfig(c json.RawMessage) (*LBConfig, error) {
var cfg LBConfig
if err := json.Unmarshal(c, &cfg); err != nil {
return nil, err
}
prioritiesSet := make(map[string]bool)
for _, name := range cfg.Priorities {
if _, ok := cfg.Children[name]; !ok {
return nil, fmt.Errorf("LB policy name %q found in Priorities field (%v) is not found in Children field (%+v)", name, cfg.Priorities, cfg.Children)
}
prioritiesSet[name] = true
}
for name := range cfg.Children {
if _, ok := prioritiesSet[name]; !ok {
return nil, fmt.Errorf("LB policy name %q found in Children field (%v) is not found in Priorities field (%+v)", name, cfg.Children, cfg.Priorities)
}
}
return &cfg, nil
}

View File

@@ -0,0 +1,108 @@
/*
*
* Copyright 2020 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 priority
import (
"testing"
"github.com/google/go-cmp/cmp"
"google.golang.org/grpc/balancer/roundrobin"
internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
)
func TestParseConfig(t *testing.T) {
tests := []struct {
name string
js string
want *LBConfig
wantErr bool
}{
{
name: "child not found",
js: `{
"priorities": ["child-1", "child-2", "child-3"],
"children": {
"child-1": {"config": [{"round_robin":{}}]},
"child-3": {"config": [{"round_robin":{}}]}
}
}
`,
wantErr: true,
},
{
name: "child not used",
js: `{
"priorities": ["child-1", "child-2"],
"children": {
"child-1": {"config": [{"round_robin":{}}]},
"child-2": {"config": [{"round_robin":{}}]},
"child-3": {"config": [{"round_robin":{}}]}
}
}
`,
wantErr: true,
},
{
name: "good",
js: `{
"priorities": ["child-1", "child-2", "child-3"],
"children": {
"child-1": {"config": [{"round_robin":{}}], "ignoreReresolutionRequests": true},
"child-2": {"config": [{"round_robin":{}}]},
"child-3": {"config": [{"round_robin":{}}]}
}
}
`,
want: &LBConfig{
Children: map[string]*Child{
"child-1": {
Config: &internalserviceconfig.BalancerConfig{
Name: roundrobin.Name,
},
IgnoreReresolutionRequests: true,
},
"child-2": {
Config: &internalserviceconfig.BalancerConfig{
Name: roundrobin.Name,
},
},
"child-3": {
Config: &internalserviceconfig.BalancerConfig{
Name: roundrobin.Name,
},
},
},
Priorities: []string{"child-1", "child-2", "child-3"},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseConfig([]byte(tt.js))
if (err != nil) != tt.wantErr {
t.Errorf("parseConfig() error = %v, wantErr %v", err, tt.wantErr)
return
}
if diff := cmp.Diff(got, tt.want); diff != "" {
t.Errorf("parseConfig() got = %v, want %v, diff: %s", got, tt.want, diff)
}
})
}
}

View File

@@ -0,0 +1,50 @@
/*
*
* Copyright 2021 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 priority
import (
"sync/atomic"
"google.golang.org/grpc/balancer"
"google.golang.org/grpc/resolver"
)
// ignoreResolveNowClientConn wraps a balancer.ClientConn and overrides the
// ResolveNow() method to ignore those calls if the ignoreResolveNow bit is set.
type ignoreResolveNowClientConn struct {
balancer.ClientConn
ignoreResolveNow atomic.Bool
}
func newIgnoreResolveNowClientConn(cc balancer.ClientConn, ignore bool) *ignoreResolveNowClientConn {
ret := &ignoreResolveNowClientConn{ClientConn: cc}
ret.updateIgnoreResolveNow(ignore)
return ret
}
func (i *ignoreResolveNowClientConn) updateIgnoreResolveNow(b bool) {
i.ignoreResolveNow.Store(b)
}
func (i *ignoreResolveNowClientConn) ResolveNow(o resolver.ResolveNowOptions) {
if i.ignoreResolveNow.Load() {
return
}
i.ClientConn.ResolveNow(o)
}

View File

@@ -0,0 +1,66 @@
/*
*
* Copyright 2021 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 priority
import (
"context"
"testing"
"time"
"google.golang.org/grpc/internal/testutils"
"google.golang.org/grpc/resolver"
)
func (s) TestIgnoreResolveNowClientConn(t *testing.T) {
cc := testutils.NewBalancerClientConn(t)
ignoreCC := newIgnoreResolveNowClientConn(cc, false)
// Call ResolveNow() on the CC, it should be forwarded.
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
ignoreCC.ResolveNow(resolver.ResolveNowOptions{})
select {
case <-cc.ResolveNowCh:
case <-ctx.Done():
t.Fatalf("Timeout waiting for ResolveNow()")
}
// Update ignoreResolveNow to true, call ResolveNow() on the CC, they should
// all be ignored.
ignoreCC.updateIgnoreResolveNow(true)
for i := 0; i < 5; i++ {
ignoreCC.ResolveNow(resolver.ResolveNowOptions{})
}
select {
case <-cc.ResolveNowCh:
t.Fatalf("got unexpected ResolveNow() call")
case <-time.After(defaultTestShortTimeout):
}
// Update ignoreResolveNow to false, new ResolveNow() calls should be
// forwarded.
ignoreCC.updateIgnoreResolveNow(false)
ignoreCC.ResolveNow(resolver.ResolveNowOptions{})
select {
case <-cc.ResolveNowCh:
case <-ctx.Done():
t.Fatalf("timeout waiting for ResolveNow()")
}
}

View File

@@ -0,0 +1,34 @@
/*
*
* Copyright 2021 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 priority
import (
"fmt"
"google.golang.org/grpc/grpclog"
internalgrpclog "google.golang.org/grpc/internal/grpclog"
)
const prefix = "[priority-lb %p] "
var logger = grpclog.Component("xds")
func prefixLogger(p *priorityBalancer) *internalgrpclog.PrefixLogger {
return internalgrpclog.NewPrefixLogger(logger, fmt.Sprintf(prefix, p))
}