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 }