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 }