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:
328
internal/xds/xdsclient/clientimpl.go
Normal file
328
internal/xds/xdsclient/clientimpl.go
Normal file
@@ -0,0 +1,328 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2022 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 xdsclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
estats "google.golang.org/grpc/experimental/stats"
|
||||
"google.golang.org/grpc/internal/backoff"
|
||||
"google.golang.org/grpc/internal/grpclog"
|
||||
"google.golang.org/grpc/internal/xds/bootstrap"
|
||||
|
||||
"google.golang.org/grpc/internal/xds/clients"
|
||||
"google.golang.org/grpc/internal/xds/clients/grpctransport"
|
||||
"google.golang.org/grpc/internal/xds/clients/lrsclient"
|
||||
"google.golang.org/grpc/internal/xds/clients/xdsclient"
|
||||
"google.golang.org/grpc/internal/xds/clients/xdsclient/metrics"
|
||||
xdsbootstrap "google.golang.org/grpc/xds/bootstrap"
|
||||
)
|
||||
|
||||
const (
|
||||
// NameForServer represents the value to be passed as name when creating an xDS
|
||||
// client from xDS-enabled gRPC servers. This is a well-known dedicated key
|
||||
// value, and is defined in gRFC A71.
|
||||
NameForServer = "#server"
|
||||
|
||||
defaultWatchExpiryTimeout = 15 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
// The following functions are no-ops in the actual code, but can be
|
||||
// overridden in tests to give them visibility into certain events.
|
||||
xdsClientImplCreateHook = func(string) {}
|
||||
xdsClientImplCloseHook = func(string) {}
|
||||
|
||||
defaultExponentialBackoff = backoff.DefaultExponential.Backoff
|
||||
|
||||
xdsClientResourceUpdatesValidMetric = estats.RegisterInt64Count(estats.MetricDescriptor{
|
||||
Name: "grpc.xds_client.resource_updates_valid",
|
||||
Description: "A counter of resources received that were considered valid. The counter will be incremented even for resources that have not changed.",
|
||||
Unit: "{resource}",
|
||||
Labels: []string{"grpc.target", "grpc.xds.server", "grpc.xds.resource_type"},
|
||||
Default: false,
|
||||
})
|
||||
xdsClientResourceUpdatesInvalidMetric = estats.RegisterInt64Count(estats.MetricDescriptor{
|
||||
Name: "grpc.xds_client.resource_updates_invalid",
|
||||
Description: "A counter of resources received that were considered invalid.",
|
||||
Unit: "{resource}",
|
||||
Labels: []string{"grpc.target", "grpc.xds.server", "grpc.xds.resource_type"},
|
||||
Default: false,
|
||||
})
|
||||
xdsClientServerFailureMetric = estats.RegisterInt64Count(estats.MetricDescriptor{
|
||||
Name: "grpc.xds_client.server_failure",
|
||||
Description: "A counter of xDS servers going from healthy to unhealthy. A server goes unhealthy when we have a connectivity failure or when the ADS stream fails without seeing a response message, as per gRFC A57.",
|
||||
Unit: "{failure}",
|
||||
Labels: []string{"grpc.target", "grpc.xds.server"},
|
||||
Default: false,
|
||||
})
|
||||
xdsClientConnectedMetric = estats.RegisterInt64AsyncGauge(estats.MetricDescriptor{
|
||||
Name: "grpc.xds_client.connected",
|
||||
Description: "A metric that is 1 if the xDS Client has a working ADS stream to the server, 0 otherwise.",
|
||||
Unit: "{connected}",
|
||||
Type: estats.MetricTypeIntAsyncGauge,
|
||||
Labels: []string{"grpc.target", "grpc.xds.server"},
|
||||
})
|
||||
xdsClientResourcesMetric = estats.RegisterInt64AsyncGauge(estats.MetricDescriptor{
|
||||
Name: "grpc.xds_client.resources",
|
||||
Description: "Counts of xDS resources.",
|
||||
Unit: "{resource}",
|
||||
Type: estats.MetricTypeIntAsyncGauge,
|
||||
Labels: []string{"grpc.target", "grpc.xds.authority", "grpc.xds.cache_state", "grpc.xds.resource_type"},
|
||||
})
|
||||
)
|
||||
|
||||
// clientImpl embed xdsclient.XDSClient and implement internal XDSClient
|
||||
// interface with ref counting so that it can be shared by the xds resolver and
|
||||
// balancer implementations, across multiple ClientConns and Servers.
|
||||
type clientImpl struct {
|
||||
*xdsclient.XDSClient // TODO: #8313 - get rid of embedding, if possible.
|
||||
|
||||
// The following fields are initialized at creation time and are read-only
|
||||
// after that.
|
||||
xdsClientConfig xdsclient.Config
|
||||
bootstrapConfig *bootstrap.Config
|
||||
logger *grpclog.PrefixLogger
|
||||
target string
|
||||
lrsClient *lrsclient.LRSClient
|
||||
|
||||
// Accessed atomically
|
||||
refCount int32
|
||||
}
|
||||
|
||||
// metricsReporter implements the clients.MetricsReporter interface and uses an
|
||||
// underlying stats.MetricsRecorderList to record metrics.
|
||||
type metricsReporter struct {
|
||||
recorder estats.MetricsRecorder
|
||||
target string
|
||||
}
|
||||
|
||||
// ReportMetric implements the clients.MetricsReporter interface.
|
||||
// It receives metric data, determines the appropriate metric based on the type
|
||||
// of the data, and records it using the embedded MetricsRecorderList.
|
||||
func (mr *metricsReporter) ReportMetric(metric any) {
|
||||
if mr.recorder == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch m := metric.(type) {
|
||||
case *metrics.ResourceUpdateValid:
|
||||
xdsClientResourceUpdatesValidMetric.Record(mr.recorder, 1, mr.target, m.ServerURI, m.ResourceType)
|
||||
case *metrics.ResourceUpdateInvalid:
|
||||
xdsClientResourceUpdatesInvalidMetric.Record(mr.recorder, 1, mr.target, m.ServerURI, m.ResourceType)
|
||||
case *metrics.ServerFailure:
|
||||
xdsClientServerFailureMetric.Record(mr.recorder, 1, mr.target, m.ServerURI)
|
||||
}
|
||||
}
|
||||
|
||||
func newClientImpl(config *bootstrap.Config, metricsRecorder estats.MetricsRecorder, target string, watchExpiryTimeout time.Duration) (*clientImpl, error) {
|
||||
gConfig, err := BuildXDSClientConfig(config, metricsRecorder, target, watchExpiryTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := xdsclient.New(gConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lrsC, err := lrsclient.New(lrsclient.Config{
|
||||
Node: gConfig.Node,
|
||||
TransportBuilder: gConfig.TransportBuilder,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &clientImpl{
|
||||
XDSClient: client,
|
||||
xdsClientConfig: gConfig,
|
||||
bootstrapConfig: config,
|
||||
target: target,
|
||||
refCount: 1,
|
||||
lrsClient: lrsC,
|
||||
}
|
||||
c.logger = prefixLogger(c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// BootstrapConfig returns the configuration read from the bootstrap file.
|
||||
// Callers must treat the return value as read-only.
|
||||
func (c *clientImpl) BootstrapConfig() *bootstrap.Config {
|
||||
return c.bootstrapConfig
|
||||
}
|
||||
|
||||
func (c *clientImpl) incrRef() int32 {
|
||||
return atomic.AddInt32(&c.refCount, 1)
|
||||
}
|
||||
|
||||
func (c *clientImpl) decrRef() int32 {
|
||||
return atomic.AddInt32(&c.refCount, -1)
|
||||
}
|
||||
|
||||
func buildServerConfigs(bootstrapSC []*bootstrap.ServerConfig, grpcTransportConfigs map[string]grpctransport.Config, gServerCfgMap map[xdsclient.ServerConfig]*bootstrap.ServerConfig) ([]xdsclient.ServerConfig, error) {
|
||||
var gServerCfg []xdsclient.ServerConfig
|
||||
for _, sc := range bootstrapSC {
|
||||
if err := populateGRPCTransportConfigsFromServerConfig(sc, grpcTransportConfigs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var serverFeatures xdsclient.ServerFeature
|
||||
if sc.ServerFeaturesIgnoreResourceDeletion() {
|
||||
serverFeatures = serverFeatures | xdsclient.ServerFeatureIgnoreResourceDeletion
|
||||
}
|
||||
if sc.ServerFeaturesTrustedXDSServer() {
|
||||
serverFeatures = serverFeatures | xdsclient.ServerFeatureTrustedXDSServer
|
||||
}
|
||||
gsc := xdsclient.ServerConfig{
|
||||
ServerIdentifier: clients.ServerIdentifier{
|
||||
ServerURI: sc.ServerURI(),
|
||||
Extensions: grpctransport.ServerIdentifierExtension{ConfigName: sc.SelectedChannelCreds().Type},
|
||||
},
|
||||
ServerFeature: serverFeatures,
|
||||
}
|
||||
gServerCfg = append(gServerCfg, gsc)
|
||||
gServerCfgMap[gsc] = sc
|
||||
}
|
||||
return gServerCfg, nil
|
||||
}
|
||||
|
||||
// BuildXDSClientConfig builds the xdsclient.Config from the bootstrap.Config.
|
||||
//
|
||||
// This function is exported for fuzz testing purposes only.
|
||||
func BuildXDSClientConfig(config *bootstrap.Config, metricsRecorder estats.MetricsRecorder, target string, watchExpiryTimeout time.Duration) (xdsclient.Config, error) {
|
||||
grpcTransportConfigs := make(map[string]grpctransport.Config)
|
||||
gServerCfgMap := make(map[xdsclient.ServerConfig]*bootstrap.ServerConfig)
|
||||
|
||||
gAuthorities := make(map[string]xdsclient.Authority)
|
||||
for name, cfg := range config.Authorities() {
|
||||
// If server configs are specified in the authorities map, use that.
|
||||
// Else, use the top-level server configs.
|
||||
serverCfg := config.XDSServers()
|
||||
if len(cfg.XDSServers) >= 1 {
|
||||
serverCfg = cfg.XDSServers
|
||||
}
|
||||
gsc, err := buildServerConfigs(serverCfg, grpcTransportConfigs, gServerCfgMap)
|
||||
if err != nil {
|
||||
return xdsclient.Config{}, err
|
||||
}
|
||||
gAuthorities[name] = xdsclient.Authority{XDSServers: gsc}
|
||||
}
|
||||
|
||||
gServerCfgs, err := buildServerConfigs(config.XDSServers(), grpcTransportConfigs, gServerCfgMap)
|
||||
if err != nil {
|
||||
return xdsclient.Config{}, err
|
||||
}
|
||||
|
||||
node := config.Node()
|
||||
gNode := clients.Node{
|
||||
ID: node.GetId(),
|
||||
Cluster: node.GetCluster(),
|
||||
Metadata: node.Metadata,
|
||||
UserAgentName: node.UserAgentName,
|
||||
UserAgentVersion: node.GetUserAgentVersion(),
|
||||
}
|
||||
if node.Locality != nil {
|
||||
gNode.Locality = clients.Locality{
|
||||
Region: node.Locality.Region,
|
||||
Zone: node.Locality.Zone,
|
||||
SubZone: node.Locality.SubZone,
|
||||
}
|
||||
}
|
||||
|
||||
return xdsclient.Config{
|
||||
Authorities: gAuthorities,
|
||||
Servers: gServerCfgs,
|
||||
Node: gNode,
|
||||
TransportBuilder: grpctransport.NewBuilder(grpcTransportConfigs),
|
||||
ResourceTypes: supportedResourceTypes(config, gServerCfgMap),
|
||||
MetricsReporter: &metricsReporter{recorder: metricsRecorder, target: target},
|
||||
WatchExpiryTimeout: watchExpiryTimeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// populateGRPCTransportConfigsFromServerConfig iterates through the channel
|
||||
// credentials of the provided server configuration, builds credential bundles,
|
||||
// and populates the grpctransport.Config map.
|
||||
func populateGRPCTransportConfigsFromServerConfig(sc *bootstrap.ServerConfig, grpcTransportConfigs map[string]grpctransport.Config) error {
|
||||
for _, cc := range sc.ChannelCreds() {
|
||||
c := xdsbootstrap.GetChannelCredentials(cc.Type)
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
bundle, _, err := c.Build(cc.Config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("xds: failed to build credentials bundle from bootstrap for %q: %v", cc.Type, err)
|
||||
}
|
||||
grpcTransportConfigs[cc.Type] = grpctransport.Config{
|
||||
Credentials: bundle,
|
||||
GRPCNewClient: func(target string, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
|
||||
opts = append(opts, sc.DialOptions()...)
|
||||
return grpc.NewClient(target, opts...)
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterAsyncReporter adapts the generic clients.AsyncReporter to the
|
||||
// estats.AsyncMetricReporter interface and registers it.
|
||||
func (mr *metricsReporter) RegisterAsyncReporter(reporter clients.AsyncReporter) func() {
|
||||
if mr.recorder == nil || reporter == nil {
|
||||
return func() {}
|
||||
}
|
||||
|
||||
// Define which metrics we intend to report for OTel registration.
|
||||
descriptors := []estats.AsyncMetric{
|
||||
xdsClientConnectedMetric,
|
||||
xdsClientResourcesMetric,
|
||||
}
|
||||
|
||||
// Create the callback wrapper.
|
||||
// This function is invoked by the stats system during a scrape.
|
||||
cbWrapper := func(rec estats.AsyncMetricsRecorder) error {
|
||||
wrapper := &asyncMetricsRecorderAdapter{
|
||||
target: mr.target,
|
||||
delegate: rec,
|
||||
}
|
||||
reporter.Report(wrapper)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Register with the underlying gRPC stats recorder.
|
||||
return mr.recorder.RegisterAsyncReporter(estats.AsyncMetricReporterFunc(cbWrapper), descriptors...)
|
||||
}
|
||||
|
||||
// asyncMetricsRecorderAdapter adapts estats.AsyncMetricsRecorder to clients.AsyncMetricsRecorder.
|
||||
type asyncMetricsRecorderAdapter struct {
|
||||
target string
|
||||
delegate estats.AsyncMetricsRecorder
|
||||
}
|
||||
|
||||
func (a *asyncMetricsRecorderAdapter) ReportMetric(metric any) {
|
||||
switch m := metric.(type) {
|
||||
case *metrics.XDSClientConnected:
|
||||
// Record: grpc.xds_client.connected
|
||||
// Labels: grpc.target, grpc.xds.server
|
||||
a.delegate.RecordInt64AsyncGauge(xdsClientConnectedMetric, m.Value, a.target, m.ServerURI)
|
||||
case *metrics.XDSClientResourceStats:
|
||||
// Record: grpc.xds_client.resources
|
||||
// Labels: grpc.target, grpc.xds.authority, grpc.xds.cache_state, grpc.xds.resource_type
|
||||
a.delegate.RecordInt64AsyncGauge(xdsClientResourcesMetric, m.Count, a.target, m.Authority, m.CacheState, m.ResourceType)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user