google.golang.org/grpc@v1.74.2/xds/internal/clients/lrsclient/lrsclient.go (about)

     1  /*
     2   *
     3   * Copyright 2025 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 lrsclient provides an LRS (Load Reporting Service) client.
    20  //
    21  // See: https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/load_stats/v3/lrs.proto
    22  package lrsclient
    23  
    24  import (
    25  	"context"
    26  	"errors"
    27  	"fmt"
    28  	"sync"
    29  	"time"
    30  
    31  	"google.golang.org/grpc/grpclog"
    32  	igrpclog "google.golang.org/grpc/internal/grpclog"
    33  	"google.golang.org/grpc/xds/internal/clients"
    34  	clientsinternal "google.golang.org/grpc/xds/internal/clients/internal"
    35  	"google.golang.org/grpc/xds/internal/clients/internal/backoff"
    36  )
    37  
    38  const (
    39  	clientFeatureNoOverprovisioning = "envoy.lb.does_not_support_overprovisioning"
    40  	clientFeatureResourceWrapper    = "xds.config.resource-in-sotw"
    41  )
    42  
    43  var (
    44  	defaultExponentialBackoff = backoff.DefaultExponential.Backoff
    45  )
    46  
    47  // LRSClient is an LRS (Load Reporting Service) client.
    48  type LRSClient struct {
    49  	transportBuilder clients.TransportBuilder
    50  	node             clients.Node
    51  	backoff          func(int) time.Duration // Backoff for LRS stream failures.
    52  	logger           *igrpclog.PrefixLogger
    53  
    54  	// The LRSClient owns a bunch of streams to individual LRS servers.
    55  	//
    56  	// Once all references to a stream are dropped, the stream is closed.
    57  	mu         sync.Mutex
    58  	lrsStreams map[clients.ServerIdentifier]*streamImpl // Map from server config to in-use streamImpls.
    59  	lrsRefs    map[clients.ServerIdentifier]int         // Map from server config to number of references.
    60  }
    61  
    62  // New returns a new LRS Client configured with the provided config.
    63  func New(config Config) (*LRSClient, error) {
    64  	switch {
    65  	case config.Node.ID == "":
    66  		return nil, errors.New("lrsclient: node ID in node is empty")
    67  	case config.TransportBuilder == nil:
    68  		return nil, errors.New("lrsclient: transport builder is nil")
    69  	}
    70  
    71  	c := &LRSClient{
    72  		transportBuilder: config.TransportBuilder,
    73  		node:             config.Node,
    74  		backoff:          defaultExponentialBackoff,
    75  		lrsStreams:       make(map[clients.ServerIdentifier]*streamImpl),
    76  		lrsRefs:          make(map[clients.ServerIdentifier]int),
    77  	}
    78  	c.logger = prefixLogger(c)
    79  	return c, nil
    80  }
    81  
    82  // ReportLoad creates and returns a LoadStore for the caller to report loads
    83  // using a LoadReportingStream.
    84  //
    85  // Caller must call Stop on the returned LoadStore when they are done reporting
    86  // load to this server.
    87  func (c *LRSClient) ReportLoad(si clients.ServerIdentifier) (*LoadStore, error) {
    88  	lrs, err := c.getOrCreateLRSStream(si)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	return lrs.loadStore, nil
    93  }
    94  
    95  // getOrCreateLRSStream returns an lrs stream for the given server identifier.
    96  //
    97  // If an active lrs stream exists for the given server identifier, it is
    98  // returned. Otherwise, a new lrs stream is created and returned.
    99  func (c *LRSClient) getOrCreateLRSStream(serverIdentifier clients.ServerIdentifier) (*streamImpl, error) {
   100  	c.mu.Lock()
   101  	defer c.mu.Unlock()
   102  
   103  	if c.logger.V(2) {
   104  		c.logger.Infof("Received request for a reference to an lrs stream for server identifier %q", serverIdentifier)
   105  	}
   106  
   107  	// Use an existing stream, if one exists for this server identifier.
   108  	if s, ok := c.lrsStreams[serverIdentifier]; ok {
   109  		if c.logger.V(2) {
   110  			c.logger.Infof("Reusing an existing lrs stream for server identifier %q", serverIdentifier)
   111  		}
   112  		c.lrsRefs[serverIdentifier]++
   113  		return s, nil
   114  	}
   115  
   116  	if c.logger.V(2) {
   117  		c.logger.Infof("Creating a new lrs stream for server identifier %q", serverIdentifier)
   118  	}
   119  
   120  	l := grpclog.Component("xds")
   121  	logPrefix := clientPrefix(c)
   122  	c.logger = igrpclog.NewPrefixLogger(l, logPrefix)
   123  
   124  	// Create a new transport and create a new lrs stream, and add it to the
   125  	// map of lrs streams.
   126  	tr, err := c.transportBuilder.Build(serverIdentifier)
   127  	if err != nil {
   128  		return nil, fmt.Errorf("lrsclient: failed to create transport for server identifier %s: %v", serverIdentifier, err)
   129  	}
   130  
   131  	nodeProto := clientsinternal.NodeProto(c.node)
   132  	nodeProto.ClientFeatures = []string{clientFeatureNoOverprovisioning, clientFeatureResourceWrapper}
   133  	lrs := newStreamImpl(streamOpts{
   134  		transport: tr,
   135  		backoff:   c.backoff,
   136  		nodeProto: nodeProto,
   137  		logPrefix: logPrefix,
   138  	})
   139  
   140  	// Register a stop function that decrements the reference count, stops
   141  	// the LRS stream when the last reference is removed and closes the
   142  	// transport and removes the lrs stream and its references from the
   143  	// respective maps. Before closing the stream, it waits for the provided
   144  	// context to be done (timeout or cancellation).
   145  	stop := func(ctx context.Context) {
   146  		c.mu.Lock()
   147  		defer c.mu.Unlock()
   148  
   149  		if r, ok := c.lrsRefs[serverIdentifier]; !ok || r == 0 {
   150  			c.logger.Errorf("Attempting to stop already stopped StreamImpl")
   151  			return
   152  		}
   153  		c.lrsRefs[serverIdentifier]--
   154  		if c.lrsRefs[serverIdentifier] != 0 {
   155  			return
   156  		}
   157  
   158  		lrs.finalSendRequest <- struct{}{}
   159  
   160  		select {
   161  		case err := <-lrs.finalSendDone:
   162  			if err != nil {
   163  				c.logger.Warningf("Final send attempt failed: %v", err)
   164  			}
   165  		case <-ctx.Done():
   166  			c.logger.Warningf("Context canceled before finishing the final send attempt: %v", err)
   167  		}
   168  
   169  		lrs.cancelStream()
   170  		lrs.cancelStream = nil
   171  		lrs.logger.Infof("Stopping LRS stream")
   172  		<-lrs.doneCh
   173  
   174  		delete(c.lrsStreams, serverIdentifier)
   175  		tr.Close()
   176  	}
   177  	lrs.loadStore.stop = stop
   178  
   179  	c.lrsStreams[serverIdentifier] = lrs
   180  	c.lrsRefs[serverIdentifier] = 1
   181  
   182  	return lrs, nil
   183  }