vitess.io/vitess@v0.16.2/go/vt/grpcclient/client.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package grpcclient contains utility methods for gRPC client implementations 18 // to use. It also supports plug-in authentication. 19 package grpcclient 20 21 import ( 22 "context" 23 "crypto/tls" 24 "time" 25 26 grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" 27 grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" 28 "github.com/spf13/pflag" 29 "google.golang.org/grpc" 30 "google.golang.org/grpc/credentials" 31 "google.golang.org/grpc/credentials/insecure" 32 "google.golang.org/grpc/keepalive" 33 34 "vitess.io/vitess/go/trace" 35 "vitess.io/vitess/go/vt/grpccommon" 36 "vitess.io/vitess/go/vt/log" 37 "vitess.io/vitess/go/vt/servenv" 38 "vitess.io/vitess/go/vt/vttls" 39 ) 40 41 var ( 42 keepaliveTime = 10 * time.Second 43 keepaliveTimeout = 10 * time.Second 44 initialConnWindowSize int 45 initialWindowSize int 46 47 // every vitess binary that makes grpc client-side calls. 48 grpcclientBinaries = []string{ 49 "mysqlctld", 50 "vtadmin", 51 "vtbackup", 52 "vtbench", 53 "vtclient", 54 "vtctl", 55 "vtctlclient", 56 "vtctld", 57 "vtgate", 58 "vtgateclienttest", 59 "vtgr", 60 "vtorc", 61 "vttablet", 62 "vttestserver", 63 } 64 ) 65 66 func RegisterFlags(fs *pflag.FlagSet) { 67 fs.DurationVar(&keepaliveTime, "grpc_keepalive_time", keepaliveTime, "After a duration of this time, if the client doesn't see any activity, it pings the server to see if the transport is still alive.") 68 fs.DurationVar(&keepaliveTimeout, "grpc_keepalive_timeout", keepaliveTimeout, "After having pinged for keepalive check, the client waits for a duration of Timeout and if no activity is seen even after that the connection is closed.") 69 fs.IntVar(&initialConnWindowSize, "grpc_initial_conn_window_size", initialConnWindowSize, "gRPC initial connection window size") 70 fs.IntVar(&initialWindowSize, "grpc_initial_window_size", initialWindowSize, "gRPC initial window size") 71 fs.StringVar(&compression, "grpc_compression", compression, "Which protocol to use for compressing gRPC. Default: nothing. Supported: snappy") 72 73 fs.StringVar(&credsFile, "grpc_auth_static_client_creds", credsFile, "When using grpc_static_auth in the server, this file provides the credentials to use to authenticate with server.") 74 } 75 76 func init() { 77 for _, cmd := range grpcclientBinaries { 78 servenv.OnParseFor(cmd, RegisterFlags) 79 } 80 } 81 82 // FailFast is a self-documenting type for the grpc.FailFast. 83 type FailFast bool 84 85 // grpcDialOptions is a registry of functions that append grpcDialOption to use when dialing a service 86 var grpcDialOptions []func(opts []grpc.DialOption) ([]grpc.DialOption, error) 87 88 // RegisterGRPCDialOptions registers an implementation of AuthServer. 89 func RegisterGRPCDialOptions(grpcDialOptionsFunc func(opts []grpc.DialOption) ([]grpc.DialOption, error)) { 90 grpcDialOptions = append(grpcDialOptions, grpcDialOptionsFunc) 91 } 92 93 // Dial creates a grpc connection to the given target. 94 // failFast is a non-optional parameter because callers are required to specify 95 // what that should be. 96 func Dial(target string, failFast FailFast, opts ...grpc.DialOption) (*grpc.ClientConn, error) { 97 return DialContext(context.Background(), target, failFast, opts...) 98 } 99 100 // DialContext creates a grpc connection to the given target. Setup steps are 101 // covered by the context deadline, and, if WithBlock is specified in the dial 102 // options, connection establishment steps are covered by the context as well. 103 // 104 // failFast is a non-optional parameter because callers are required to specify 105 // what that should be. 106 func DialContext(ctx context.Context, target string, failFast FailFast, opts ...grpc.DialOption) (*grpc.ClientConn, error) { 107 msgSize := grpccommon.MaxMessageSize() 108 newopts := []grpc.DialOption{ 109 grpc.WithDefaultCallOptions( 110 grpc.MaxCallRecvMsgSize(msgSize), 111 grpc.MaxCallSendMsgSize(msgSize), 112 grpc.WaitForReady(bool(!failFast)), 113 ), 114 } 115 116 if keepaliveTime != 0 || keepaliveTimeout != 0 { 117 kp := keepalive.ClientParameters{ 118 // After a duration of this time if the client doesn't see any activity it pings the server to see if the transport is still alive. 119 Time: keepaliveTime, 120 // After having pinged for keepalive check, the client waits for a duration of Timeout and if no activity is seen even after that 121 // the connection is closed. (This will eagerly fail inflight grpc requests even if they don't have timeouts.) 122 Timeout: keepaliveTimeout, 123 PermitWithoutStream: true, 124 } 125 newopts = append(newopts, grpc.WithKeepaliveParams(kp)) 126 } 127 128 if initialConnWindowSize != 0 { 129 newopts = append(newopts, grpc.WithInitialConnWindowSize(int32(initialConnWindowSize))) 130 } 131 132 if initialWindowSize != 0 { 133 newopts = append(newopts, grpc.WithInitialWindowSize(int32(initialWindowSize))) 134 } 135 136 newopts = append(newopts, opts...) 137 var err error 138 for _, grpcDialOptionInitializer := range grpcDialOptions { 139 newopts, err = grpcDialOptionInitializer(newopts) 140 if err != nil { 141 log.Fatalf("There was an error initializing client grpc.DialOption: %v", err) 142 } 143 } 144 145 newopts = append(newopts, interceptors()...) 146 147 return grpc.DialContext(ctx, target, newopts...) 148 } 149 150 func interceptors() []grpc.DialOption { 151 builder := &clientInterceptorBuilder{} 152 if grpccommon.EnableGRPCPrometheus() { 153 builder.Add(grpc_prometheus.StreamClientInterceptor, grpc_prometheus.UnaryClientInterceptor) 154 } 155 trace.AddGrpcClientOptions(builder.Add) 156 return builder.Build() 157 } 158 159 // SecureDialOption returns the gRPC dial option to use for the 160 // given client connection. It is either using TLS, or Insecure if 161 // nothing is set. 162 func SecureDialOption(cert, key, ca, crl, name string) (grpc.DialOption, error) { 163 // No security options set, just return. 164 if (cert == "" || key == "") && ca == "" { 165 return grpc.WithTransportCredentials(insecure.NewCredentials()), nil 166 } 167 168 // Load the config. At this point we know 169 // we want a strict config with verify identity. 170 config, err := vttls.ClientConfig(vttls.VerifyIdentity, cert, key, ca, crl, name, tls.VersionTLS12) 171 if err != nil { 172 return nil, err 173 } 174 175 // Create the creds server options. 176 creds := credentials.NewTLS(config) 177 return grpc.WithTransportCredentials(creds), nil 178 } 179 180 // Allows for building a chain of interceptors without knowing the total size up front 181 type clientInterceptorBuilder struct { 182 unaryInterceptors []grpc.UnaryClientInterceptor 183 streamInterceptors []grpc.StreamClientInterceptor 184 } 185 186 // Add adds interceptors to the chain of interceptors 187 func (collector *clientInterceptorBuilder) Add(s grpc.StreamClientInterceptor, u grpc.UnaryClientInterceptor) { 188 collector.unaryInterceptors = append(collector.unaryInterceptors, u) 189 collector.streamInterceptors = append(collector.streamInterceptors, s) 190 } 191 192 // Build returns DialOptions to add to the grpc.Dial call 193 func (collector *clientInterceptorBuilder) Build() []grpc.DialOption { 194 switch len(collector.unaryInterceptors) + len(collector.streamInterceptors) { 195 case 0: 196 return []grpc.DialOption{} 197 default: 198 return []grpc.DialOption{ 199 grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(collector.unaryInterceptors...)), 200 grpc.WithStreamInterceptor(grpc_middleware.ChainStreamClient(collector.streamInterceptors...)), 201 } 202 } 203 }