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  }