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 }