vitess.io/vitess@v0.16.2/go/vt/vtadmin/vtctldclient/proxy.go (about)

     1  /*
     2  Copyright 2021 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 vtctldclient
    18  
    19  import (
    20  	"context"
    21  	"sync"
    22  	"time"
    23  
    24  	"google.golang.org/grpc/credentials/insecure"
    25  
    26  	"google.golang.org/grpc"
    27  	grpcresolver "google.golang.org/grpc/resolver"
    28  
    29  	"vitess.io/vitess/go/trace"
    30  	"vitess.io/vitess/go/vt/grpcclient"
    31  	"vitess.io/vitess/go/vt/log"
    32  	"vitess.io/vitess/go/vt/vtadmin/cluster/resolver"
    33  	"vitess.io/vitess/go/vt/vtadmin/debug"
    34  	"vitess.io/vitess/go/vt/vtadmin/vtadminproto"
    35  	"vitess.io/vitess/go/vt/vtctl/grpcvtctldclient"
    36  	"vitess.io/vitess/go/vt/vtctl/vtctldclient"
    37  
    38  	vtadminpb "vitess.io/vitess/go/vt/proto/vtadmin"
    39  	vtctlservicepb "vitess.io/vitess/go/vt/proto/vtctlservice"
    40  )
    41  
    42  // Proxy defines the connection interface of a proxied vtctldclient used by
    43  // VTAdmin clusters.
    44  type Proxy interface {
    45  	// Close closes the underlying vtctldclient connection. This is a no-op if
    46  	// the Proxy has no current, valid connection. It is safe to call repeatedly.
    47  	//
    48  	// Once closed, a proxy is not safe for reuse.
    49  	Close() error
    50  
    51  	vtctlservicepb.VtctldClient
    52  }
    53  
    54  // ClientProxy implements the Proxy interface relying on a discovery.Discovery
    55  // implementation to handle vtctld discovery and connection management.
    56  type ClientProxy struct {
    57  	vtctldclient.VtctldClient // embedded to provide easy implementation of the vtctlservicepb.VtctldClient interface
    58  
    59  	cluster *vtadminpb.Cluster
    60  	creds   *grpcclient.StaticAuthClientCreds
    61  	cfg     *Config
    62  
    63  	// DialFunc is called to open a new vtctdclient connection. In production,
    64  	// this should always be grpcvtctldclient.NewWithDialOpts, but it is
    65  	// exported for testing purposes.
    66  	dialFunc func(addr string, ff grpcclient.FailFast, opts ...grpc.DialOption) (vtctldclient.VtctldClient, error)
    67  	resolver grpcresolver.Builder
    68  
    69  	m        sync.Mutex
    70  	closed   bool
    71  	dialedAt time.Time
    72  }
    73  
    74  // New returns a ClientProxy to the given cluster. When Dial-ing, it will use
    75  // the given discovery implementation to find a vtctld to connect to, and the
    76  // given creds to dial the underlying gRPC connection, both of which are
    77  // provided by the Config.
    78  //
    79  // It does not open a connection to a vtctld; users must call Dial before first
    80  // use.
    81  func New(ctx context.Context, cfg *Config) (*ClientProxy, error) {
    82  	dialFunc := cfg.dialFunc
    83  	if dialFunc == nil {
    84  		dialFunc = grpcvtctldclient.NewWithDialOpts
    85  	}
    86  
    87  	proxy := ClientProxy{
    88  		cfg:      cfg,
    89  		cluster:  cfg.Cluster,
    90  		creds:    cfg.Credentials,
    91  		dialFunc: dialFunc,
    92  		resolver: cfg.ResolverOptions.NewBuilder(cfg.Cluster.Id),
    93  		closed:   true,
    94  	}
    95  
    96  	if err := proxy.dial(ctx); err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	return &proxy, nil
   101  }
   102  
   103  // dial invokes a grpc.Dial call with the discovery-backed resolver for vtctlds
   104  // in the proxy's cluster.
   105  //
   106  // it is called once at ClientProxy instantiation (in New()).
   107  func (vtctld *ClientProxy) dial(ctx context.Context) error {
   108  	span, _ := trace.NewSpan(ctx, "VtctldClientProxy.Dial")
   109  	defer span.Finish()
   110  
   111  	vtadminproto.AnnotateClusterSpan(vtctld.cluster, span)
   112  	span.Annotate("is_using_credentials", vtctld.creds != nil)
   113  
   114  	opts := []grpc.DialOption{
   115  		// TODO: make configurable. right now, omitting this and attempting
   116  		// to not use TLS results in:
   117  		//		grpc: no transport security set (use grpc.WithInsecure() explicitly or set credentials)
   118  		grpc.WithTransportCredentials(insecure.NewCredentials()),
   119  	}
   120  
   121  	if vtctld.creds != nil {
   122  		opts = append(opts, grpc.WithPerRPCCredentials(vtctld.creds))
   123  	}
   124  
   125  	opts = append(opts, grpc.WithResolvers(vtctld.resolver))
   126  
   127  	// TODO: update dialFunc to take ctx as first arg.
   128  	client, err := vtctld.dialFunc(resolver.DialAddr(vtctld.resolver, "vtctld"), grpcclient.FailFast(false), opts...)
   129  	if err != nil {
   130  		return err
   131  	}
   132  
   133  	log.Infof("Established gRPC connection to vtctld\n")
   134  
   135  	vtctld.m.Lock()
   136  	defer vtctld.m.Unlock()
   137  
   138  	vtctld.dialedAt = time.Now()
   139  	vtctld.VtctldClient = client
   140  	vtctld.closed = false
   141  
   142  	return nil
   143  }
   144  
   145  // Close is part of the Proxy interface.
   146  func (vtctld *ClientProxy) Close() error {
   147  	vtctld.m.Lock()
   148  	defer vtctld.m.Unlock()
   149  
   150  	if vtctld.VtctldClient == nil {
   151  		vtctld.closed = true
   152  
   153  		return nil
   154  	}
   155  
   156  	// TODO: (ajm188) Figure out if this comment is still accurate.
   157  	// Mark the vtctld connection as "closed" from the proxy side even if
   158  	// the client connection does not shut down cleanly. This makes VTAdmin's dialer more resilient,
   159  	// but, as a caveat, it _can_ potentially leak improperly-closed gRPC connections.
   160  	defer func() { vtctld.closed = true }()
   161  
   162  	return vtctld.VtctldClient.Close()
   163  }
   164  
   165  // Debug implements debug.Debuggable for ClientProxy.
   166  func (vtctld *ClientProxy) Debug() map[string]any {
   167  	vtctld.m.Lock()
   168  	defer vtctld.m.Unlock()
   169  
   170  	m := map[string]any{
   171  		"is_connected": !vtctld.closed,
   172  	}
   173  
   174  	if vtctld.creds != nil {
   175  		m["credentials"] = map[string]any{
   176  			"source":   vtctld.cfg.CredentialsPath,
   177  			"username": vtctld.creds.Username,
   178  			"password": debug.SanitizeString(vtctld.creds.Password),
   179  		}
   180  	}
   181  
   182  	if !vtctld.closed {
   183  		m["dialed_at"] = debug.TimeToString(vtctld.dialedAt)
   184  	}
   185  
   186  	if dr, ok := vtctld.resolver.(debug.Debuggable); ok {
   187  		m["resolver"] = dr.Debug()
   188  	}
   189  
   190  	return m
   191  }