google.golang.org/grpc@v1.72.2/balancer/weightedtarget/weightedtarget.go (about)

     1  /*
     2   *
     3   * Copyright 2020 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 weightedtarget implements the weighted_target balancer.
    20  //
    21  // All APIs in this package are experimental.
    22  package weightedtarget
    23  
    24  import (
    25  	"encoding/json"
    26  	"fmt"
    27  	"time"
    28  
    29  	"google.golang.org/grpc/balancer"
    30  	"google.golang.org/grpc/balancer/weightedtarget/weightedaggregator"
    31  	"google.golang.org/grpc/internal/balancergroup"
    32  	"google.golang.org/grpc/internal/grpclog"
    33  	"google.golang.org/grpc/internal/hierarchy"
    34  	"google.golang.org/grpc/internal/pretty"
    35  	"google.golang.org/grpc/internal/wrr"
    36  	"google.golang.org/grpc/resolver"
    37  	"google.golang.org/grpc/serviceconfig"
    38  )
    39  
    40  // Name is the name of the weighted_target balancer.
    41  const Name = "weighted_target_experimental"
    42  
    43  // NewRandomWRR is the WRR constructor used to pick sub-pickers from
    44  // sub-balancers. It's to be modified in tests.
    45  var NewRandomWRR = wrr.NewRandom
    46  
    47  func init() {
    48  	balancer.Register(bb{})
    49  }
    50  
    51  type bb struct{}
    52  
    53  func (bb) Build(cc balancer.ClientConn, bOpts balancer.BuildOptions) balancer.Balancer {
    54  	b := &weightedTargetBalancer{}
    55  	b.logger = prefixLogger(b)
    56  	b.stateAggregator = weightedaggregator.New(cc, b.logger, NewRandomWRR)
    57  	b.stateAggregator.Start()
    58  	b.bg = balancergroup.New(balancergroup.Options{
    59  		CC:                      cc,
    60  		BuildOpts:               bOpts,
    61  		StateAggregator:         b.stateAggregator,
    62  		Logger:                  b.logger,
    63  		SubBalancerCloseTimeout: time.Duration(0), // Disable caching of removed child policies
    64  	})
    65  	b.logger.Infof("Created")
    66  	return b
    67  }
    68  
    69  func (bb) Name() string {
    70  	return Name
    71  }
    72  
    73  func (bb) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
    74  	return parseConfig(c)
    75  }
    76  
    77  type weightedTargetBalancer struct {
    78  	logger *grpclog.PrefixLogger
    79  
    80  	bg              *balancergroup.BalancerGroup
    81  	stateAggregator *weightedaggregator.Aggregator
    82  
    83  	targets map[string]Target
    84  }
    85  
    86  type localityKeyType string
    87  
    88  const localityKey = localityKeyType("locality")
    89  
    90  // LocalityFromResolverState returns the locality from the resolver.State
    91  // provided, or an empty string if not present.
    92  func LocalityFromResolverState(state resolver.State) string {
    93  	locality, _ := state.Attributes.Value(localityKey).(string)
    94  	return locality
    95  }
    96  
    97  // UpdateClientConnState takes the new targets in balancer group,
    98  // creates/deletes sub-balancers and sends them update. addresses are split into
    99  // groups based on hierarchy path.
   100  func (b *weightedTargetBalancer) UpdateClientConnState(s balancer.ClientConnState) error {
   101  	if b.logger.V(2) {
   102  		b.logger.Infof("Received update from resolver, balancer config: %+v", pretty.ToJSON(s.BalancerConfig))
   103  	}
   104  
   105  	newConfig, ok := s.BalancerConfig.(*LBConfig)
   106  	if !ok {
   107  		return fmt.Errorf("unexpected balancer config with type: %T", s.BalancerConfig)
   108  	}
   109  	addressesSplit := hierarchy.Group(s.ResolverState.Addresses)
   110  	endpointsSplit := hierarchy.GroupEndpoints(s.ResolverState.Endpoints)
   111  
   112  	b.stateAggregator.PauseStateUpdates()
   113  	defer b.stateAggregator.ResumeStateUpdates()
   114  
   115  	// Remove sub-pickers and sub-balancers that are not in the new config.
   116  	for name := range b.targets {
   117  		if _, ok := newConfig.Targets[name]; !ok {
   118  			b.stateAggregator.Remove(name)
   119  			b.bg.Remove(name)
   120  		}
   121  	}
   122  
   123  	// For sub-balancers in the new config
   124  	// - if it's new. add to balancer group,
   125  	// - if it's old, but has a new weight, update weight in balancer group.
   126  	//
   127  	// For all sub-balancers, forward the address/balancer config update.
   128  	for name, newT := range newConfig.Targets {
   129  		oldT, ok := b.targets[name]
   130  		if !ok {
   131  			// If this is a new sub-balancer, add weights to the picker map.
   132  			b.stateAggregator.Add(name, newT.Weight)
   133  			// Then add to the balancer group.
   134  			b.bg.Add(name, balancer.Get(newT.ChildPolicy.Name))
   135  			// Not trigger a state/picker update. Wait for the new sub-balancer
   136  			// to send its updates.
   137  		} else if newT.ChildPolicy.Name != oldT.ChildPolicy.Name {
   138  			// If the child policy name is different, remove from balancer group
   139  			// and re-add.
   140  			b.stateAggregator.Remove(name)
   141  			b.bg.Remove(name)
   142  			b.stateAggregator.Add(name, newT.Weight)
   143  			b.bg.Add(name, balancer.Get(newT.ChildPolicy.Name))
   144  		} else if newT.Weight != oldT.Weight {
   145  			// If this is an existing sub-balancer, update weight if necessary.
   146  			b.stateAggregator.UpdateWeight(name, newT.Weight)
   147  		}
   148  
   149  		// Forwards all the update:
   150  		// - addresses are from the map after splitting with hierarchy path,
   151  		// - Top level service config and attributes are the same,
   152  		// - Balancer config comes from the targets map.
   153  		//
   154  		// TODO: handle error? How to aggregate errors and return?
   155  		_ = b.bg.UpdateClientConnState(name, balancer.ClientConnState{
   156  			ResolverState: resolver.State{
   157  				Addresses:     addressesSplit[name],
   158  				Endpoints:     endpointsSplit[name],
   159  				ServiceConfig: s.ResolverState.ServiceConfig,
   160  				Attributes:    s.ResolverState.Attributes.WithValue(localityKey, name),
   161  			},
   162  			BalancerConfig: newT.ChildPolicy.Config,
   163  		})
   164  	}
   165  
   166  	b.targets = newConfig.Targets
   167  
   168  	// If the targets length is zero, it means we have removed all child
   169  	// policies from the balancer group and aggregator.
   170  	// At the start of this UpdateClientConnState() operation, a call to
   171  	// b.stateAggregator.ResumeStateUpdates() is deferred. Thus, setting the
   172  	// needUpdateStateOnResume bool to true here will ensure a new picker is
   173  	// built as part of that deferred function. Since there are now no child
   174  	// policies, the aggregated connectivity state reported form the Aggregator
   175  	// will be TRANSIENT_FAILURE.
   176  	if len(b.targets) == 0 {
   177  		b.stateAggregator.NeedUpdateStateOnResume()
   178  	}
   179  
   180  	return nil
   181  }
   182  
   183  func (b *weightedTargetBalancer) ResolverError(err error) {
   184  	b.bg.ResolverError(err)
   185  }
   186  
   187  func (b *weightedTargetBalancer) UpdateSubConnState(sc balancer.SubConn, state balancer.SubConnState) {
   188  	b.logger.Errorf("UpdateSubConnState(%v, %+v) called unexpectedly", sc, state)
   189  }
   190  
   191  func (b *weightedTargetBalancer) Close() {
   192  	b.stateAggregator.Stop()
   193  	b.bg.Close()
   194  }
   195  
   196  func (b *weightedTargetBalancer) ExitIdle() {
   197  	b.bg.ExitIdle()
   198  }