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 }