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

     1  /*
     2  Copyright 2020 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 vtsql
    18  
    19  import (
    20  	"context"
    21  	"database/sql"
    22  	"fmt"
    23  	"sync"
    24  	"time"
    25  
    26  	"google.golang.org/grpc/credentials/insecure"
    27  
    28  	"google.golang.org/grpc"
    29  	grpcresolver "google.golang.org/grpc/resolver"
    30  
    31  	"vitess.io/vitess/go/trace"
    32  	"vitess.io/vitess/go/vt/callerid"
    33  	"vitess.io/vitess/go/vt/log"
    34  	"vitess.io/vitess/go/vt/vitessdriver"
    35  	"vitess.io/vitess/go/vt/vtadmin/cluster/resolver"
    36  	"vitess.io/vitess/go/vt/vtadmin/debug"
    37  	"vitess.io/vitess/go/vt/vtadmin/vtadminproto"
    38  
    39  	vtadminpb "vitess.io/vitess/go/vt/proto/vtadmin"
    40  )
    41  
    42  // DB defines the connection and query interface of vitess SQL queries used by
    43  // VTAdmin clusters.
    44  type DB interface {
    45  	// ShowTablets executes `SHOW vitess_tablets` and returns the result.
    46  	ShowTablets(ctx context.Context) (*sql.Rows, error)
    47  
    48  	// Ping behaves like (*sql.DB).Ping.
    49  	Ping() error
    50  	// PingContext behaves like (*sql.DB).PingContext.
    51  	PingContext(ctx context.Context) error
    52  
    53  	// Close closes the underlying database connection. This is a no-op if
    54  	// the DB has no current valid connection. It is safe to call repeatedly.
    55  	//
    56  	// Once closed, a DB is not safe for reuse.
    57  	Close() error
    58  }
    59  
    60  // VTGateProxy is a proxy for creating and using database connections to vtgates
    61  // in a Vitess cluster.
    62  type VTGateProxy struct {
    63  	cluster *vtadminpb.Cluster
    64  	creds   Credentials
    65  	cfg     *Config
    66  
    67  	// DialFunc is called to open a new database connection. In production this
    68  	// should always be vitessdriver.OpenWithConfiguration, but it is exported
    69  	// for testing purposes.
    70  	dialFunc func(cfg vitessdriver.Configuration) (*sql.DB, error)
    71  	resolver grpcresolver.Builder
    72  
    73  	conn *sql.DB
    74  
    75  	m        sync.Mutex
    76  	closed   bool
    77  	dialedAt time.Time
    78  }
    79  
    80  var _ DB = (*VTGateProxy)(nil)
    81  
    82  // New returns a VTGateProxy to the given cluster. When Dial-ing, it will use
    83  // the given discovery implementation to find a vtgate to connect to, and the
    84  // given creds to dial the underlying gRPC connection, both of which are
    85  // provided by the Config.
    86  //
    87  // It does not open a connection to a vtgate; users must call Dial before first
    88  // use.
    89  func New(ctx context.Context, cfg *Config) (*VTGateProxy, error) {
    90  	dialFunc := cfg.dialFunc
    91  	if dialFunc == nil {
    92  		dialFunc = vitessdriver.OpenWithConfiguration
    93  	}
    94  
    95  	proxy := VTGateProxy{
    96  		cluster:  cfg.Cluster,
    97  		creds:    cfg.Credentials,
    98  		cfg:      cfg,
    99  		dialFunc: dialFunc,
   100  		resolver: cfg.ResolverOptions.NewBuilder(cfg.Cluster.Id),
   101  	}
   102  
   103  	if err := proxy.dial(ctx, ""); err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	return &proxy, nil
   108  }
   109  
   110  // getQueryContext returns a new context with the correct effective and immediate
   111  // Caller IDs set, so queries do not passed to vttablet as the application RW
   112  // user. All calls to to vtgate.conn should pass a context wrapped with this
   113  // function.
   114  //
   115  // It returns the original context unchanged if the vtgate has no credentials
   116  // configured.
   117  func (vtgate *VTGateProxy) getQueryContext(ctx context.Context) context.Context {
   118  	if vtgate.creds == nil {
   119  		return ctx
   120  	}
   121  
   122  	return callerid.NewContext(
   123  		ctx,
   124  		callerid.NewEffectiveCallerID(vtgate.creds.GetEffectiveUsername(), "vtadmin", ""),
   125  		callerid.NewImmediateCallerID(vtgate.creds.GetUsername()),
   126  	)
   127  }
   128  
   129  // Dial is part of the DB interface. The proxy's DiscoveryTags can be set to
   130  // narrow the set of possible gates it will connect to.
   131  func (vtgate *VTGateProxy) dial(ctx context.Context, target string, opts ...grpc.DialOption) (err error) {
   132  	span, _ := trace.NewSpan(ctx, "VTGateProxy.Dial")
   133  	defer span.Finish()
   134  
   135  	vtadminproto.AnnotateClusterSpan(vtgate.cluster, span)
   136  	span.Annotate("is_using_credentials", vtgate.creds != nil)
   137  
   138  	conf := vitessdriver.Configuration{
   139  		Protocol:        fmt.Sprintf("grpc_%s", vtgate.cluster.Id),
   140  		Address:         resolver.DialAddr(vtgate.resolver, "vtgate"),
   141  		Target:          target,
   142  		GRPCDialOptions: append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(vtgate.resolver)),
   143  	}
   144  
   145  	if vtgate.creds != nil {
   146  		conf.GRPCDialOptions = append([]grpc.DialOption{
   147  			grpc.WithPerRPCCredentials(vtgate.creds),
   148  		}, conf.GRPCDialOptions...)
   149  	}
   150  
   151  	vtgate.conn, err = vtgate.dialFunc(conf)
   152  	if err != nil {
   153  		return fmt.Errorf("error dialing vtgate: %w", err)
   154  	}
   155  
   156  	log.Infof("Established gRPC connection to vtgate\n")
   157  
   158  	vtgate.m.Lock()
   159  	defer vtgate.m.Unlock()
   160  
   161  	vtgate.closed = false
   162  	vtgate.dialedAt = time.Now()
   163  
   164  	return nil
   165  }
   166  
   167  // ShowTablets is part of the DB interface.
   168  func (vtgate *VTGateProxy) ShowTablets(ctx context.Context) (*sql.Rows, error) {
   169  	span, ctx := trace.NewSpan(ctx, "VTGateProxy.ShowTablets")
   170  	defer span.Finish()
   171  
   172  	vtadminproto.AnnotateClusterSpan(vtgate.cluster, span)
   173  
   174  	return vtgate.conn.QueryContext(vtgate.getQueryContext(ctx), "SHOW vitess_tablets")
   175  }
   176  
   177  // Ping is part of the DB interface.
   178  func (vtgate *VTGateProxy) Ping() error {
   179  	return vtgate.pingContext(context.Background())
   180  }
   181  
   182  // PingContext is part of the DB interface.
   183  func (vtgate *VTGateProxy) PingContext(ctx context.Context) error {
   184  	span, ctx := trace.NewSpan(ctx, "VTGateProxy.PingContext")
   185  	defer span.Finish()
   186  
   187  	vtadminproto.AnnotateClusterSpan(vtgate.cluster, span)
   188  
   189  	return vtgate.pingContext(ctx)
   190  }
   191  
   192  func (vtgate *VTGateProxy) pingContext(ctx context.Context) error {
   193  	return vtgate.conn.PingContext(vtgate.getQueryContext(ctx))
   194  }
   195  
   196  // Close is part of the DB interface and satisfies io.Closer.
   197  func (vtgate *VTGateProxy) Close() error {
   198  	vtgate.m.Lock()
   199  	defer vtgate.m.Unlock()
   200  
   201  	if vtgate.closed {
   202  		return nil
   203  	}
   204  
   205  	defer func() { vtgate.closed = true }()
   206  	return vtgate.conn.Close()
   207  }
   208  
   209  // Debug implements debug.Debuggable for VTGateProxy.
   210  func (vtgate *VTGateProxy) Debug() map[string]any {
   211  	vtgate.m.Lock()
   212  	defer vtgate.m.Unlock()
   213  
   214  	m := map[string]any{
   215  		"is_connected": (!vtgate.closed),
   216  	}
   217  
   218  	if !vtgate.closed {
   219  		m["dialed_at"] = debug.TimeToString(vtgate.dialedAt)
   220  	}
   221  
   222  	if vtgate.creds != nil {
   223  		cmap := map[string]any{
   224  			"source":         vtgate.cfg.CredentialsPath,
   225  			"immediate_user": vtgate.creds.GetUsername(),
   226  			"effective_user": vtgate.creds.GetEffectiveUsername(),
   227  		}
   228  
   229  		if creds, ok := vtgate.creds.(*StaticAuthCredentials); ok {
   230  			cmap["password"] = debug.SanitizeString(creds.Password)
   231  		}
   232  
   233  		m["credentials"] = cmap
   234  	}
   235  
   236  	if dr, ok := vtgate.resolver.(debug.Debuggable); ok {
   237  		m["resolver"] = dr.Debug()
   238  	}
   239  
   240  	return m
   241  }