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
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:
644
internal/xds/balancer/clusterimpl/clusterimpl.go
Normal file
644
internal/xds/balancer/clusterimpl/clusterimpl.go
Normal file
@@ -0,0 +1,644 @@
|
||||
/*
|
||||
*
|
||||
* 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 clusterimpl implements the xds_cluster_impl balancing policy. It
|
||||
// handles the cluster features (e.g. circuit_breaking, RPC dropping).
|
||||
//
|
||||
// Note that it doesn't handle name resolution, which is done by policy
|
||||
// xds_cluster_resolver.
|
||||
package clusterimpl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"slices"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/balancer"
|
||||
"google.golang.org/grpc/balancer/weightedroundrobin"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/tls/certprovider"
|
||||
"google.golang.org/grpc/internal/balancer/gracefulswitch"
|
||||
"google.golang.org/grpc/internal/credentials/xds"
|
||||
"google.golang.org/grpc/internal/grpclog"
|
||||
"google.golang.org/grpc/internal/grpcsync"
|
||||
"google.golang.org/grpc/internal/pretty"
|
||||
xdsinternal "google.golang.org/grpc/internal/xds"
|
||||
|
||||
"google.golang.org/grpc/internal/xds/balancer/clusterimpl/internal"
|
||||
"google.golang.org/grpc/internal/xds/balancer/loadstore"
|
||||
"google.golang.org/grpc/internal/xds/bootstrap"
|
||||
"google.golang.org/grpc/internal/xds/clients"
|
||||
"google.golang.org/grpc/internal/xds/clients/lrsclient"
|
||||
"google.golang.org/grpc/internal/xds/xdsclient"
|
||||
"google.golang.org/grpc/internal/xds/xdsclient/xdsresource"
|
||||
"google.golang.org/grpc/resolver"
|
||||
"google.golang.org/grpc/serviceconfig"
|
||||
)
|
||||
|
||||
const (
|
||||
// Name is the name of the cluster_impl balancer.
|
||||
Name = "xds_cluster_impl_experimental"
|
||||
defaultRequestCountMax = 1024
|
||||
loadStoreStopTimeout = 1 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
// Below function is no-op in actual code, but can be overridden in
|
||||
// tests to give tests visibility into exactly when certain events happen.
|
||||
clientConnUpdateHook = func() {}
|
||||
pickerUpdateHook = func() {}
|
||||
buildProvider = buildProviderFunc
|
||||
)
|
||||
|
||||
func init() {
|
||||
balancer.Register(bb{})
|
||||
}
|
||||
|
||||
type bb struct{}
|
||||
|
||||
func (bb) Build(cc balancer.ClientConn, bOpts balancer.BuildOptions) balancer.Balancer {
|
||||
b := &clusterImplBalancer{
|
||||
ClientConn: cc,
|
||||
loadWrapper: loadstore.NewWrapper(),
|
||||
requestCountMax: defaultRequestCountMax,
|
||||
}
|
||||
b.xdsHIPtr.Store(xds.NewHandshakeInfo(nil, nil, nil, "", false, false, false))
|
||||
b.logger = prefixLogger(b)
|
||||
b.child = gracefulswitch.NewBalancer(b, bOpts)
|
||||
b.logger.Infof("Created")
|
||||
|
||||
var creds credentials.TransportCredentials
|
||||
switch {
|
||||
case bOpts.DialCreds != nil:
|
||||
creds = bOpts.DialCreds
|
||||
case bOpts.CredsBundle != nil:
|
||||
creds = bOpts.CredsBundle.TransportCredentials()
|
||||
}
|
||||
if xc, ok := creds.(interface{ UsesXDS() bool }); ok && xc.UsesXDS() {
|
||||
b.xdsCredsInUse = true
|
||||
}
|
||||
b.logger.Infof("xDS credentials in use: %v", b.xdsCredsInUse)
|
||||
return b
|
||||
}
|
||||
|
||||
func (bb) Name() string {
|
||||
return Name
|
||||
}
|
||||
|
||||
func (bb) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
|
||||
return parseConfig(c)
|
||||
}
|
||||
|
||||
type clusterImplBalancer struct {
|
||||
balancer.ClientConn
|
||||
|
||||
// The following fields are set at creation time, and are read-only after
|
||||
// that, and therefore need not be protected by a mutex.
|
||||
logger *grpclog.PrefixLogger
|
||||
// TODO: #8366 - Refactor usage of loadWrapper to easily plugin a test
|
||||
// load reporter from tests.
|
||||
loadWrapper *loadstore.Wrapper
|
||||
|
||||
// The following fields are only accessed from balancer API methods, which
|
||||
// are guaranteed to be called serially by gRPC.
|
||||
xdsClient xdsclient.XDSClient // Sent down in ResolverState attributes.
|
||||
cancelLoadReport func(context.Context) // To stop reporting load through the above xDS client.
|
||||
edsServiceName string // EDS service name to report load for.
|
||||
lrsServer *bootstrap.ServerConfig // Load reporting server configuration.
|
||||
dropCategories []DropConfig // The categories for drops.
|
||||
child *gracefulswitch.Balancer
|
||||
xdsHIPtr atomic.Pointer[grpcsync.RefCounted[*xds.HandshakeInfo]] // Accessed atomically as it is shared between the balancer and the transport.
|
||||
xdsCredsInUse bool
|
||||
|
||||
// The active security configuration received from the control plane.
|
||||
securityConfig *xdsresource.SecurityConfig
|
||||
|
||||
// The following fields are protected by mu, since they are accessed in
|
||||
// balancer API methods and in methods called from the child policy.
|
||||
mu sync.Mutex
|
||||
clusterName string // The cluster name for credentials handshaking.
|
||||
inhibitPickerUpdates bool // Inhibits state updates from child policy when processing an update from the parent.
|
||||
pendingPickerUpdates bool // True if a picker update from the child policy was inhibited when processing an update from the parent.
|
||||
childState balancer.State // Most recent state update from the child policy.
|
||||
drops []*dropper // Drops implementation.
|
||||
requestCounterCluster string // The cluster name for the request counter, from LB config.
|
||||
requestCounterService string // The service name for the request counter, from LB config.
|
||||
requestCountMax uint32 // Max concurrent requests, from LB config.
|
||||
requestCounter *xdsclient.ClusterRequestsCounter // Tracks total inflight requests for a given service.
|
||||
telemetryLabels map[string]string // Telemetry labels to set on picks, from LB config.
|
||||
lrsReportEndpointMetrics *xdsresource.LRSReportEndpointMetricsConfig // LRS metrics to propagate.
|
||||
}
|
||||
|
||||
// dropRequestsPerMillion scales a drop overload's numerator and denominator to
|
||||
// a number of requests to drop per million. numerator can be as large as a
|
||||
// million (a 100% drop), so the multiplication is done in uint64 to avoid
|
||||
// overflowing a uint32, and the result is capped at a million because a drop
|
||||
// ratio above 100% is treated as 100%.
|
||||
func dropRequestsPerMillion(numerator, denominator uint32) uint32 {
|
||||
rpm := uint64(numerator) * million / uint64(denominator)
|
||||
if rpm > million {
|
||||
rpm = million
|
||||
}
|
||||
return uint32(rpm)
|
||||
}
|
||||
|
||||
// handleClusterConfigLocked updates the internal state of the balancer with the
|
||||
// new cluster configuration. It returns true if a new picker needs to be
|
||||
// generated as a result of these changes. It must be called with b.mu held.
|
||||
func (b *clusterImplBalancer) handleClusterConfigLocked(clusterConfig xdsresource.ClusterConfig) bool {
|
||||
clusterUpdate := clusterConfig.Cluster
|
||||
var updatePicker bool
|
||||
|
||||
b.telemetryLabels = clusterUpdate.TelemetryLabels
|
||||
|
||||
if !b.lrsReportEndpointMetrics.Equal(clusterUpdate.LRSReportEndpointMetrics) {
|
||||
b.lrsReportEndpointMetrics = clusterUpdate.LRSReportEndpointMetrics
|
||||
updatePicker = true
|
||||
}
|
||||
|
||||
var newDrops []DropConfig
|
||||
if clusterUpdate.ClusterType == xdsresource.ClusterTypeEDS {
|
||||
edsUpdate := clusterConfig.EndpointConfig.EDSUpdate
|
||||
newDrops = make([]DropConfig, 0, len(edsUpdate.Drops))
|
||||
for _, d := range edsUpdate.Drops {
|
||||
newDrops = append(newDrops, DropConfig{
|
||||
Category: d.Category,
|
||||
RequestsPerMillion: dropRequestsPerMillion(d.Numerator, d.Denominator),
|
||||
})
|
||||
}
|
||||
if !slices.Equal(b.dropCategories, newDrops) {
|
||||
b.dropCategories = newDrops
|
||||
b.drops = make([]*dropper, 0, len(newDrops))
|
||||
for _, c := range newDrops {
|
||||
b.drops = append(b.drops, newDropper(c))
|
||||
}
|
||||
updatePicker = true
|
||||
}
|
||||
}
|
||||
|
||||
if b.requestCounterCluster != clusterUpdate.ClusterName || b.requestCounterService != clusterUpdate.EDSServiceName {
|
||||
b.requestCounterCluster = clusterUpdate.ClusterName
|
||||
b.requestCounterService = clusterUpdate.EDSServiceName
|
||||
b.requestCounter = xdsclient.GetClusterRequestsCounter(clusterUpdate.ClusterName, clusterUpdate.EDSServiceName)
|
||||
updatePicker = true
|
||||
}
|
||||
var newRequestCountMax uint32 = 1024
|
||||
if clusterUpdate.MaxRequests != nil {
|
||||
newRequestCountMax = *clusterUpdate.MaxRequests
|
||||
}
|
||||
if b.requestCountMax != newRequestCountMax {
|
||||
b.requestCountMax = newRequestCountMax
|
||||
updatePicker = true
|
||||
}
|
||||
|
||||
return updatePicker
|
||||
}
|
||||
|
||||
func (b *clusterImplBalancer) newPickerLocked() *picker {
|
||||
return &picker{
|
||||
drops: b.drops,
|
||||
s: b.childState,
|
||||
loadStore: b.loadWrapper,
|
||||
counter: b.requestCounter,
|
||||
countMax: b.requestCountMax,
|
||||
telemetryLabels: b.telemetryLabels,
|
||||
clusterName: b.clusterName,
|
||||
metrics: b.lrsReportEndpointMetrics,
|
||||
}
|
||||
}
|
||||
|
||||
// updateLoadStore checks the config for load store, and decides whether it
|
||||
// needs to restart the load reporting stream.
|
||||
func (b *clusterImplBalancer) updateLoadStore(clusterUpdate *xdsresource.ClusterUpdate) error {
|
||||
var updateLoadClusterAndService bool
|
||||
|
||||
// ClusterName is different, restart. ClusterName is from ClusterName and
|
||||
// EDSServiceName.
|
||||
clusterName := b.getClusterName()
|
||||
if clusterName != clusterUpdate.ClusterName {
|
||||
updateLoadClusterAndService = true
|
||||
b.setClusterName(clusterUpdate.ClusterName)
|
||||
clusterName = clusterUpdate.ClusterName
|
||||
}
|
||||
if b.edsServiceName != clusterUpdate.EDSServiceName {
|
||||
updateLoadClusterAndService = true
|
||||
b.edsServiceName = clusterUpdate.EDSServiceName
|
||||
}
|
||||
if updateLoadClusterAndService {
|
||||
// This updates the clusterName and serviceName that will be reported
|
||||
// for the loads. The update here is too early, the perfect timing is
|
||||
// when the picker is updated with the new connection. But from this
|
||||
// balancer's point of view, it's impossible to tell.
|
||||
//
|
||||
// On the other hand, this will almost never happen. Each LRS policy
|
||||
// shouldn't get updated config. The parent should do a graceful switch
|
||||
// when the clusterName or serviceName is changed.
|
||||
b.loadWrapper.UpdateClusterAndService(clusterName, b.edsServiceName)
|
||||
}
|
||||
|
||||
var (
|
||||
stopOldLoadReport bool
|
||||
startNewLoadReport bool
|
||||
)
|
||||
|
||||
// Check if it's necessary to restart load report.
|
||||
if b.lrsServer == nil {
|
||||
if clusterUpdate.LRSServerConfig != nil {
|
||||
// Old is nil, new is not nil, start new LRS.
|
||||
b.lrsServer = clusterUpdate.LRSServerConfig
|
||||
startNewLoadReport = true
|
||||
}
|
||||
// Old is nil, new is nil, do nothing.
|
||||
} else if clusterUpdate.LRSServerConfig == nil {
|
||||
// Old is not nil, new is nil, stop old, don't start new.
|
||||
b.lrsServer = nil
|
||||
stopOldLoadReport = true
|
||||
} else {
|
||||
// Old is not nil, new is not nil, compare string values, if
|
||||
// different, stop old and start new.
|
||||
if !b.lrsServer.Equal(clusterUpdate.LRSServerConfig) {
|
||||
b.lrsServer = clusterUpdate.LRSServerConfig
|
||||
stopOldLoadReport = true
|
||||
startNewLoadReport = true
|
||||
}
|
||||
}
|
||||
|
||||
if stopOldLoadReport {
|
||||
if b.cancelLoadReport != nil {
|
||||
stopCtx, stopCancel := context.WithTimeout(context.Background(), loadStoreStopTimeout)
|
||||
defer stopCancel()
|
||||
b.cancelLoadReport(stopCtx)
|
||||
b.cancelLoadReport = nil
|
||||
if !startNewLoadReport {
|
||||
// If a new LRS stream will be started later, no need to update
|
||||
// it to nil here.
|
||||
b.loadWrapper.UpdateLoadStore(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
if startNewLoadReport {
|
||||
var loadStore *lrsclient.LoadStore
|
||||
if b.xdsClient != nil {
|
||||
loadStore, b.cancelLoadReport = b.xdsClient.ReportLoad(b.lrsServer)
|
||||
}
|
||||
b.loadWrapper.UpdateLoadStore(loadStore)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildProviderFunc(configs map[string]*certprovider.BuildableConfig, instanceName, certName string, wantIdentity, wantRoot bool) (certprovider.Provider, error) {
|
||||
cfg := configs[instanceName]
|
||||
provider, err := cfg.Build(certprovider.BuildOptions{
|
||||
CertName: certName,
|
||||
WantIdentity: wantIdentity,
|
||||
WantRoot: wantRoot,
|
||||
})
|
||||
if err != nil {
|
||||
// This error is not expected since the bootstrap process parses the
|
||||
// config and makes sure that it is acceptable to the plugin. Still, it
|
||||
// is possible that the plugin parses the config successfully, but its
|
||||
// Build() method errors out.
|
||||
return nil, fmt.Errorf("xds: failed to get security plugin instance (%+v): %v", cfg, err)
|
||||
}
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func (b *clusterImplBalancer) buildProviders(config *xdsresource.SecurityConfig) (certprovider.Provider, certprovider.Provider, error) {
|
||||
cpc := b.xdsClient.BootstrapConfig().CertProviderConfigs()
|
||||
var rootProvider certprovider.Provider
|
||||
if config.UseSystemRootCerts {
|
||||
rootProvider = systemRootCertsProvider{}
|
||||
} else {
|
||||
rp, err := buildProvider(cpc, config.RootInstanceName, config.RootCertName, false, true)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
rootProvider = rp
|
||||
}
|
||||
|
||||
var identityProvider certprovider.Provider
|
||||
if name, cert := config.IdentityInstanceName, config.IdentityCertName; name != "" {
|
||||
ip, err := buildProvider(cpc, name, cert, true, false)
|
||||
if err != nil {
|
||||
if rootProvider != nil {
|
||||
rootProvider.Close()
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
identityProvider = ip
|
||||
}
|
||||
return rootProvider, identityProvider, nil
|
||||
}
|
||||
|
||||
// handleSecurityConfig processes the security configuration received from the
|
||||
// management server, creates appropriate certificate provider plugins, and
|
||||
// updates the HandshakeInfo which is added as an address attribute in
|
||||
// NewSubConn() calls.
|
||||
func (b *clusterImplBalancer) handleSecurityConfig(config *xdsresource.SecurityConfig) error {
|
||||
// If xdsCredentials are not in use, i.e, the user did not want to get
|
||||
// security configuration from an xDS server, we should not be acting on the
|
||||
// received security config here. Doing so poses a security threat.
|
||||
if !b.xdsCredsInUse || config.Equal(b.securityConfig) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Security config being nil is a valid case where the management server has
|
||||
// not sent any security configuration. The xdsCredentials implementation
|
||||
// handles this by delegating to its fallback credentials.
|
||||
if config == nil {
|
||||
b.securityConfig = nil
|
||||
oldHI := b.xdsHIPtr.Swap(xds.NewHandshakeInfo(nil, nil, nil, "", false, false, false))
|
||||
if oldHI != nil {
|
||||
oldHI.Decrement()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
rootProvider, identityProvider, err := b.buildProviders(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newHI := xds.NewHandshakeInfo(rootProvider, identityProvider, config.SubjectAltNameMatchers, config.SNI, false, config.AutoSNISANValidation, config.UseAutoHostSNI)
|
||||
b.securityConfig = config
|
||||
oldHI := b.xdsHIPtr.Swap(newHI)
|
||||
if oldHI != nil {
|
||||
oldHI.Decrement()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *clusterImplBalancer) UpdateClientConnState(s balancer.ClientConnState) error {
|
||||
defer clientConnUpdateHook()
|
||||
|
||||
b.mu.Lock()
|
||||
b.inhibitPickerUpdates = true
|
||||
b.mu.Unlock()
|
||||
if b.logger.V(2) {
|
||||
b.logger.Infof("Received configuration: %s", pretty.ToJSON(s.BalancerConfig))
|
||||
}
|
||||
newConfig, ok := s.BalancerConfig.(*LBConfig)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected balancer config with type: %T", s.BalancerConfig)
|
||||
}
|
||||
|
||||
// Need to check for potential errors at the beginning of this function, so
|
||||
// that on errors, we reject the whole config, instead of applying part of
|
||||
// it.
|
||||
bb := balancer.Get(newConfig.ChildPolicy.Name)
|
||||
if bb == nil {
|
||||
return fmt.Errorf("child policy %q not registered", newConfig.ChildPolicy.Name)
|
||||
}
|
||||
|
||||
if b.xdsClient == nil {
|
||||
c := xdsclient.FromResolverState(s.ResolverState)
|
||||
if c == nil {
|
||||
return balancer.ErrBadResolverState
|
||||
}
|
||||
b.xdsClient = c
|
||||
}
|
||||
|
||||
xdsConfig := xdsresource.XDSConfigFromResolverState(s.ResolverState)
|
||||
if xdsConfig == nil {
|
||||
b.logger.Warningf("Received balancer config with no xDS config")
|
||||
return balancer.ErrBadResolverState
|
||||
}
|
||||
clusterCfg := xdsConfig.Clusters[newConfig.Cluster]
|
||||
clusterUpdate := clusterCfg.Config.Cluster
|
||||
if err := b.handleSecurityConfig(clusterUpdate.SecurityCfg); err != nil {
|
||||
// If the security config is invalid, for example, if the provider
|
||||
// instance is not found in the bootstrap config, we need to put the
|
||||
// channel in transient failure.
|
||||
return fmt.Errorf("received Cluster resource that contains invalid security config: %v", err)
|
||||
|
||||
}
|
||||
// Update load reporting config. This needs to be done before updating the
|
||||
// child policy because we need the loadStore from the updated client to be
|
||||
// passed to the ccWrapper, so that the next picker from the child policy
|
||||
// will pick up the new loadStore.
|
||||
if err := b.updateLoadStore(clusterUpdate); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Build config for the gracefulswitch balancer. It is safe to ignore JSON
|
||||
// marshaling errors here, since the config was already validated as part of
|
||||
// ParseConfig().
|
||||
cfg := []map[string]any{{newConfig.ChildPolicy.Name: newConfig.ChildPolicy.Config}}
|
||||
cfgJSON, _ := json.Marshal(cfg)
|
||||
parsedCfg, err := gracefulswitch.ParseConfig(cfgJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Addresses and sub-balancer config are sent to sub-balancer.
|
||||
err = b.child.UpdateClientConnState(balancer.ClientConnState{
|
||||
ResolverState: weightedroundrobin.SetBackendService(s.ResolverState, b.clusterName),
|
||||
BalancerConfig: parsedCfg,
|
||||
})
|
||||
|
||||
b.mu.Lock()
|
||||
updatePicker := b.handleClusterConfigLocked(clusterCfg.Config)
|
||||
// We want to send a picker update to the parent if one of the two
|
||||
// conditions are met:
|
||||
// - drop/request count config or LRS metrics config has changed *and* there
|
||||
// is already a picker from the child, or
|
||||
// - there is a pending picker update from the child (and this covers the
|
||||
// case where the drop/request config has not changed, but the child sent
|
||||
// a picker update while we were still processing config from our parent).
|
||||
if (updatePicker && b.childState.Picker != nil) || b.pendingPickerUpdates {
|
||||
b.pendingPickerUpdates = false
|
||||
b.ClientConn.UpdateState(balancer.State{
|
||||
ConnectivityState: b.childState.ConnectivityState,
|
||||
Picker: b.newPickerLocked(),
|
||||
})
|
||||
}
|
||||
b.inhibitPickerUpdates = false
|
||||
b.mu.Unlock()
|
||||
pickerUpdateHook()
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *clusterImplBalancer) ResolverError(err error) {
|
||||
b.child.ResolverError(err)
|
||||
}
|
||||
|
||||
func (b *clusterImplBalancer) updateSubConnState(_ balancer.SubConn, s balancer.SubConnState, cb func(balancer.SubConnState)) {
|
||||
// Trigger re-resolution when a SubConn turns transient failure. This is
|
||||
// necessary for the LogicalDNS in cluster_resolver policy to re-resolve.
|
||||
//
|
||||
// Note that this happens not only for the addresses from DNS, but also for
|
||||
// EDS (cluster_impl doesn't know if it's DNS or EDS, only the parent
|
||||
// knows). The parent priority policy is configured to ignore re-resolution
|
||||
// signal from the EDS children.
|
||||
if s.ConnectivityState == connectivity.TransientFailure {
|
||||
b.ClientConn.ResolveNow(resolver.ResolveNowOptions{})
|
||||
}
|
||||
|
||||
if cb != nil {
|
||||
cb(s)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *clusterImplBalancer) UpdateSubConnState(sc balancer.SubConn, s balancer.SubConnState) {
|
||||
b.logger.Errorf("UpdateSubConnState(%v, %+v) called unexpectedly", sc, s)
|
||||
}
|
||||
|
||||
func (b *clusterImplBalancer) Close() {
|
||||
b.child.Close()
|
||||
b.childState = balancer.State{}
|
||||
|
||||
if b.cancelLoadReport != nil {
|
||||
stopCtx, stopCancel := context.WithTimeout(context.Background(), loadStoreStopTimeout)
|
||||
defer stopCancel()
|
||||
b.cancelLoadReport(stopCtx)
|
||||
b.cancelLoadReport = nil
|
||||
}
|
||||
oldHI := b.xdsHIPtr.Swap(nil)
|
||||
if oldHI != nil {
|
||||
oldHI.Decrement()
|
||||
}
|
||||
b.securityConfig = nil
|
||||
b.logger.Infof("Shutdown")
|
||||
}
|
||||
|
||||
func (b *clusterImplBalancer) ExitIdle() {
|
||||
b.child.ExitIdle()
|
||||
}
|
||||
|
||||
// Override methods to accept updates from the child LB.
|
||||
|
||||
func (b *clusterImplBalancer) UpdateState(state balancer.State) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
// Inhibit sending a picker update to our parent as part of handling new
|
||||
// state from the child, if we are currently handling an update from our
|
||||
// parent. Update the childState field regardless.
|
||||
b.childState = state
|
||||
if b.inhibitPickerUpdates {
|
||||
b.pendingPickerUpdates = true
|
||||
if b.logger.V(2) {
|
||||
b.logger.Infof("Received a picker update from the child when processing an update from the parent")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
b.ClientConn.UpdateState(balancer.State{
|
||||
ConnectivityState: state.ConnectivityState,
|
||||
Picker: b.newPickerLocked(),
|
||||
})
|
||||
pickerUpdateHook()
|
||||
}
|
||||
|
||||
func (b *clusterImplBalancer) setClusterName(n string) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
b.clusterName = n
|
||||
}
|
||||
|
||||
func (b *clusterImplBalancer) getClusterName() string {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
return b.clusterName
|
||||
}
|
||||
|
||||
// scWrapper is a wrapper of SubConn with locality ID. The locality ID can be
|
||||
// retrieved from the addresses when creating SubConn.
|
||||
//
|
||||
// All SubConns passed to the child policies are wrapped in this, so that the
|
||||
// picker can get the localityID from the picked SubConn, and do load reporting.
|
||||
//
|
||||
// After wrapping, all SubConns to and from the parent ClientConn (e.g. for
|
||||
// SubConn state update, update/remove SubConn) must be the original SubConns.
|
||||
// All SubConns to and from the child policy (NewSubConn, forwarding SubConn
|
||||
// state update) must be the wrapper. The balancer keeps a map from the original
|
||||
// SubConn to the wrapper for this purpose.
|
||||
type scWrapper struct {
|
||||
balancer.SubConn
|
||||
|
||||
localityID clients.Locality
|
||||
|
||||
hostname string
|
||||
}
|
||||
|
||||
func (b *clusterImplBalancer) NewSubConn(addrs []resolver.Address, opts balancer.NewSubConnOptions) (balancer.SubConn, error) {
|
||||
clusterName := b.getClusterName()
|
||||
newAddrs := make([]resolver.Address, len(addrs))
|
||||
for i, addr := range addrs {
|
||||
newAddrs[i] = xdsinternal.SetXDSHandshakeClusterName(addr, clusterName)
|
||||
newAddrs[i] = xds.SetHandshakeInfo(newAddrs[i], &b.xdsHIPtr)
|
||||
|
||||
host := xdsresource.Hostname(addr)
|
||||
// If the hostname contains a port, strip it. Per [RFC 6066, Section
|
||||
// 3](https://www.rfc-editor.org/rfc/rfc6066.html#section-3), the SNI
|
||||
// may only contain a qualified DNS hostname, which excludes port
|
||||
// numbers.
|
||||
h, _, err := net.SplitHostPort(host)
|
||||
if err == nil {
|
||||
host = h
|
||||
}
|
||||
// Store hostname in the address attributes, so that it can be used in
|
||||
// the client handshake.
|
||||
newAddrs[i] = xds.SetAddressHostname(newAddrs[i], host)
|
||||
}
|
||||
var sc balancer.SubConn
|
||||
scw := &scWrapper{}
|
||||
if len(addrs) > 0 {
|
||||
scw.hostname = xdsresource.Hostname(addrs[0])
|
||||
scw.localityID = xdsinternal.GetLocalityID(addrs[0])
|
||||
}
|
||||
oldListener := opts.StateListener
|
||||
opts.StateListener = func(state balancer.SubConnState) {
|
||||
b.updateSubConnState(sc, state, oldListener)
|
||||
}
|
||||
sc, err := b.ClientConn.NewSubConn(newAddrs, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scw.SubConn = sc
|
||||
return scw, nil
|
||||
}
|
||||
|
||||
func (b *clusterImplBalancer) RemoveSubConn(sc balancer.SubConn) {
|
||||
b.logger.Errorf("RemoveSubConn(%v) called unexpectedly", sc)
|
||||
}
|
||||
|
||||
func (b *clusterImplBalancer) UpdateAddresses(sc balancer.SubConn, _ []resolver.Address) {
|
||||
b.logger.Errorf("UpdateAddresses(%v) called unexpectedly", sc)
|
||||
}
|
||||
|
||||
// systemRootCertsProvider implements a certprovider.Provider that returns the
|
||||
// system default root certificates for validation.
|
||||
type systemRootCertsProvider struct{}
|
||||
|
||||
func (systemRootCertsProvider) Close() {}
|
||||
|
||||
func (systemRootCertsProvider) KeyMaterial(context.Context) (*certprovider.KeyMaterial, error) {
|
||||
rootCAs, err := internal.X509SystemCertPoolFunc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &certprovider.KeyMaterial{Roots: rootCAs}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user