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
176 lines
5.5 KiB
Go
176 lines
5.5 KiB
Go
/*
|
|
*
|
|
* Copyright 2025 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 lrsclient provides an LRS (Load Reporting Service) client.
|
|
//
|
|
// See: https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/load_stats/v3/lrs.proto
|
|
package lrsclient
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
igrpclog "google.golang.org/grpc/internal/grpclog"
|
|
"google.golang.org/grpc/internal/xds/clients"
|
|
clientsinternal "google.golang.org/grpc/internal/xds/clients/internal"
|
|
"google.golang.org/grpc/internal/xds/clients/internal/backoff"
|
|
)
|
|
|
|
const (
|
|
clientFeatureNoOverprovisioning = "envoy.lb.does_not_support_overprovisioning"
|
|
clientFeatureResourceWrapper = "xds.config.resource-in-sotw"
|
|
)
|
|
|
|
var (
|
|
defaultExponentialBackoff = backoff.DefaultExponential.Backoff
|
|
)
|
|
|
|
// LRSClient is an LRS (Load Reporting Service) client.
|
|
type LRSClient struct {
|
|
transportBuilder clients.TransportBuilder
|
|
node clients.Node
|
|
backoff func(int) time.Duration // Backoff for LRS stream failures.
|
|
logger *igrpclog.PrefixLogger
|
|
|
|
// The LRSClient owns a bunch of streams to individual LRS servers.
|
|
//
|
|
// Once all references to a stream are dropped, the stream is closed.
|
|
mu sync.Mutex
|
|
lrsStreams map[clients.ServerIdentifier]*streamImpl // Map from server config to in-use streamImpls.
|
|
lrsRefs map[clients.ServerIdentifier]int // Map from server config to number of references.
|
|
}
|
|
|
|
// New returns a new LRS Client configured with the provided config.
|
|
func New(config Config) (*LRSClient, error) {
|
|
if config.TransportBuilder == nil {
|
|
return nil, errors.New("lrsclient: transport builder is nil")
|
|
}
|
|
|
|
c := &LRSClient{
|
|
transportBuilder: config.TransportBuilder,
|
|
node: config.Node,
|
|
backoff: defaultExponentialBackoff,
|
|
lrsStreams: make(map[clients.ServerIdentifier]*streamImpl),
|
|
lrsRefs: make(map[clients.ServerIdentifier]int),
|
|
}
|
|
c.logger = prefixLogger(c)
|
|
return c, nil
|
|
}
|
|
|
|
// ReportLoad creates and returns a LoadStore for the caller to report loads
|
|
// using a LoadReportingStream.
|
|
//
|
|
// Caller must call Stop on the returned LoadStore when they are done reporting
|
|
// load to this server.
|
|
func (c *LRSClient) ReportLoad(si clients.ServerIdentifier) (*LoadStore, error) {
|
|
lrs, err := c.getOrCreateLRSStream(si)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return lrs.loadStore, nil
|
|
}
|
|
|
|
// getOrCreateLRSStream returns an lrs stream for the given server identifier.
|
|
//
|
|
// If an active lrs stream exists for the given server identifier, it is
|
|
// returned. Otherwise, a new lrs stream is created and returned.
|
|
func (c *LRSClient) getOrCreateLRSStream(serverIdentifier clients.ServerIdentifier) (*streamImpl, error) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
if c.logger.V(2) {
|
|
c.logger.Infof("Received request for a reference to an lrs stream for server identifier %q", serverIdentifier)
|
|
}
|
|
|
|
// Use an existing stream, if one exists for this server identifier.
|
|
if s, ok := c.lrsStreams[serverIdentifier]; ok {
|
|
if c.logger.V(2) {
|
|
c.logger.Infof("Reusing an existing lrs stream for server identifier %q", serverIdentifier)
|
|
}
|
|
c.lrsRefs[serverIdentifier]++
|
|
return s, nil
|
|
}
|
|
|
|
if c.logger.V(2) {
|
|
c.logger.Infof("Creating a new lrs stream for server identifier %q", serverIdentifier)
|
|
}
|
|
|
|
// Create a new transport and create a new lrs stream, and add it to the
|
|
// map of lrs streams.
|
|
tr, err := c.transportBuilder.Build(serverIdentifier)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("lrsclient: failed to create transport for server identifier %s: %v", serverIdentifier, err)
|
|
}
|
|
|
|
nodeProto := clientsinternal.NodeProto(c.node)
|
|
nodeProto.ClientFeatures = []string{clientFeatureNoOverprovisioning, clientFeatureResourceWrapper}
|
|
lrs := newStreamImpl(streamOpts{
|
|
transport: tr,
|
|
backoff: c.backoff,
|
|
nodeProto: nodeProto,
|
|
logPrefix: clientPrefix(c),
|
|
})
|
|
|
|
// Register a stop function that decrements the reference count, stops
|
|
// the LRS stream when the last reference is removed and closes the
|
|
// transport and removes the lrs stream and its references from the
|
|
// respective maps. Before closing the stream, it waits for the provided
|
|
// context to be done (timeout or cancellation).
|
|
stop := func(ctx context.Context) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
if r, ok := c.lrsRefs[serverIdentifier]; !ok || r == 0 {
|
|
c.logger.Errorf("Attempting to stop already stopped StreamImpl")
|
|
return
|
|
}
|
|
c.lrsRefs[serverIdentifier]--
|
|
if c.lrsRefs[serverIdentifier] != 0 {
|
|
return
|
|
}
|
|
|
|
lrs.finalSendRequest <- struct{}{}
|
|
|
|
select {
|
|
case err := <-lrs.finalSendDone:
|
|
if err != nil {
|
|
c.logger.Warningf("Final send attempt failed: %v", err)
|
|
}
|
|
case <-ctx.Done():
|
|
c.logger.Warningf("Context canceled before finishing the final send attempt: %v", err)
|
|
}
|
|
|
|
lrs.cancelStream()
|
|
lrs.cancelStream = nil
|
|
lrs.logger.Infof("Stopping LRS stream")
|
|
<-lrs.doneCh
|
|
|
|
delete(c.lrsStreams, serverIdentifier)
|
|
tr.Close()
|
|
}
|
|
lrs.loadStore.stop = stop
|
|
|
|
c.lrsStreams[serverIdentifier] = lrs
|
|
c.lrsRefs[serverIdentifier] = 1
|
|
|
|
return lrs, nil
|
|
}
|