google.golang.org/grpc@v1.74.2/xds/internal/clients/grpctransport/grpc_transport.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 grpctransport provides an implementation of the
    20  // clients.TransportBuilder interface using gRPC.
    21  package grpctransport
    22  
    23  import (
    24  	"context"
    25  	"fmt"
    26  	"sync"
    27  	"time"
    28  
    29  	"google.golang.org/grpc"
    30  	"google.golang.org/grpc/credentials"
    31  	"google.golang.org/grpc/grpclog"
    32  	"google.golang.org/grpc/keepalive"
    33  	"google.golang.org/grpc/xds/internal/clients"
    34  )
    35  
    36  var (
    37  	logger = grpclog.Component("grpctransport")
    38  )
    39  
    40  // ServerIdentifierExtension holds settings for connecting to a gRPC server,
    41  // such as an xDS management or an LRS server.
    42  //
    43  // It must be set by value (not pointer) in the
    44  // clients.ServerIdentifier.Extensions field (See Example).
    45  type ServerIdentifierExtension struct {
    46  	// ConfigName is the name of the configuration to use for this transport.
    47  	// It must be present as a key in the map of configs passed to NewBuilder.
    48  	ConfigName string
    49  }
    50  
    51  // Builder creates gRPC-based Transports. It must be paired with ServerIdentifiers
    52  // that contain an Extension field of type ServerIdentifierExtension.
    53  type Builder struct {
    54  	// configs is a map of configuration names to their respective Config.
    55  	configs map[string]Config
    56  
    57  	mu sync.Mutex
    58  	// connections is a map of clients.ServerIdentifiers in use by the Builder
    59  	// to connect to different servers.
    60  	connections map[clients.ServerIdentifier]*grpc.ClientConn
    61  	// refs tracks the number of active references to each connection.
    62  	refs map[clients.ServerIdentifier]int
    63  }
    64  
    65  // Config defines the configuration for connecting to a gRPC server, including
    66  // credentials and an optional custom new client function.
    67  type Config struct {
    68  	// Credentials is the credentials bundle to be used for the connection.
    69  	Credentials credentials.Bundle
    70  	// GRPCNewClient is an optional custom function to establish a gRPC connection.
    71  	// If nil, grpc.NewClient will be used as the default.
    72  	GRPCNewClient func(target string, opts ...grpc.DialOption) (*grpc.ClientConn, error)
    73  }
    74  
    75  // NewBuilder provides a builder for creating gRPC-based Transports using
    76  // the credentials from provided map of credentials names to
    77  // credentials.Bundle.
    78  func NewBuilder(configs map[string]Config) *Builder {
    79  	return &Builder{
    80  		configs:     configs,
    81  		connections: make(map[clients.ServerIdentifier]*grpc.ClientConn),
    82  		refs:        make(map[clients.ServerIdentifier]int),
    83  	}
    84  }
    85  
    86  // Build returns a gRPC-based clients.Transport.
    87  //
    88  // The Extension field of the ServerIdentifier must be a ServerIdentifierExtension.
    89  func (b *Builder) Build(si clients.ServerIdentifier) (clients.Transport, error) {
    90  	if si.ServerURI == "" {
    91  		return nil, fmt.Errorf("grpctransport: ServerURI is not set in ServerIdentifier")
    92  	}
    93  	if si.Extensions == nil {
    94  		return nil, fmt.Errorf("grpctransport: Extensions is not set in ServerIdentifier")
    95  	}
    96  	sce, ok := si.Extensions.(ServerIdentifierExtension)
    97  	if !ok {
    98  		return nil, fmt.Errorf("grpctransport: Extensions field is %T, but must be %T in ServerIdentifier", si.Extensions, ServerIdentifierExtension{})
    99  	}
   100  
   101  	config, ok := b.configs[sce.ConfigName]
   102  	if !ok {
   103  		return nil, fmt.Errorf("grpctransport: unknown config name %q specified in ServerIdentifierExtension", sce.ConfigName)
   104  	}
   105  	if config.Credentials == nil {
   106  		return nil, fmt.Errorf("grpctransport: config %q has nil credentials bundle", sce.ConfigName)
   107  	}
   108  
   109  	b.mu.Lock()
   110  	defer b.mu.Unlock()
   111  
   112  	if cc, ok := b.connections[si]; ok {
   113  		if logger.V(2) {
   114  			logger.Info("Reusing existing connection to the server for ServerIdentifier: %v", si)
   115  		}
   116  		b.refs[si]++
   117  		tr := &grpcTransport{cc: cc}
   118  		tr.cleanup = b.cleanupFunc(si, tr)
   119  		return tr, nil
   120  	}
   121  
   122  	// Create a new gRPC client/channel for the server with the provided
   123  	// credentials, server URI, and a byte codec to send and receive messages.
   124  	// Also set a static keepalive configuration that is common across gRPC
   125  	// language implementations.
   126  	kpCfg := grpc.WithKeepaliveParams(keepalive.ClientParameters{
   127  		Time:    5 * time.Minute,
   128  		Timeout: 20 * time.Second,
   129  	})
   130  	dopts := []grpc.DialOption{kpCfg, grpc.WithCredentialsBundle(config.Credentials), grpc.WithDefaultCallOptions(grpc.ForceCodec(&byteCodec{}))}
   131  	newClientFunc := grpc.NewClient
   132  	if config.GRPCNewClient != nil {
   133  		newClientFunc = config.GRPCNewClient
   134  	}
   135  	cc, err := newClientFunc(si.ServerURI, dopts...)
   136  	if err != nil {
   137  		return nil, fmt.Errorf("grpctransport: failed to create connection to server %q: %v", si.ServerURI, err)
   138  	}
   139  	tr := &grpcTransport{cc: cc}
   140  	// Register a cleanup function that decrements the refs to the gRPC
   141  	// transport each time Close() is called to close it and remove from
   142  	// transports and connections map if last reference is being released.
   143  	tr.cleanup = b.cleanupFunc(si, tr)
   144  
   145  	// Add the newly created connection to the maps to re-use the transport
   146  	// channel and track references.
   147  	b.connections[si] = cc
   148  	b.refs[si] = 1
   149  
   150  	if logger.V(2) {
   151  		logger.Info("Created a new transport to the server for ServerIdentifier: %v", si)
   152  	}
   153  	return tr, nil
   154  }
   155  
   156  func (b *Builder) cleanupFunc(si clients.ServerIdentifier, tr *grpcTransport) func() {
   157  	return sync.OnceFunc(func() {
   158  		b.mu.Lock()
   159  		defer b.mu.Unlock()
   160  
   161  		b.refs[si]--
   162  		if b.refs[si] != 0 {
   163  			return
   164  		}
   165  
   166  		tr.cc.Close()
   167  		tr.cc = nil
   168  		delete(b.connections, si)
   169  		delete(b.refs, si)
   170  	})
   171  }
   172  
   173  type grpcTransport struct {
   174  	cc *grpc.ClientConn
   175  
   176  	// cleanup is the function to be invoked for releasing the references to
   177  	// the gRPC transport each time Close() is called.
   178  	cleanup func()
   179  }
   180  
   181  // NewStream creates a new gRPC stream to the server for the specified method.
   182  func (g *grpcTransport) NewStream(ctx context.Context, method string) (clients.Stream, error) {
   183  	s, err := g.cc.NewStream(ctx, &grpc.StreamDesc{ClientStreams: true, ServerStreams: true}, method)
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  	return &stream{stream: s}, nil
   188  }
   189  
   190  // Close closes the gRPC channel to the server.
   191  func (g *grpcTransport) Close() {
   192  	g.cleanup()
   193  }
   194  
   195  type stream struct {
   196  	stream grpc.ClientStream
   197  }
   198  
   199  // Send sends a message to the server.
   200  func (s *stream) Send(msg []byte) error {
   201  	return s.stream.SendMsg(msg)
   202  }
   203  
   204  // Recv receives a message from the server.
   205  func (s *stream) Recv() ([]byte, error) {
   206  	var typedRes []byte
   207  
   208  	if err := s.stream.RecvMsg(&typedRes); err != nil {
   209  		return nil, err
   210  	}
   211  	return typedRes, nil
   212  }
   213  
   214  // byteCodec here is still sending proto messages. It's just they are
   215  // in []byte form.
   216  type byteCodec struct{}
   217  
   218  func (c *byteCodec) Marshal(v any) ([]byte, error) {
   219  	if b, ok := v.([]byte); ok {
   220  		return b, nil
   221  	}
   222  	return nil, fmt.Errorf("grpctransport: message is %T, but must be a []byte", v)
   223  }
   224  
   225  func (c *byteCodec) Unmarshal(data []byte, v any) error {
   226  	if b, ok := v.(*[]byte); ok {
   227  		*b = data
   228  		return nil
   229  	}
   230  	return fmt.Errorf("grpctransport: target is %T, but must be *[]byte", v)
   231  }
   232  
   233  func (c *byteCodec) Name() string {
   234  	// Return "" to ensure the Content-Type header is "application/grpc",
   235  	// which is expected by standard gRPC servers for protobuf messages.
   236  	return ""
   237  }