google.golang.org/grpc@v1.74.2/xds/internal/xdsclient/clientimpl.go (about)

     1  /*
     2   *
     3   * Copyright 2022 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 xdsclient
    20  
    21  import (
    22  	"fmt"
    23  	"sync/atomic"
    24  	"time"
    25  
    26  	"google.golang.org/grpc"
    27  	estats "google.golang.org/grpc/experimental/stats"
    28  	"google.golang.org/grpc/internal/backoff"
    29  	"google.golang.org/grpc/internal/grpclog"
    30  	"google.golang.org/grpc/internal/xds/bootstrap"
    31  
    32  	xdsbootstrap "google.golang.org/grpc/xds/bootstrap"
    33  	"google.golang.org/grpc/xds/internal/clients"
    34  	"google.golang.org/grpc/xds/internal/clients/grpctransport"
    35  	"google.golang.org/grpc/xds/internal/clients/lrsclient"
    36  	"google.golang.org/grpc/xds/internal/clients/xdsclient"
    37  	"google.golang.org/grpc/xds/internal/clients/xdsclient/metrics"
    38  )
    39  
    40  const (
    41  	// NameForServer represents the value to be passed as name when creating an xDS
    42  	// client from xDS-enabled gRPC servers. This is a well-known dedicated key
    43  	// value, and is defined in gRFC A71.
    44  	NameForServer = "#server"
    45  
    46  	defaultWatchExpiryTimeout = 15 * time.Second
    47  )
    48  
    49  var (
    50  	// The following functions are no-ops in the actual code, but can be
    51  	// overridden in tests to give them visibility into certain events.
    52  	xdsClientImplCreateHook = func(string) {}
    53  	xdsClientImplCloseHook  = func(string) {}
    54  
    55  	defaultExponentialBackoff = backoff.DefaultExponential.Backoff
    56  
    57  	xdsClientResourceUpdatesValidMetric = estats.RegisterInt64Count(estats.MetricDescriptor{
    58  		Name:        "grpc.xds_client.resource_updates_valid",
    59  		Description: "A counter of resources received that were considered valid. The counter will be incremented even for resources that have not changed.",
    60  		Unit:        "resource",
    61  		Labels:      []string{"grpc.target", "grpc.xds.server", "grpc.xds.resource_type"},
    62  		Default:     false,
    63  	})
    64  	xdsClientResourceUpdatesInvalidMetric = estats.RegisterInt64Count(estats.MetricDescriptor{
    65  		Name:        "grpc.xds_client.resource_updates_invalid",
    66  		Description: "A counter of resources received that were considered invalid.",
    67  		Unit:        "resource",
    68  		Labels:      []string{"grpc.target", "grpc.xds.server", "grpc.xds.resource_type"},
    69  		Default:     false,
    70  	})
    71  	xdsClientServerFailureMetric = estats.RegisterInt64Count(estats.MetricDescriptor{
    72  		Name:        "grpc.xds_client.server_failure",
    73  		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.",
    74  		Unit:        "failure",
    75  		Labels:      []string{"grpc.target", "grpc.xds.server"},
    76  		Default:     false,
    77  	})
    78  )
    79  
    80  // clientImpl embed xdsclient.XDSClient and implement internal XDSClient
    81  // interface with ref counting so that it can be shared by the xds resolver and
    82  // balancer implementations, across multiple ClientConns and Servers.
    83  type clientImpl struct {
    84  	*xdsclient.XDSClient // TODO: #8313 - get rid of embedding, if possible.
    85  
    86  	// The following fields are initialized at creation time and are read-only
    87  	// after that.
    88  	xdsClientConfig xdsclient.Config
    89  	bootstrapConfig *bootstrap.Config
    90  	logger          *grpclog.PrefixLogger
    91  	target          string
    92  	lrsClient       *lrsclient.LRSClient
    93  
    94  	// Accessed atomically
    95  	refCount int32
    96  }
    97  
    98  // metricsReporter implements the clients.MetricsReporter interface and uses an
    99  // underlying stats.MetricsRecorderList to record metrics.
   100  type metricsReporter struct {
   101  	recorder estats.MetricsRecorder
   102  	target   string
   103  }
   104  
   105  // ReportMetric implements the clients.MetricsReporter interface.
   106  // It receives metric data, determines the appropriate metric based on the type
   107  // of the data, and records it using the embedded MetricsRecorderList.
   108  func (mr *metricsReporter) ReportMetric(metric any) {
   109  	if mr.recorder == nil {
   110  		return
   111  	}
   112  
   113  	switch m := metric.(type) {
   114  	case *metrics.ResourceUpdateValid:
   115  		xdsClientResourceUpdatesValidMetric.Record(mr.recorder, 1, mr.target, m.ServerURI, m.ResourceType)
   116  	case *metrics.ResourceUpdateInvalid:
   117  		xdsClientResourceUpdatesInvalidMetric.Record(mr.recorder, 1, mr.target, m.ServerURI, m.ResourceType)
   118  	case *metrics.ServerFailure:
   119  		xdsClientServerFailureMetric.Record(mr.recorder, 1, mr.target, m.ServerURI)
   120  	}
   121  }
   122  
   123  func newClientImpl(config *bootstrap.Config, metricsRecorder estats.MetricsRecorder, target string) (*clientImpl, error) {
   124  	gConfig, err := buildXDSClientConfig(config, metricsRecorder, target)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  	client, err := xdsclient.New(gConfig)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  	c := &clientImpl{XDSClient: client, xdsClientConfig: gConfig, bootstrapConfig: config, target: target, refCount: 1}
   133  	c.logger = prefixLogger(c)
   134  	return c, nil
   135  }
   136  
   137  // BootstrapConfig returns the configuration read from the bootstrap file.
   138  // Callers must treat the return value as read-only.
   139  func (c *clientImpl) BootstrapConfig() *bootstrap.Config {
   140  	return c.bootstrapConfig
   141  }
   142  
   143  func (c *clientImpl) incrRef() int32 {
   144  	return atomic.AddInt32(&c.refCount, 1)
   145  }
   146  
   147  func (c *clientImpl) decrRef() int32 {
   148  	return atomic.AddInt32(&c.refCount, -1)
   149  }
   150  
   151  // buildXDSClientConfig builds the xdsclient.Config from the bootstrap.Config.
   152  func buildXDSClientConfig(config *bootstrap.Config, metricsRecorder estats.MetricsRecorder, target string) (xdsclient.Config, error) {
   153  	grpcTransportConfigs := make(map[string]grpctransport.Config)
   154  	gServerCfgMap := make(map[xdsclient.ServerConfig]*bootstrap.ServerConfig)
   155  
   156  	gAuthorities := make(map[string]xdsclient.Authority)
   157  	for name, cfg := range config.Authorities() {
   158  		// If server configs are specified in the authorities map, use that.
   159  		// Else, use the top-level server configs.
   160  		serverCfg := config.XDSServers()
   161  		if len(cfg.XDSServers) >= 1 {
   162  			serverCfg = cfg.XDSServers
   163  		}
   164  		var gServerCfg []xdsclient.ServerConfig
   165  		for _, sc := range serverCfg {
   166  			if err := populateGRPCTransportConfigsFromServerConfig(sc, grpcTransportConfigs); err != nil {
   167  				return xdsclient.Config{}, err
   168  			}
   169  			gsc := xdsclient.ServerConfig{
   170  				ServerIdentifier:       clients.ServerIdentifier{ServerURI: sc.ServerURI(), Extensions: grpctransport.ServerIdentifierExtension{ConfigName: sc.SelectedCreds().Type}},
   171  				IgnoreResourceDeletion: sc.ServerFeaturesIgnoreResourceDeletion()}
   172  			gServerCfg = append(gServerCfg, gsc)
   173  			gServerCfgMap[gsc] = sc
   174  		}
   175  		gAuthorities[name] = xdsclient.Authority{XDSServers: gServerCfg}
   176  	}
   177  
   178  	gServerCfgs := make([]xdsclient.ServerConfig, 0, len(config.XDSServers()))
   179  	for _, sc := range config.XDSServers() {
   180  		if err := populateGRPCTransportConfigsFromServerConfig(sc, grpcTransportConfigs); err != nil {
   181  			return xdsclient.Config{}, err
   182  		}
   183  		gsc := xdsclient.ServerConfig{
   184  			ServerIdentifier:       clients.ServerIdentifier{ServerURI: sc.ServerURI(), Extensions: grpctransport.ServerIdentifierExtension{ConfigName: sc.SelectedCreds().Type}},
   185  			IgnoreResourceDeletion: sc.ServerFeaturesIgnoreResourceDeletion()}
   186  		gServerCfgs = append(gServerCfgs, gsc)
   187  		gServerCfgMap[gsc] = sc
   188  	}
   189  
   190  	node := config.Node()
   191  	gNode := clients.Node{
   192  		ID:               node.GetId(),
   193  		Cluster:          node.GetCluster(),
   194  		Metadata:         node.Metadata,
   195  		UserAgentName:    node.UserAgentName,
   196  		UserAgentVersion: node.GetUserAgentVersion(),
   197  	}
   198  	if node.Locality != nil {
   199  		gNode.Locality = clients.Locality{
   200  			Region:  node.Locality.Region,
   201  			Zone:    node.Locality.Zone,
   202  			SubZone: node.Locality.SubZone,
   203  		}
   204  	}
   205  
   206  	return xdsclient.Config{
   207  		Authorities:      gAuthorities,
   208  		Servers:          gServerCfgs,
   209  		Node:             gNode,
   210  		TransportBuilder: grpctransport.NewBuilder(grpcTransportConfigs),
   211  		ResourceTypes:    supportedResourceTypes(config, gServerCfgMap),
   212  		MetricsReporter:  &metricsReporter{recorder: metricsRecorder, target: target},
   213  	}, nil
   214  }
   215  
   216  // populateGRPCTransportConfigsFromServerConfig iterates through the channel
   217  // credentials of the provided server configuration, builds credential bundles,
   218  // and populates the grpctransport.Config map.
   219  func populateGRPCTransportConfigsFromServerConfig(sc *bootstrap.ServerConfig, grpcTransportConfigs map[string]grpctransport.Config) error {
   220  	for _, cc := range sc.ChannelCreds() {
   221  		c := xdsbootstrap.GetCredentials(cc.Type)
   222  		if c == nil {
   223  			continue
   224  		}
   225  		bundle, _, err := c.Build(cc.Config)
   226  		if err != nil {
   227  			return fmt.Errorf("xds: failed to build credentials bundle from bootstrap for %q: %v", cc.Type, err)
   228  		}
   229  		grpcTransportConfigs[cc.Type] = grpctransport.Config{
   230  			Credentials: bundle,
   231  			GRPCNewClient: func(target string, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
   232  				opts = append(opts, sc.DialOptions()...)
   233  				return grpc.NewClient(target, opts...)
   234  			},
   235  		}
   236  	}
   237  	return nil
   238  }