vitess.io/vitess@v0.16.2/go/vt/vtadmin/api.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 vtadmin 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "net/http" 24 "net/http/pprof" 25 "net/url" 26 stdsort "sort" 27 "strings" 28 "sync" 29 "time" 30 31 "github.com/gorilla/handlers" 32 "github.com/gorilla/mux" 33 "github.com/patrickmn/go-cache" 34 "k8s.io/apimachinery/pkg/util/sets" 35 36 "vitess.io/vitess/go/trace" 37 "vitess.io/vitess/go/vt/concurrency" 38 "vitess.io/vitess/go/vt/log" 39 "vitess.io/vitess/go/vt/topo" 40 "vitess.io/vitess/go/vt/topo/topoproto" 41 "vitess.io/vitess/go/vt/vtadmin/cluster" 42 "vitess.io/vitess/go/vt/vtadmin/cluster/dynamic" 43 "vitess.io/vitess/go/vt/vtadmin/errors" 44 "vitess.io/vitess/go/vt/vtadmin/grpcserver" 45 vtadminhttp "vitess.io/vitess/go/vt/vtadmin/http" 46 "vitess.io/vitess/go/vt/vtadmin/http/debug" 47 "vitess.io/vitess/go/vt/vtadmin/http/experimental" 48 vthandlers "vitess.io/vitess/go/vt/vtadmin/http/handlers" 49 "vitess.io/vitess/go/vt/vtadmin/rbac" 50 "vitess.io/vitess/go/vt/vtadmin/sort" 51 "vitess.io/vitess/go/vt/vtadmin/vtadminproto" 52 "vitess.io/vitess/go/vt/vterrors" 53 "vitess.io/vitess/go/vt/vtexplain" 54 55 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 56 vtadminpb "vitess.io/vitess/go/vt/proto/vtadmin" 57 vtctldatapb "vitess.io/vitess/go/vt/proto/vtctldata" 58 vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" 59 ) 60 61 // API is the main entrypoint for the vtadmin server. It implements 62 // vtadminpb.VTAdminServer. 63 type API struct { 64 vtadminpb.UnimplementedVTAdminServer 65 66 clusterMu sync.Mutex // guards `clusters` and `clusterMap` 67 clusters []*cluster.Cluster 68 clusterMap map[string]*cluster.Cluster 69 clusterCache *cache.Cache 70 serv *grpcserver.Server 71 router *mux.Router 72 73 authz *rbac.Authorizer 74 75 options Options 76 } 77 78 // Options wraps the configuration options for different components of the 79 // vtadmin API. 80 type Options struct { 81 GRPCOpts grpcserver.Options 82 HTTPOpts vtadminhttp.Options 83 RBAC *rbac.Config 84 // EnableDynamicClusters makes it so that clients can pass clusters dynamically 85 // in a session-like way, either via HTTP cookies or gRPC metadata. 86 EnableDynamicClusters bool 87 } 88 89 // NewAPI returns a new API, configured to service the given set of clusters, 90 // and configured with the given options. 91 func NewAPI(clusters []*cluster.Cluster, opts Options) *API { 92 clusterMap := make(map[string]*cluster.Cluster, len(clusters)) 93 for _, cluster := range clusters { 94 clusterMap[cluster.ID] = cluster 95 } 96 97 sort.ClustersBy(func(c1, c2 *cluster.Cluster) bool { 98 return c1.ID < c2.ID 99 }).Sort(clusters) 100 101 var ( 102 authn rbac.Authenticator 103 authz *rbac.Authorizer 104 ) 105 if opts.RBAC != nil { 106 authn = opts.RBAC.GetAuthenticator() 107 authz = opts.RBAC.GetAuthorizer() 108 109 if authn != nil { 110 opts.GRPCOpts.StreamInterceptors = append(opts.GRPCOpts.StreamInterceptors, rbac.AuthenticationStreamInterceptor(authn)) 111 opts.GRPCOpts.UnaryInterceptors = append(opts.GRPCOpts.UnaryInterceptors, rbac.AuthenticationUnaryInterceptor(authn)) 112 } 113 } 114 115 if authz == nil { 116 authz, _ = rbac.NewAuthorizer(&rbac.Config{ 117 Rules: []*struct { 118 Resource string 119 Actions []string 120 Subjects []string 121 Clusters []string 122 }{ 123 { 124 Resource: "*", 125 Actions: []string{"*"}, 126 Subjects: []string{"*"}, 127 Clusters: []string{"*"}, 128 }, 129 }, 130 }) 131 } 132 133 api := &API{ 134 clusters: clusters, 135 clusterMap: clusterMap, 136 authz: authz, 137 } 138 139 if opts.EnableDynamicClusters { 140 api.clusterCache = cache.New(24*time.Hour, 24*time.Hour) 141 api.clusterCache.OnEvicted(api.EjectDynamicCluster) 142 143 opts.GRPCOpts.StreamInterceptors = append(opts.GRPCOpts.StreamInterceptors, dynamic.StreamServerInterceptor(api)) 144 opts.GRPCOpts.UnaryInterceptors = append(opts.GRPCOpts.UnaryInterceptors, dynamic.UnaryServerInterceptor(api)) 145 } 146 147 api.options = opts 148 149 serv := grpcserver.New("vtadmin", opts.GRPCOpts) 150 serv.Router().HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { 151 w.Write([]byte("ok\n")) 152 }) 153 154 router := serv.Router().PathPrefix("/api").Subrouter() 155 router.PathPrefix("/").Handler(api).Methods("DELETE", "OPTIONS", "GET", "POST", "PUT") 156 157 api.serv = serv 158 api.router = router 159 vtadminpb.RegisterVTAdminServer(api.serv.GRPCServer(), api) 160 161 if !opts.HTTPOpts.DisableDebug { 162 // Due to the way net/http/pprof insists on registering its handlers, we 163 // have to put these on the root router, and not on the /debug prefixed 164 // subrouter, which would make way more sense, but alas. Additional 165 // debug routes should still go on the /debug subrouter, though. 166 serv.Router().HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 167 serv.Router().HandleFunc("/debug/pprof/profile", pprof.Profile) 168 serv.Router().HandleFunc("/debug/pprof/symbol", pprof.Symbol) 169 serv.Router().PathPrefix("/debug/pprof").HandlerFunc(pprof.Index) 170 171 dapi := &debugAPI{api} 172 debugRouter := serv.Router().PathPrefix("/debug").Subrouter() 173 debugRouter.HandleFunc("/env", debug.Env) 174 debugRouter.HandleFunc("/cluster/{cluster_id}", debug.Cluster(dapi)) 175 debugRouter.HandleFunc("/clusters", debug.Clusters(dapi)) 176 } 177 178 // Middlewares are executed in order of addition. Our ordering (all 179 // middlewares being optional) is: 180 // 1. CORS. CORS is a special case and is applied globally, the rest are applied only to the subrouter. 181 // 2. Compression 182 // 3. Tracing 183 // 4. Authentication 184 middlewares := []mux.MiddlewareFunc{} 185 186 if len(opts.HTTPOpts.CORSOrigins) > 0 { 187 serv.Router().Use(handlers.CORS( 188 handlers.AllowCredentials(), handlers.AllowedOrigins(opts.HTTPOpts.CORSOrigins), handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"}))) 189 } 190 191 if !opts.HTTPOpts.DisableCompression { 192 middlewares = append(middlewares, handlers.CompressHandler) 193 } 194 195 if opts.HTTPOpts.EnableTracing { 196 middlewares = append(middlewares, vthandlers.TraceHandler) 197 } 198 199 if authn != nil { 200 middlewares = append(middlewares, vthandlers.NewAuthenticationHandler(authn)) 201 } 202 203 router.Use(middlewares...) 204 205 return api 206 } 207 208 // Close closes all the clusters in an API concurrently. Its primary function is 209 // to gracefully shutdown cache background goroutines to avoid data races in 210 // tests, but needs to be exported to be called by those tests. It does not have 211 // any production use case. 212 func (api *API) Close() error { 213 var ( 214 wg sync.WaitGroup 215 rec concurrency.AllErrorRecorder 216 ) 217 218 for _, c := range api.clusters { 219 wg.Add(1) 220 go func(c *cluster.Cluster) { 221 defer wg.Done() 222 rec.RecordError(c.Close()) 223 }(c) 224 } 225 226 wg.Wait() 227 return rec.Error() 228 } 229 230 // ListenAndServe starts serving this API on the configured Addr (see 231 // grpcserver.Options) until shutdown or irrecoverable error occurs. 232 func (api *API) ListenAndServe() error { 233 return api.serv.ListenAndServe() 234 } 235 236 // ServeHTTP serves all routes matching path "/api" (see above) 237 // It first processes cookies, and acts accordingly 238 // Primarily, it sets up a dynamic API if HttpOpts.EnableDynamicClusters is set 239 // to true. 240 func (api *API) ServeHTTP(w http.ResponseWriter, r *http.Request) { 241 if !api.options.EnableDynamicClusters { 242 api.Handler().ServeHTTP(w, r) 243 return 244 } 245 246 var dynamicAPI dynamic.API = api 247 248 if clusterCookie, err := r.Cookie("cluster"); err == nil { 249 urlDecoded, err := url.QueryUnescape(clusterCookie.Value) 250 if err == nil { 251 c, id, err := dynamic.ClusterFromString(r.Context(), urlDecoded) 252 if id != "" { 253 if err != nil { 254 log.Warningf("failed to extract valid cluster from cookie; attempting to use existing cluster with id=%s; error: %s", id, err) 255 } 256 257 dynamicAPI = api.WithCluster(c, id) 258 } else { 259 log.Warningf("failed to unmarshal dynamic cluster spec from cookie; falling back to static API; error: %s", err) 260 } 261 } 262 } 263 264 dynamicAPI.Handler().ServeHTTP(w, r) 265 } 266 267 // WithCluster returns a dynamic API with the given cluster. If `c` is non-nil, 268 // it is used as the selected cluster. If the cluster is nil, then a cluster 269 // with the given id is retrieved from the API and used in the dynamic API. 270 // 271 // Callers must ensure that: 272 // 1. If c is non-nil, c.ID == id. 273 // 2. id is non-empty. 274 // 275 // Note that using dynamic.ClusterFromString ensures both of these 276 // preconditions. 277 func (api *API) WithCluster(c *cluster.Cluster, id string) dynamic.API { 278 api.clusterMu.Lock() 279 defer api.clusterMu.Unlock() 280 281 dynamicAPI := &API{ 282 router: api.router, 283 serv: api.serv, 284 authz: api.authz, 285 options: api.options, 286 } 287 288 if c != nil { 289 existingCluster, exists := api.clusterMap[id] 290 shouldAddCluster := !exists 291 if exists { 292 isEqual, err := existingCluster.Equal(c) 293 if err != nil { 294 log.Errorf("Error checking for existing cluster %s equality with new cluster %s: %v", existingCluster.ID, id, err) 295 } 296 shouldAddCluster = shouldAddCluster || !isEqual 297 } 298 if shouldAddCluster { 299 if existingCluster != nil { 300 if err := existingCluster.Close(); err != nil { 301 log.Errorf("%s; some connections and goroutines may linger", err.Error()) 302 } 303 304 idx := stdsort.Search(len(api.clusters), func(i int) bool { 305 return api.clusters[i].ID == existingCluster.ID 306 }) 307 if idx >= 0 && idx < len(api.clusters) { 308 api.clusters = append(api.clusters[:idx], api.clusters[idx+1:]...) 309 } 310 } 311 312 api.clusterMap[id] = c 313 api.clusters = append(api.clusters, c) 314 sort.ClustersBy(func(c1, c2 *cluster.Cluster) bool { 315 return c1.ID < c2.ID 316 }).Sort(api.clusters) 317 318 api.clusterCache.Set(id, c, cache.DefaultExpiration) 319 } else { 320 log.Infof("API already has cluster with id %s, using that instead", id) 321 } 322 } 323 324 selectedCluster := api.clusterMap[id] 325 dynamicAPI.clusters = []*cluster.Cluster{selectedCluster} 326 dynamicAPI.clusterMap = map[string]*cluster.Cluster{id: selectedCluster} 327 328 return dynamicAPI 329 } 330 331 // Handler handles all routes under "/api" (see above) 332 func (api *API) Handler() http.Handler { 333 router := mux.NewRouter().PathPrefix("/api").Subrouter() 334 335 router.Use(handlers.CORS( 336 handlers.AllowCredentials(), handlers.AllowedOrigins(api.options.HTTPOpts.CORSOrigins), handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"}))) 337 338 httpAPI := vtadminhttp.NewAPI(api, api.options.HTTPOpts) 339 340 router.HandleFunc("/backups", httpAPI.Adapt(vtadminhttp.GetBackups)).Name("API.GetBackups") 341 router.HandleFunc("/cells", httpAPI.Adapt(vtadminhttp.GetCellInfos)).Name("API.GetCellInfos") 342 router.HandleFunc("/cells_aliases", httpAPI.Adapt(vtadminhttp.GetCellsAliases)).Name("API.GetCellsAliases") 343 router.HandleFunc("/clusters", httpAPI.Adapt(vtadminhttp.GetClusters)).Name("API.GetClusters") 344 router.HandleFunc("/cluster/{cluster_id}/topology", httpAPI.Adapt(vtadminhttp.GetTopologyPath)).Name("API.GetTopologyPath") 345 router.HandleFunc("/cluster/{cluster_id}/validate", httpAPI.Adapt(vtadminhttp.Validate)).Name("API.Validate").Methods("PUT", "OPTIONS") 346 router.HandleFunc("/gates", httpAPI.Adapt(vtadminhttp.GetGates)).Name("API.GetGates") 347 router.HandleFunc("/keyspace/{cluster_id}", httpAPI.Adapt(vtadminhttp.CreateKeyspace)).Name("API.CreateKeyspace").Methods("POST") 348 router.HandleFunc("/keyspace/{cluster_id}/{name}", httpAPI.Adapt(vtadminhttp.DeleteKeyspace)).Name("API.DeleteKeyspace").Methods("DELETE") 349 router.HandleFunc("/keyspace/{cluster_id}/{name}", httpAPI.Adapt(vtadminhttp.GetKeyspace)).Name("API.GetKeyspace") 350 router.HandleFunc("/keyspace/{cluster_id}/{name}/rebuild_keyspace_graph", httpAPI.Adapt(vtadminhttp.RebuildKeyspaceGraph)).Name("API.RebuildKeyspaceGraph").Methods("PUT", "OPTIONS") 351 router.HandleFunc("/keyspace/{cluster_id}/{name}/remove_keyspace_cell", httpAPI.Adapt(vtadminhttp.RemoveKeyspaceCell)).Name("API.RemoveKeyspaceCell").Methods("PUT", "OPTIONS") 352 router.HandleFunc("/keyspace/{cluster_id}/{name}/validate", httpAPI.Adapt(vtadminhttp.ValidateKeyspace)).Name("API.ValidateKeyspace").Methods("PUT", "OPTIONS") 353 router.HandleFunc("/keyspace/{cluster_id}/{name}/validate/schema", httpAPI.Adapt(vtadminhttp.ValidateSchemaKeyspace)).Name("API.ValidateSchemaKeyspace").Methods("PUT", "OPTIONS") 354 router.HandleFunc("/keyspace/{cluster_id}/{name}/validate/version", httpAPI.Adapt(vtadminhttp.ValidateVersionKeyspace)).Name("API.ValidateVersionKeyspace").Methods("PUT", "OPTIONS") 355 router.HandleFunc("/keyspaces", httpAPI.Adapt(vtadminhttp.GetKeyspaces)).Name("API.GetKeyspaces") 356 router.HandleFunc("/schema/{table}", httpAPI.Adapt(vtadminhttp.FindSchema)).Name("API.FindSchema") 357 router.HandleFunc("/schema/{cluster_id}/{keyspace}/{table}", httpAPI.Adapt(vtadminhttp.GetSchema)).Name("API.GetSchema") 358 router.HandleFunc("/schemas", httpAPI.Adapt(vtadminhttp.GetSchemas)).Name("API.GetSchemas") 359 router.HandleFunc("/schemas/reload", httpAPI.Adapt(vtadminhttp.ReloadSchemas)).Name("API.ReloadSchemas").Methods("PUT", "OPTIONS") 360 router.HandleFunc("/shard/{cluster_id}/{keyspace}/{shard}/emergency_failover", httpAPI.Adapt(vtadminhttp.EmergencyFailoverShard)).Name("API.EmergencyFailoverShard").Methods("POST") 361 router.HandleFunc("/shard/{cluster_id}/{keyspace}/{shard}/planned_failover", httpAPI.Adapt(vtadminhttp.PlannedFailoverShard)).Name("API.PlannedFailoverShard").Methods("POST") 362 router.HandleFunc("/shard/{cluster_id}/{keyspace}/{shard}/reload_schema_shard", httpAPI.Adapt(vtadminhttp.ReloadSchemaShard)).Name("API.ReloadSchemaShard").Methods("PUT", "OPTIONS") 363 router.HandleFunc("/shard/{cluster_id}/{keyspace}/{shard}/validate", httpAPI.Adapt(vtadminhttp.ValidateShard)).Name("API.ValidateShard").Methods("PUT", "OPTIONS") 364 router.HandleFunc("/shard/{cluster_id}/{keyspace}/{shard}/validate_version", httpAPI.Adapt(vtadminhttp.ValidateVersionShard)).Name("API.ValidateVersionShard").Methods("PUT", "OPTIONS") 365 router.HandleFunc("/shard_replication_positions", httpAPI.Adapt(vtadminhttp.GetShardReplicationPositions)).Name("API.GetShardReplicationPositions") 366 router.HandleFunc("/shards/{cluster_id}", httpAPI.Adapt(vtadminhttp.CreateShard)).Name("API.CreateShard").Methods("POST") 367 router.HandleFunc("/shards/{cluster_id}", httpAPI.Adapt(vtadminhttp.DeleteShards)).Name("API.DeleteShards").Methods("DELETE") 368 router.HandleFunc("/srvvschema/{cluster_id}/{cell}", httpAPI.Adapt(vtadminhttp.GetSrvVSchema)).Name("API.GetSrvVSchema") 369 router.HandleFunc("/srvvschemas", httpAPI.Adapt(vtadminhttp.GetSrvVSchemas)).Name("API.GetSrvVSchemas") 370 router.HandleFunc("/tablets", httpAPI.Adapt(vtadminhttp.GetTablets)).Name("API.GetTablets") 371 router.HandleFunc("/tablet/{tablet}", httpAPI.Adapt(vtadminhttp.GetTablet)).Name("API.GetTablet").Methods("GET") 372 router.HandleFunc("/tablet/{tablet}", httpAPI.Adapt(vtadminhttp.DeleteTablet)).Name("API.DeleteTablet").Methods("DELETE", "OPTIONS") 373 router.HandleFunc("/tablet/{tablet}/full_status", httpAPI.Adapt(vtadminhttp.GetFullStatus)).Name("API.GetFullStatus").Methods("GET") 374 router.HandleFunc("/tablet/{tablet}/healthcheck", httpAPI.Adapt(vtadminhttp.RunHealthCheck)).Name("API.RunHealthCheck") 375 router.HandleFunc("/tablet/{tablet}/ping", httpAPI.Adapt(vtadminhttp.PingTablet)).Name("API.PingTablet") 376 router.HandleFunc("/tablet/{tablet}/refresh", httpAPI.Adapt(vtadminhttp.RefreshState)).Name("API.RefreshState").Methods("PUT", "OPTIONS") 377 router.HandleFunc("/tablet/{tablet}/refresh_replication_source", httpAPI.Adapt(vtadminhttp.RefreshTabletReplicationSource)).Name("API.RefreshTabletReplicationSource").Methods("PUT", "OPTIONS") 378 router.HandleFunc("/tablet/{tablet}/reload_schema", httpAPI.Adapt(vtadminhttp.ReloadTabletSchema)).Name("API.ReloadTabletSchema").Methods("PUT", "OPTIONS") 379 router.HandleFunc("/tablet/{tablet}/set_read_only", httpAPI.Adapt(vtadminhttp.SetReadOnly)).Name("API.SetReadOnly").Methods("PUT", "OPTIONS") 380 router.HandleFunc("/tablet/{tablet}/set_read_write", httpAPI.Adapt(vtadminhttp.SetReadWrite)).Name("API.SetReadWrite").Methods("PUT", "OPTIONS") 381 router.HandleFunc("/tablet/{tablet}/start_replication", httpAPI.Adapt(vtadminhttp.StartReplication)).Name("API.StartReplication").Methods("PUT", "OPTIONS") 382 router.HandleFunc("/tablet/{tablet}/stop_replication", httpAPI.Adapt(vtadminhttp.StopReplication)).Name("API.StopReplication").Methods("PUT", "OPTIONS") 383 router.HandleFunc("/tablet/{tablet}/externally_promoted", httpAPI.Adapt(vtadminhttp.TabletExternallyPromoted)).Name("API.TabletExternallyPromoted").Methods("POST") 384 router.HandleFunc("/vschema/{cluster_id}/{keyspace}", httpAPI.Adapt(vtadminhttp.GetVSchema)).Name("API.GetVSchema") 385 router.HandleFunc("/vschemas", httpAPI.Adapt(vtadminhttp.GetVSchemas)).Name("API.GetVSchemas") 386 router.HandleFunc("/vtctlds", httpAPI.Adapt(vtadminhttp.GetVtctlds)).Name("API.GetVtctlds") 387 router.HandleFunc("/vtexplain", httpAPI.Adapt(vtadminhttp.VTExplain)).Name("API.VTExplain") 388 router.HandleFunc("/workflow/{cluster_id}/{keyspace}/{name}", httpAPI.Adapt(vtadminhttp.GetWorkflow)).Name("API.GetWorkflow") 389 router.HandleFunc("/workflows", httpAPI.Adapt(vtadminhttp.GetWorkflows)).Name("API.GetWorkflows") 390 391 experimentalRouter := router.PathPrefix("/experimental").Subrouter() 392 experimentalRouter.HandleFunc("/tablet/{tablet}/debug/vars", httpAPI.Adapt(experimental.TabletDebugVarsPassthrough)).Name("API.TabletDebugVarsPassthrough") 393 experimentalRouter.HandleFunc("/whoami", httpAPI.Adapt(experimental.WhoAmI)) 394 395 return router 396 } 397 398 func (api *API) EjectDynamicCluster(key string, value any) { 399 api.clusterMu.Lock() 400 defer api.clusterMu.Unlock() 401 402 // Delete dynamic clusters from clusterMap when they are expired from clusterCache 403 c, ok := api.clusterMap[key] 404 if ok { 405 delete(api.clusterMap, key) 406 if err := c.Close(); err != nil { 407 log.Errorf("%s; some connections and goroutines may linger", err.Error()) 408 } 409 } 410 411 // Maintain order of clusters when removing dynamic cluster 412 clusterIndex := stdsort.Search(len(api.clusters), func(i int) bool { return api.clusters[i].ID == key }) 413 if clusterIndex >= len(api.clusters) || clusterIndex < 0 { 414 log.Errorf("Cannot remove cluster %s from api.clusters. Cluster index %d is out of range for clusters slice of %d length.", key, clusterIndex, len(api.clusters)) 415 } 416 417 api.clusters = append(api.clusters[:clusterIndex], api.clusters[clusterIndex+1:]...) 418 } 419 420 // CreateKeyspace is part of the vtadminpb.VTAdminServer interface. 421 func (api *API) CreateKeyspace(ctx context.Context, req *vtadminpb.CreateKeyspaceRequest) (*vtadminpb.CreateKeyspaceResponse, error) { 422 span, ctx := trace.NewSpan(ctx, "API.CreateKeyspace") 423 defer span.Finish() 424 425 span.Annotate("cluster_id", req.ClusterId) 426 427 if !api.authz.IsAuthorized(ctx, req.ClusterId, rbac.KeyspaceResource, rbac.CreateAction) { 428 return nil, fmt.Errorf("%w: cannot create keyspace in %s", errors.ErrUnauthorized, req.ClusterId) 429 } 430 431 c, err := api.getClusterForRequest(req.ClusterId) 432 if err != nil { 433 return nil, err 434 } 435 436 ks, err := c.CreateKeyspace(ctx, req.Options) 437 if err != nil { 438 return nil, err 439 } 440 441 return &vtadminpb.CreateKeyspaceResponse{ 442 Keyspace: ks, 443 }, nil 444 } 445 446 // CreateShard is part of the vtadminpb.VTAdminServer interface. 447 func (api *API) CreateShard(ctx context.Context, req *vtadminpb.CreateShardRequest) (*vtctldatapb.CreateShardResponse, error) { 448 span, ctx := trace.NewSpan(ctx, "API.CreateShard") 449 defer span.Finish() 450 451 span.Annotate("cluster_id", req.ClusterId) 452 453 if !api.authz.IsAuthorized(ctx, req.ClusterId, rbac.ShardResource, rbac.CreateAction) { 454 return nil, fmt.Errorf("%w: cannot create shard in %s", errors.ErrUnauthorized, req.ClusterId) 455 } 456 457 c, err := api.getClusterForRequest(req.ClusterId) 458 if err != nil { 459 return nil, err 460 } 461 462 return c.CreateShard(ctx, req.Options) 463 } 464 465 // DeleteKeyspace is part of the vtadminpb.VTAdminServer interface. 466 func (api *API) DeleteKeyspace(ctx context.Context, req *vtadminpb.DeleteKeyspaceRequest) (*vtctldatapb.DeleteKeyspaceResponse, error) { 467 span, ctx := trace.NewSpan(ctx, "API.DeleteKeyspace") 468 defer span.Finish() 469 470 span.Annotate("cluster_id", req.ClusterId) 471 472 if !api.authz.IsAuthorized(ctx, req.ClusterId, rbac.KeyspaceResource, rbac.DeleteAction) { 473 return nil, fmt.Errorf("%w: cannot delete keyspace in %s", errors.ErrUnauthorized, req.ClusterId) 474 } 475 476 c, err := api.getClusterForRequest(req.ClusterId) 477 if err != nil { 478 return nil, err 479 } 480 481 return c.DeleteKeyspace(ctx, req.Options) 482 } 483 484 // DeleteShards is part of the vtadminpb.VTAdminServer interface. 485 func (api *API) DeleteShards(ctx context.Context, req *vtadminpb.DeleteShardsRequest) (*vtctldatapb.DeleteShardsResponse, error) { 486 span, ctx := trace.NewSpan(ctx, "API.DeleteShards") 487 defer span.Finish() 488 489 span.Annotate("cluster_id", req.ClusterId) 490 491 if !api.authz.IsAuthorized(ctx, req.ClusterId, rbac.ShardResource, rbac.DeleteAction) { 492 return nil, fmt.Errorf("%w: cannot delete shards in %s", errors.ErrUnauthorized, req.ClusterId) 493 } 494 495 c, err := api.getClusterForRequest(req.ClusterId) 496 if err != nil { 497 return nil, err 498 } 499 500 return c.DeleteShards(ctx, req.Options) 501 } 502 503 // DeleteTablet is part of the vtadminpb.VTAdminServer interface. 504 func (api *API) DeleteTablet(ctx context.Context, req *vtadminpb.DeleteTabletRequest) (*vtadminpb.DeleteTabletResponse, error) { 505 span, ctx := trace.NewSpan(ctx, "API.DeleteTablet") 506 defer span.Finish() 507 508 tablet, c, err := api.getTabletForAction(ctx, span, rbac.DeleteAction, req.Alias, req.ClusterIds) 509 if err != nil { 510 return nil, err 511 } 512 513 if _, err := c.DeleteTablets(ctx, &vtctldatapb.DeleteTabletsRequest{ 514 AllowPrimary: req.AllowPrimary, 515 TabletAliases: []*topodatapb.TabletAlias{tablet.Tablet.Alias}, 516 }); err != nil { 517 return nil, fmt.Errorf("failed to delete tablet: %w", err) 518 } 519 520 return &vtadminpb.DeleteTabletResponse{ 521 Status: "ok", 522 Cluster: c.ToProto(), 523 }, nil 524 } 525 526 // EmergencyFailoverShard is part of the vtadminpb.VTAdminServer interface. 527 func (api *API) EmergencyFailoverShard(ctx context.Context, req *vtadminpb.EmergencyFailoverShardRequest) (*vtadminpb.EmergencyFailoverShardResponse, error) { 528 span, ctx := trace.NewSpan(ctx, "API.EmergencyFailoverShard") 529 defer span.Finish() 530 531 c, err := api.getClusterForRequest(req.ClusterId) 532 if err != nil { 533 return nil, err 534 } 535 536 if !api.authz.IsAuthorized(ctx, c.ID, rbac.ShardResource, rbac.EmergencyFailoverShardAction) { 537 return nil, nil 538 } 539 540 return c.EmergencyFailoverShard(ctx, req.Options) 541 } 542 543 // FindSchema is part of the vtadminpb.VTAdminServer interface. 544 func (api *API) FindSchema(ctx context.Context, req *vtadminpb.FindSchemaRequest) (*vtadminpb.Schema, error) { 545 span, _ := trace.NewSpan(ctx, "API.FindSchema") 546 defer span.Finish() 547 548 span.Annotate("table", req.Table) 549 550 clusters, clusterIDs := api.getClustersForRequest(req.ClusterIds) 551 552 var ( 553 m sync.Mutex 554 wg sync.WaitGroup 555 rec concurrency.AllErrorRecorder 556 results []*vtadminpb.Schema 557 ) 558 559 for _, c := range clusters { 560 if !api.authz.IsAuthorized(ctx, c.ID, rbac.SchemaResource, rbac.GetAction) { 561 continue 562 } 563 564 wg.Add(1) 565 566 go func(c *cluster.Cluster) { 567 defer wg.Done() 568 569 schemas, err := c.GetSchemas(ctx, cluster.GetSchemaOptions{ 570 TableSizeOptions: req.TableSizeOptions, 571 }) 572 if err != nil { 573 rec.RecordError(err) 574 return 575 } 576 577 for _, schema := range schemas { 578 for _, td := range schema.TableDefinitions { 579 if td.Name == req.Table { 580 m.Lock() 581 results = append(results, schema) 582 m.Unlock() 583 584 return 585 } 586 } 587 } 588 589 log.Infof("cluster %s has no tables named %s", c.ID, req.Table) 590 }(c) 591 } 592 593 wg.Wait() 594 595 if rec.HasErrors() { 596 return nil, rec.Error() 597 } 598 599 switch len(results) { 600 case 0: 601 return nil, &errors.NoSuchSchema{ 602 Clusters: clusterIDs, 603 Table: req.Table, 604 } 605 case 1: 606 return results[0], nil 607 default: 608 return nil, fmt.Errorf("%w: %d schemas found with table named %s", errors.ErrAmbiguousSchema, len(results), req.Table) 609 } 610 } 611 612 // GetBackups is part of the vtadminpb.VTAdminServer interface. 613 func (api *API) GetBackups(ctx context.Context, req *vtadminpb.GetBackupsRequest) (*vtadminpb.GetBackupsResponse, error) { 614 span, ctx := trace.NewSpan(ctx, "API.GetBackups") 615 defer span.Finish() 616 617 clusters, _ := api.getClustersForRequest(req.ClusterIds) 618 619 var ( 620 m sync.Mutex 621 wg sync.WaitGroup 622 rec concurrency.AllErrorRecorder 623 backups []*vtadminpb.ClusterBackup 624 ) 625 626 if req.RequestOptions == nil { 627 req.RequestOptions = &vtctldatapb.GetBackupsRequest{} 628 } 629 630 for _, c := range clusters { 631 if !api.authz.IsAuthorized(ctx, c.ID, rbac.BackupResource, rbac.GetAction) { 632 continue 633 } 634 635 wg.Add(1) 636 637 go func(c *cluster.Cluster) { 638 defer wg.Done() 639 640 bs, err := c.GetBackups(ctx, req) 641 if err != nil { 642 rec.RecordError(err) 643 return 644 } 645 646 m.Lock() 647 defer m.Unlock() 648 649 backups = append(backups, bs...) 650 }(c) 651 } 652 653 wg.Wait() 654 655 if rec.HasErrors() { 656 return nil, rec.Error() 657 } 658 659 return &vtadminpb.GetBackupsResponse{ 660 Backups: backups, 661 }, nil 662 } 663 664 // GetCellInfos is part of the vtadminpb.VTAdminServer interface. 665 func (api *API) GetCellInfos(ctx context.Context, req *vtadminpb.GetCellInfosRequest) (*vtadminpb.GetCellInfosResponse, error) { 666 span, ctx := trace.NewSpan(ctx, "API.GetCellInfos") 667 defer span.Finish() 668 669 clusters, _ := api.getClustersForRequest(req.ClusterIds) 670 671 var ( 672 m sync.Mutex 673 wg sync.WaitGroup 674 rec concurrency.AllErrorRecorder 675 cellInfos []*vtadminpb.ClusterCellInfo 676 ) 677 678 for _, c := range clusters { 679 if !api.authz.IsAuthorized(ctx, c.ID, rbac.CellInfoResource, rbac.GetAction) { 680 continue 681 } 682 683 wg.Add(1) 684 go func(c *cluster.Cluster) { 685 defer wg.Done() 686 687 clusterCellInfos, err := c.GetCellInfos(ctx, req) 688 if err != nil { 689 rec.RecordError(fmt.Errorf("failed to GetCellInfos for cluster %s: %w", c.ID, err)) 690 return 691 } 692 693 m.Lock() 694 defer m.Unlock() 695 cellInfos = append(cellInfos, clusterCellInfos...) 696 }(c) 697 } 698 699 wg.Wait() 700 if rec.HasErrors() { 701 return nil, rec.Error() 702 } 703 704 return &vtadminpb.GetCellInfosResponse{ 705 CellInfos: cellInfos, 706 }, nil 707 } 708 709 // GetCellsAliases is part of the vtadminpb.VTAdminServer interface. 710 func (api *API) GetCellsAliases(ctx context.Context, req *vtadminpb.GetCellsAliasesRequest) (*vtadminpb.GetCellsAliasesResponse, error) { 711 span, ctx := trace.NewSpan(ctx, "API.GetCellsAliases") 712 defer span.Finish() 713 714 clusters, _ := api.getClustersForRequest(req.ClusterIds) 715 716 var ( 717 m sync.Mutex 718 wg sync.WaitGroup 719 rec concurrency.AllErrorRecorder 720 aliases []*vtadminpb.ClusterCellsAliases 721 ) 722 723 for _, c := range clusters { 724 if !api.authz.IsAuthorized(ctx, c.ID, rbac.CellsAliasResource, rbac.GetAction) { 725 continue 726 } 727 728 wg.Add(1) 729 go func(c *cluster.Cluster) { 730 defer wg.Done() 731 732 clusterAliases, err := c.GetCellsAliases(ctx) 733 if err != nil { 734 rec.RecordError(fmt.Errorf("failed to GetCellsAliases for cluster %s: %w", c.ID, err)) 735 return 736 } 737 738 m.Lock() 739 defer m.Unlock() 740 aliases = append(aliases, clusterAliases) 741 }(c) 742 } 743 744 wg.Wait() 745 if rec.HasErrors() { 746 return nil, rec.Error() 747 } 748 749 return &vtadminpb.GetCellsAliasesResponse{ 750 Aliases: aliases, 751 }, nil 752 } 753 754 // GetClusters is part of the vtadminpb.VTAdminServer interface. 755 func (api *API) GetClusters(ctx context.Context, req *vtadminpb.GetClustersRequest) (*vtadminpb.GetClustersResponse, error) { 756 span, _ := trace.NewSpan(ctx, "API.GetClusters") 757 defer span.Finish() 758 759 clusters, _ := api.getClustersForRequest(nil) 760 761 vcs := make([]*vtadminpb.Cluster, 0, len(clusters)) 762 763 for _, c := range clusters { 764 if !api.authz.IsAuthorized(ctx, c.ID, rbac.ClusterResource, rbac.GetAction) { 765 continue 766 } 767 768 vcs = append(vcs, &vtadminpb.Cluster{ 769 Id: c.ID, 770 Name: c.Name, 771 }) 772 } 773 774 return &vtadminpb.GetClustersResponse{ 775 Clusters: vcs, 776 }, nil 777 } 778 779 // GetFullStatus is part of the vtadminpb.VTAdminServer interface. 780 func (api *API) GetFullStatus(ctx context.Context, req *vtadminpb.GetFullStatusRequest) (*vtctldatapb.GetFullStatusResponse, error) { 781 span, ctx := trace.NewSpan(ctx, "API.GetFullStatus") 782 defer span.Finish() 783 784 c, err := api.getClusterForRequest(req.ClusterId) 785 if err != nil { 786 return nil, err 787 } 788 789 if !api.authz.IsAuthorized(ctx, c.ID, rbac.TabletFullStatusResource, rbac.GetAction) { 790 return nil, nil 791 } 792 793 return c.Vtctld.GetFullStatus(ctx, &vtctldatapb.GetFullStatusRequest{ 794 TabletAlias: req.Alias, 795 }) 796 } 797 798 // GetGates is part of the vtadminpb.VTAdminServer interface. 799 func (api *API) GetGates(ctx context.Context, req *vtadminpb.GetGatesRequest) (*vtadminpb.GetGatesResponse, error) { 800 span, ctx := trace.NewSpan(ctx, "API.GetGates") 801 defer span.Finish() 802 803 clusters, _ := api.getClustersForRequest(req.ClusterIds) 804 805 var ( 806 gates []*vtadminpb.VTGate 807 wg sync.WaitGroup 808 er concurrency.AllErrorRecorder 809 m sync.Mutex 810 ) 811 812 for _, c := range clusters { 813 if !api.authz.IsAuthorized(ctx, c.ID, rbac.VTGateResource, rbac.GetAction) { 814 continue 815 } 816 817 wg.Add(1) 818 819 go func(c *cluster.Cluster) { 820 defer wg.Done() 821 822 gs, err := c.GetGates(ctx) 823 if err != nil { 824 er.RecordError(err) 825 return 826 } 827 828 m.Lock() 829 defer m.Unlock() 830 831 gates = append(gates, gs...) 832 }(c) 833 } 834 835 wg.Wait() 836 837 if er.HasErrors() { 838 return nil, er.Error() 839 } 840 841 return &vtadminpb.GetGatesResponse{ 842 Gates: gates, 843 }, nil 844 } 845 846 // GetKeyspace is part of the vtadminpb.VTAdminServer interface. 847 func (api *API) GetKeyspace(ctx context.Context, req *vtadminpb.GetKeyspaceRequest) (*vtadminpb.Keyspace, error) { 848 span, ctx := trace.NewSpan(ctx, "API.GetKeyspace") 849 defer span.Finish() 850 851 c, err := api.getClusterForRequest(req.ClusterId) 852 if err != nil { 853 return nil, err 854 } 855 856 if !api.authz.IsAuthorized(ctx, c.ID, rbac.KeyspaceResource, rbac.GetAction) { 857 return nil, nil 858 } 859 860 return c.GetKeyspace(ctx, req.Keyspace) 861 } 862 863 // GetKeyspaces is part of the vtadminpb.VTAdminServer interface. 864 func (api *API) GetKeyspaces(ctx context.Context, req *vtadminpb.GetKeyspacesRequest) (*vtadminpb.GetKeyspacesResponse, error) { 865 span, ctx := trace.NewSpan(ctx, "API.GetKeyspaces") 866 defer span.Finish() 867 868 clusters, _ := api.getClustersForRequest(req.ClusterIds) 869 870 var ( 871 keyspaces []*vtadminpb.Keyspace 872 wg sync.WaitGroup 873 er concurrency.AllErrorRecorder 874 m sync.Mutex 875 ) 876 877 for _, c := range clusters { 878 if !api.authz.IsAuthorized(ctx, c.ID, rbac.KeyspaceResource, rbac.GetAction) { 879 continue 880 } 881 882 wg.Add(1) 883 884 go func(c *cluster.Cluster) { 885 defer wg.Done() 886 887 kss, err := c.GetKeyspaces(ctx) 888 if err != nil { 889 er.RecordError(err) 890 return 891 } 892 893 m.Lock() 894 keyspaces = append(keyspaces, kss...) 895 m.Unlock() 896 }(c) 897 } 898 899 wg.Wait() 900 901 if er.HasErrors() { 902 return nil, er.Error() 903 } 904 905 return &vtadminpb.GetKeyspacesResponse{ 906 Keyspaces: keyspaces, 907 }, nil 908 } 909 910 // GetSchema is part of the vtadminpb.VTAdminServer interface. 911 func (api *API) GetSchema(ctx context.Context, req *vtadminpb.GetSchemaRequest) (*vtadminpb.Schema, error) { 912 span, ctx := trace.NewSpan(ctx, "API.GetSchema") 913 defer span.Finish() 914 915 span.Annotate("cluster_id", req.ClusterId) 916 span.Annotate("keyspace", req.Keyspace) 917 span.Annotate("table", req.Table) 918 vtadminproto.AnnotateSpanWithGetSchemaTableSizeOptions(req.TableSizeOptions, span) 919 920 c, err := api.getClusterForRequest(req.ClusterId) 921 if err != nil { 922 return nil, err 923 } 924 925 if !api.authz.IsAuthorized(ctx, c.ID, rbac.SchemaResource, rbac.GetAction) { 926 return nil, nil 927 } 928 929 schema, err := c.GetSchema(ctx, req.Keyspace, cluster.GetSchemaOptions{ 930 BaseRequest: &vtctldatapb.GetSchemaRequest{ 931 Tables: []string{req.Table}, 932 }, 933 TableSizeOptions: req.TableSizeOptions, 934 }) 935 if err != nil { 936 return nil, err 937 } 938 939 if schema == nil || len(schema.TableDefinitions) == 0 { 940 return nil, &errors.NoSuchSchema{ 941 Clusters: []string{req.ClusterId}, 942 Table: req.Table, 943 } 944 } 945 946 return schema, nil 947 } 948 949 // GetSchemas is part of the vtadminpb.VTAdminServer interface. 950 func (api *API) GetSchemas(ctx context.Context, req *vtadminpb.GetSchemasRequest) (*vtadminpb.GetSchemasResponse, error) { 951 span, ctx := trace.NewSpan(ctx, "API.GetSchemas") 952 defer span.Finish() 953 954 clusters, _ := api.getClustersForRequest(req.ClusterIds) 955 956 var ( 957 schemas []*vtadminpb.Schema 958 wg sync.WaitGroup 959 er concurrency.AllErrorRecorder 960 m sync.Mutex 961 ) 962 963 for _, c := range clusters { 964 if !api.authz.IsAuthorized(ctx, c.ID, rbac.SchemaResource, rbac.GetAction) { 965 continue 966 } 967 968 wg.Add(1) 969 970 // Get schemas for the cluster 971 go func(c *cluster.Cluster) { 972 defer wg.Done() 973 974 ss, err := c.GetSchemas(ctx, cluster.GetSchemaOptions{ 975 TableSizeOptions: req.TableSizeOptions, 976 }) 977 if err != nil { 978 er.RecordError(err) 979 return 980 } 981 982 m.Lock() 983 schemas = append(schemas, ss...) 984 m.Unlock() 985 }(c) 986 } 987 988 wg.Wait() 989 990 if er.HasErrors() { 991 return nil, er.Error() 992 } 993 994 stdsort.Slice(schemas, func(i, j int) bool { 995 return schemas[i].Cluster.Id < schemas[j].Cluster.Id 996 }) 997 998 return &vtadminpb.GetSchemasResponse{ 999 Schemas: schemas, 1000 }, nil 1001 } 1002 1003 // GetShardReplicationPositions is part of the vtadminpb.VTAdminServer interface. 1004 func (api *API) GetShardReplicationPositions(ctx context.Context, req *vtadminpb.GetShardReplicationPositionsRequest) (*vtadminpb.GetShardReplicationPositionsResponse, error) { 1005 span, ctx := trace.NewSpan(ctx, "API.GetShardReplicationPositions") 1006 defer span.Finish() 1007 1008 clusters, _ := api.getClustersForRequest(req.ClusterIds) 1009 1010 var ( 1011 m sync.Mutex 1012 wg sync.WaitGroup 1013 rec concurrency.AllErrorRecorder 1014 positions []*vtadminpb.ClusterShardReplicationPosition 1015 ) 1016 1017 for _, c := range clusters { 1018 if !api.authz.IsAuthorized(ctx, c.ID, rbac.ShardReplicationPositionResource, rbac.GetAction) { 1019 continue 1020 } 1021 1022 wg.Add(1) 1023 1024 go func(c *cluster.Cluster) { 1025 defer wg.Done() 1026 1027 clusterPositions, err := c.GetShardReplicationPositions(ctx, req) 1028 if err != nil { 1029 rec.RecordError(err) 1030 return 1031 } 1032 1033 m.Lock() 1034 defer m.Unlock() 1035 positions = append(positions, clusterPositions...) 1036 }(c) 1037 } 1038 1039 wg.Wait() 1040 1041 if rec.HasErrors() { 1042 return nil, rec.Error() 1043 } 1044 1045 return &vtadminpb.GetShardReplicationPositionsResponse{ 1046 ReplicationPositions: positions, 1047 }, nil 1048 } 1049 1050 // GetSrvVSchema is part of the vtadminpb.VTAdminServer interface. 1051 func (api *API) GetSrvVSchema(ctx context.Context, req *vtadminpb.GetSrvVSchemaRequest) (*vtadminpb.SrvVSchema, error) { 1052 span, ctx := trace.NewSpan(ctx, "API.GetSrvVSchema") 1053 defer span.Finish() 1054 1055 span.Annotate("cluster_id", req.ClusterId) 1056 span.Annotate("cell", req.Cell) 1057 1058 c, err := api.getClusterForRequest(req.ClusterId) 1059 if err != nil { 1060 return nil, err 1061 } 1062 1063 if !api.authz.IsAuthorized(ctx, c.ID, rbac.SrvVSchemaResource, rbac.GetAction) { 1064 return nil, nil 1065 } 1066 1067 return c.GetSrvVSchema(ctx, req.Cell) 1068 } 1069 1070 // GetSrvVSchemas is part of the vtadminpb.VTAdminServer interface. 1071 func (api *API) GetSrvVSchemas(ctx context.Context, req *vtadminpb.GetSrvVSchemasRequest) (*vtadminpb.GetSrvVSchemasResponse, error) { 1072 span, ctx := trace.NewSpan(ctx, "API.GetSrvVSchemas") 1073 defer span.Finish() 1074 1075 clusters, _ := api.getClustersForRequest(req.ClusterIds) 1076 1077 var ( 1078 svs []*vtadminpb.SrvVSchema 1079 wg sync.WaitGroup 1080 er concurrency.AllErrorRecorder 1081 m sync.Mutex 1082 ) 1083 1084 for _, c := range clusters { 1085 if !api.authz.IsAuthorized(ctx, c.ID, rbac.SrvVSchemaResource, rbac.GetAction) { 1086 continue 1087 } 1088 1089 wg.Add(1) 1090 1091 go func(c *cluster.Cluster) { 1092 defer wg.Done() 1093 1094 s, err := c.GetSrvVSchemas(ctx, req.Cells) 1095 1096 if err != nil { 1097 er.RecordError(err) 1098 return 1099 } 1100 1101 m.Lock() 1102 svs = append(svs, s...) 1103 m.Unlock() 1104 }(c) 1105 } 1106 1107 wg.Wait() 1108 1109 if er.HasErrors() { 1110 return nil, er.Error() 1111 } 1112 1113 return &vtadminpb.GetSrvVSchemasResponse{ 1114 SrvVSchemas: svs, 1115 }, nil 1116 } 1117 1118 // GetTablet is part of the vtadminpb.VTAdminServer interface. 1119 func (api *API) GetTablet(ctx context.Context, req *vtadminpb.GetTabletRequest) (*vtadminpb.Tablet, error) { 1120 span, ctx := trace.NewSpan(ctx, "API.GetTablet") 1121 defer span.Finish() 1122 1123 t, _, err := api.getTabletForAction(ctx, span, rbac.GetAction, req.Alias, req.ClusterIds) 1124 return t, err 1125 } 1126 1127 // GetTablets is part of the vtadminpb.VTAdminServer interface. 1128 func (api *API) GetTablets(ctx context.Context, req *vtadminpb.GetTabletsRequest) (*vtadminpb.GetTabletsResponse, error) { 1129 span, ctx := trace.NewSpan(ctx, "API.GetTablets") 1130 defer span.Finish() 1131 1132 clusters, _ := api.getClustersForRequest(req.ClusterIds) 1133 1134 var ( 1135 tablets []*vtadminpb.Tablet 1136 wg sync.WaitGroup 1137 er concurrency.AllErrorRecorder 1138 m sync.Mutex 1139 ) 1140 1141 for _, c := range clusters { 1142 if !api.authz.IsAuthorized(ctx, c.ID, rbac.TabletResource, rbac.GetAction) { 1143 continue 1144 } 1145 1146 wg.Add(1) 1147 1148 go func(c *cluster.Cluster) { 1149 defer wg.Done() 1150 1151 ts, err := c.GetTablets(ctx) 1152 if err != nil { 1153 er.RecordError(fmt.Errorf("GetTablets(cluster = %s): %w", c.ID, err)) 1154 return 1155 } 1156 1157 m.Lock() 1158 tablets = append(tablets, ts...) 1159 m.Unlock() 1160 }(c) 1161 } 1162 1163 wg.Wait() 1164 1165 if er.HasErrors() { 1166 return nil, er.Error() 1167 } 1168 1169 return &vtadminpb.GetTabletsResponse{ 1170 Tablets: tablets, 1171 }, nil 1172 } 1173 1174 // GetTopologyPath is part of the vtadminpb.VTAdminServer interface. 1175 func (api *API) GetTopologyPath(ctx context.Context, req *vtadminpb.GetTopologyPathRequest) (*vtctldatapb.GetTopologyPathResponse, error) { 1176 span, ctx := trace.NewSpan(ctx, "API.GetTopologyPath") 1177 defer span.Finish() 1178 1179 c, err := api.getClusterForRequest(req.ClusterId) 1180 if err != nil { 1181 return nil, err 1182 } 1183 1184 cluster.AnnotateSpan(c, span) 1185 1186 if !api.authz.IsAuthorized(ctx, c.ID, rbac.TopologyResource, rbac.GetAction) { 1187 return nil, nil 1188 } 1189 1190 return c.Vtctld.GetTopologyPath(ctx, &vtctldatapb.GetTopologyPathRequest{Path: req.Path}) 1191 } 1192 1193 // GetVSchema is part of the vtadminpb.VTAdminServer interface. 1194 func (api *API) GetVSchema(ctx context.Context, req *vtadminpb.GetVSchemaRequest) (*vtadminpb.VSchema, error) { 1195 span, ctx := trace.NewSpan(ctx, "API.GetVSchema") 1196 defer span.Finish() 1197 1198 c, err := api.getClusterForRequest(req.ClusterId) 1199 if err != nil { 1200 return nil, err 1201 } 1202 1203 cluster.AnnotateSpan(c, span) 1204 1205 if !api.authz.IsAuthorized(ctx, c.ID, rbac.VSchemaResource, rbac.GetAction) { 1206 return nil, nil 1207 } 1208 1209 return c.GetVSchema(ctx, req.Keyspace) 1210 } 1211 1212 // GetVSchemas is part of the vtadminpb.VTAdminServer interface. 1213 func (api *API) GetVSchemas(ctx context.Context, req *vtadminpb.GetVSchemasRequest) (*vtadminpb.GetVSchemasResponse, error) { 1214 span, ctx := trace.NewSpan(ctx, "API.GetVSchemas") 1215 defer span.Finish() 1216 1217 clusters, _ := api.getClustersForRequest(req.ClusterIds) 1218 1219 var ( 1220 m sync.Mutex 1221 wg sync.WaitGroup 1222 rec concurrency.AllErrorRecorder 1223 vschemas []*vtadminpb.VSchema 1224 ) 1225 1226 if len(clusters) == 0 { 1227 if len(req.ClusterIds) > 0 { 1228 return nil, fmt.Errorf("%w: %s", errors.ErrUnsupportedCluster, strings.Join(req.ClusterIds, ", ")) 1229 } 1230 1231 return &vtadminpb.GetVSchemasResponse{VSchemas: []*vtadminpb.VSchema{}}, nil 1232 } 1233 1234 for _, c := range clusters { 1235 if !api.authz.IsAuthorized(ctx, c.ID, rbac.VSchemaResource, rbac.GetAction) { 1236 continue 1237 } 1238 1239 wg.Add(1) 1240 1241 go func(c *cluster.Cluster) { 1242 defer wg.Done() 1243 1244 span, ctx := trace.NewSpan(ctx, "Cluster.GetVSchemas") 1245 defer span.Finish() 1246 1247 cluster.AnnotateSpan(c, span) 1248 1249 getKeyspacesSpan, getKeyspacesCtx := trace.NewSpan(ctx, "Cluster.GetKeyspaces") 1250 cluster.AnnotateSpan(c, getKeyspacesSpan) 1251 1252 keyspaces, err := c.Vtctld.GetKeyspaces(getKeyspacesCtx, &vtctldatapb.GetKeyspacesRequest{}) 1253 if err != nil { 1254 rec.RecordError(fmt.Errorf("GetKeyspaces(cluster = %s): %w", c.ID, err)) 1255 getKeyspacesSpan.Finish() 1256 return 1257 } 1258 1259 getKeyspacesSpan.Finish() 1260 1261 var ( 1262 clusterM sync.Mutex 1263 clusterWG sync.WaitGroup 1264 clusterRec concurrency.AllErrorRecorder 1265 clusterVSchemas = make([]*vtadminpb.VSchema, 0, len(keyspaces.Keyspaces)) 1266 ) 1267 1268 for _, keyspace := range keyspaces.Keyspaces { 1269 clusterWG.Add(1) 1270 1271 go func(keyspace *vtctldatapb.Keyspace) { 1272 defer clusterWG.Done() 1273 vschema, err := c.GetVSchema(ctx, keyspace.Name) 1274 if err != nil { 1275 clusterRec.RecordError(fmt.Errorf("GetVSchema(keyspace = %s): %w", keyspace.Name, err)) 1276 return 1277 } 1278 1279 clusterM.Lock() 1280 clusterVSchemas = append(clusterVSchemas, vschema) 1281 clusterM.Unlock() 1282 }(keyspace) 1283 } 1284 1285 clusterWG.Wait() 1286 1287 if clusterRec.HasErrors() { 1288 rec.RecordError(fmt.Errorf("GetVSchemas(cluster = %s): %w", c.ID, clusterRec.Error())) 1289 return 1290 } 1291 1292 m.Lock() 1293 vschemas = append(vschemas, clusterVSchemas...) 1294 m.Unlock() 1295 }(c) 1296 } 1297 1298 wg.Wait() 1299 1300 if rec.HasErrors() { 1301 return nil, rec.Error() 1302 } 1303 1304 return &vtadminpb.GetVSchemasResponse{ 1305 VSchemas: vschemas, 1306 }, nil 1307 } 1308 1309 // GetVtctlds is part of the vtadminpb.VTAdminServer interface. 1310 func (api *API) GetVtctlds(ctx context.Context, req *vtadminpb.GetVtctldsRequest) (*vtadminpb.GetVtctldsResponse, error) { 1311 span, ctx := trace.NewSpan(ctx, "API.GetVtctlds") 1312 defer span.Finish() 1313 1314 clusters, _ := api.getClustersForRequest(req.ClusterIds) 1315 1316 var ( 1317 m sync.Mutex 1318 wg sync.WaitGroup 1319 rec concurrency.AllErrorRecorder 1320 vtctlds []*vtadminpb.Vtctld 1321 ) 1322 1323 for _, c := range clusters { 1324 if !api.authz.IsAuthorized(ctx, c.ID, rbac.VtctldResource, rbac.GetAction) { 1325 continue 1326 } 1327 1328 wg.Add(1) 1329 go func(c *cluster.Cluster) { 1330 defer wg.Done() 1331 1332 vs, err := c.GetVtctlds(ctx) 1333 if err != nil { 1334 rec.RecordError(err) 1335 return 1336 } 1337 1338 m.Lock() 1339 defer m.Unlock() 1340 1341 vtctlds = append(vtctlds, vs...) 1342 }(c) 1343 } 1344 1345 wg.Wait() 1346 if rec.HasErrors() { 1347 return nil, rec.Error() 1348 } 1349 1350 return &vtadminpb.GetVtctldsResponse{ 1351 Vtctlds: vtctlds, 1352 }, nil 1353 } 1354 1355 // GetWorkflow is part of the vtadminpb.VTAdminServer interface. 1356 func (api *API) GetWorkflow(ctx context.Context, req *vtadminpb.GetWorkflowRequest) (*vtadminpb.Workflow, error) { 1357 span, ctx := trace.NewSpan(ctx, "API.GetWorkflow") 1358 defer span.Finish() 1359 1360 c, err := api.getClusterForRequest(req.ClusterId) 1361 if err != nil { 1362 return nil, err 1363 } 1364 1365 cluster.AnnotateSpan(c, span) 1366 span.Annotate("keyspace", req.Keyspace) 1367 span.Annotate("workflow_name", req.Name) 1368 span.Annotate("active_only", req.ActiveOnly) 1369 1370 if !api.authz.IsAuthorized(ctx, c.ID, rbac.WorkflowResource, rbac.GetAction) { 1371 return nil, nil 1372 } 1373 1374 return c.GetWorkflow(ctx, req.Keyspace, req.Name, cluster.GetWorkflowOptions{ 1375 ActiveOnly: req.ActiveOnly, 1376 }) 1377 } 1378 1379 // GetWorkflows is part of the vtadminpb.VTAdminServer interface. 1380 func (api *API) GetWorkflows(ctx context.Context, req *vtadminpb.GetWorkflowsRequest) (*vtadminpb.GetWorkflowsResponse, error) { 1381 span, ctx := trace.NewSpan(ctx, "API.GetWorkflows") 1382 defer span.Finish() 1383 1384 clusters, _ := api.getClustersForRequest(req.ClusterIds) 1385 1386 var ( 1387 m sync.Mutex 1388 wg sync.WaitGroup 1389 rec concurrency.AllErrorRecorder 1390 results = map[string]*vtadminpb.ClusterWorkflows{} 1391 ) 1392 1393 for _, c := range clusters { 1394 if !api.authz.IsAuthorized(ctx, c.ID, rbac.WorkflowResource, rbac.GetAction) { 1395 continue 1396 } 1397 1398 wg.Add(1) 1399 1400 go func(c *cluster.Cluster) { 1401 defer wg.Done() 1402 1403 workflows, err := c.GetWorkflows(ctx, req.Keyspaces, cluster.GetWorkflowsOptions{ 1404 ActiveOnly: req.ActiveOnly, 1405 IgnoreKeyspaces: sets.New[string](req.IgnoreKeyspaces...), 1406 }) 1407 if err != nil { 1408 rec.RecordError(err) 1409 1410 return 1411 } 1412 1413 m.Lock() 1414 results[c.ID] = workflows 1415 m.Unlock() 1416 }(c) 1417 } 1418 1419 wg.Wait() 1420 1421 if rec.HasErrors() { 1422 return nil, rec.Error() 1423 } 1424 1425 return &vtadminpb.GetWorkflowsResponse{ 1426 WorkflowsByCluster: results, 1427 }, nil 1428 } 1429 1430 // PingTablet is part of the vtadminpb.VTAdminServer interface. 1431 func (api *API) PingTablet(ctx context.Context, req *vtadminpb.PingTabletRequest) (*vtadminpb.PingTabletResponse, error) { 1432 span, ctx := trace.NewSpan(ctx, "API.PingTablet") 1433 defer span.Finish() 1434 1435 tablet, c, err := api.getTabletForAction(ctx, span, rbac.PingAction, req.Alias, req.ClusterIds) 1436 if err != nil { 1437 return nil, err 1438 } 1439 1440 cluster.AnnotateSpan(c, span) 1441 1442 _, err = c.Vtctld.PingTablet(ctx, &vtctldatapb.PingTabletRequest{ 1443 TabletAlias: tablet.Tablet.Alias, 1444 }) 1445 1446 if err != nil { 1447 return nil, err 1448 } 1449 1450 return &vtadminpb.PingTabletResponse{ 1451 Status: "ok", 1452 Cluster: c.ToProto(), 1453 }, nil 1454 } 1455 1456 // PlannedFailoverShard is part of the vtadminpb.VTAdminServer interface. 1457 func (api *API) PlannedFailoverShard(ctx context.Context, req *vtadminpb.PlannedFailoverShardRequest) (*vtadminpb.PlannedFailoverShardResponse, error) { 1458 span, ctx := trace.NewSpan(ctx, "API.PlannedFailoverShard") 1459 defer span.Finish() 1460 1461 c, err := api.getClusterForRequest(req.ClusterId) 1462 if err != nil { 1463 return nil, err 1464 } 1465 1466 if !api.authz.IsAuthorized(ctx, c.ID, rbac.ShardResource, rbac.PlannedFailoverShardAction) { 1467 return nil, nil 1468 } 1469 1470 return c.PlannedFailoverShard(ctx, req.Options) 1471 } 1472 1473 // RebuildKeyspaceGraph is a part of the vtadminpb.VTAdminServer interface. 1474 func (api *API) RebuildKeyspaceGraph(ctx context.Context, req *vtadminpb.RebuildKeyspaceGraphRequest) (*vtadminpb.RebuildKeyspaceGraphResponse, error) { 1475 span, ctx := trace.NewSpan(ctx, "API.RebuildKeyspaceGraph") 1476 defer span.Finish() 1477 1478 c, err := api.getClusterForRequest(req.ClusterId) 1479 if err != nil { 1480 return nil, err 1481 } 1482 1483 if !api.authz.IsAuthorized(ctx, c.ID, rbac.KeyspaceResource, rbac.PutAction) { 1484 return nil, nil 1485 } 1486 1487 _, err = c.Vtctld.RebuildKeyspaceGraph(ctx, &vtctldatapb.RebuildKeyspaceGraphRequest{ 1488 Keyspace: req.Keyspace, 1489 AllowPartial: req.AllowPartial, 1490 Cells: req.Cells, 1491 }) 1492 1493 if err != nil { 1494 return nil, err 1495 } 1496 1497 return &vtadminpb.RebuildKeyspaceGraphResponse{ 1498 Status: "ok", 1499 }, nil 1500 } 1501 1502 // RefreshState is part of the vtadminpb.VTAdminServer interface. 1503 func (api *API) RefreshState(ctx context.Context, req *vtadminpb.RefreshStateRequest) (*vtadminpb.RefreshStateResponse, error) { 1504 span, ctx := trace.NewSpan(ctx, "API.RefreshState") 1505 defer span.Finish() 1506 1507 tablet, c, err := api.getTabletForAction(ctx, span, rbac.PutAction, req.Alias, req.ClusterIds) 1508 if err != nil { 1509 return nil, err 1510 } 1511 1512 if err := c.RefreshState(ctx, tablet); err != nil { 1513 return nil, err 1514 } 1515 1516 return &vtadminpb.RefreshStateResponse{ 1517 Status: "ok", 1518 Cluster: c.ToProto(), 1519 }, nil 1520 } 1521 1522 // RefreshTabletReplicationSource is part of the vtadminpb.VTAdminServer interface. 1523 func (api *API) RefreshTabletReplicationSource(ctx context.Context, req *vtadminpb.RefreshTabletReplicationSourceRequest) (*vtadminpb.RefreshTabletReplicationSourceResponse, error) { 1524 span, ctx := trace.NewSpan(ctx, "API.RefreshTabletReplicationSource") 1525 defer span.Finish() 1526 1527 tablet, c, err := api.getTabletForAction(ctx, span, rbac.RefreshTabletReplicationSourceAction, req.Alias, req.ClusterIds) 1528 if err != nil { 1529 return nil, err 1530 } 1531 1532 return c.RefreshTabletReplicationSource(ctx, tablet) 1533 } 1534 1535 // ReloadSchemas is part of the vtadminpb.VTAdminServer interface. 1536 func (api *API) ReloadSchemas(ctx context.Context, req *vtadminpb.ReloadSchemasRequest) (*vtadminpb.ReloadSchemasResponse, error) { 1537 span, ctx := trace.NewSpan(ctx, "API.ReloadSchemas") 1538 defer span.Finish() 1539 1540 clusters, _ := api.getClustersForRequest(req.ClusterIds) 1541 1542 var ( 1543 m sync.Mutex 1544 wg sync.WaitGroup 1545 rec concurrency.AllErrorRecorder 1546 resp vtadminpb.ReloadSchemasResponse 1547 ) 1548 1549 for _, c := range clusters { 1550 if !api.authz.IsAuthorized(ctx, c.ID, rbac.SchemaResource, rbac.ReloadAction) { 1551 continue 1552 } 1553 1554 wg.Add(1) 1555 1556 go func(c *cluster.Cluster) { 1557 defer wg.Done() 1558 1559 cr, err := c.ReloadSchemas(ctx, req) 1560 if err != nil { 1561 rec.RecordError(fmt.Errorf("ReloadSchemas(cluster = %s) failed: %w", c.ID, err)) 1562 return 1563 } 1564 1565 m.Lock() 1566 defer m.Unlock() 1567 resp.KeyspaceResults = append(resp.KeyspaceResults, cr.KeyspaceResults...) 1568 resp.ShardResults = append(resp.ShardResults, cr.ShardResults...) 1569 resp.TabletResults = append(resp.TabletResults, cr.TabletResults...) 1570 }(c) 1571 } 1572 1573 wg.Wait() 1574 if rec.HasErrors() { 1575 return nil, rec.Error() 1576 } 1577 1578 return &resp, nil 1579 } 1580 1581 // RemoveKeyspaceCell is a part of the vtadminpb.VTAdminServer interface. 1582 func (api *API) RemoveKeyspaceCell(ctx context.Context, req *vtadminpb.RemoveKeyspaceCellRequest) (*vtadminpb.RemoveKeyspaceCellResponse, error) { 1583 span, ctx := trace.NewSpan(ctx, "API.RemoveKeyspaceCell") 1584 defer span.Finish() 1585 1586 c, err := api.getClusterForRequest(req.ClusterId) 1587 if err != nil { 1588 return nil, err 1589 } 1590 1591 if !api.authz.IsAuthorized(ctx, c.ID, rbac.KeyspaceResource, rbac.PutAction) { 1592 return nil, nil 1593 } 1594 1595 _, err = c.Vtctld.RemoveKeyspaceCell(ctx, &vtctldatapb.RemoveKeyspaceCellRequest{ 1596 Keyspace: req.Keyspace, 1597 Cell: req.Cell, 1598 Force: req.Force, 1599 Recursive: req.Recursive, 1600 }) 1601 1602 if err != nil { 1603 return nil, err 1604 } 1605 1606 return &vtadminpb.RemoveKeyspaceCellResponse{ 1607 Status: "ok", 1608 }, nil 1609 } 1610 1611 // ReloadSchemaShard is part of the vtadminpb.VTAdminServer interface. 1612 func (api *API) ReloadSchemaShard(ctx context.Context, req *vtadminpb.ReloadSchemaShardRequest) (*vtadminpb.ReloadSchemaShardResponse, error) { 1613 span, ctx := trace.NewSpan(ctx, "API.ReloadSchemas") 1614 defer span.Finish() 1615 1616 c, err := api.getClusterForRequest(req.ClusterId) 1617 1618 if err != nil { 1619 return nil, err 1620 } 1621 1622 res, err := c.Vtctld.ReloadSchemaShard(ctx, &vtctldatapb.ReloadSchemaShardRequest{ 1623 WaitPosition: req.WaitPosition, 1624 IncludePrimary: req.IncludePrimary, 1625 Concurrency: req.Concurrency, 1626 }) 1627 1628 if err != nil { 1629 return nil, err 1630 } 1631 1632 return &vtadminpb.ReloadSchemaShardResponse{ 1633 Events: res.Events, 1634 }, nil 1635 } 1636 1637 // RunHealthCheck is part of the vtadminpb.VTAdminServer interface. 1638 func (api *API) RunHealthCheck(ctx context.Context, req *vtadminpb.RunHealthCheckRequest) (*vtadminpb.RunHealthCheckResponse, error) { 1639 span, ctx := trace.NewSpan(ctx, "API.RunHealthCheck") 1640 defer span.Finish() 1641 1642 tablet, c, err := api.getTabletForAction(ctx, span, rbac.GetAction, req.Alias, req.ClusterIds) 1643 if err != nil { 1644 return nil, err 1645 } 1646 1647 cluster.AnnotateSpan(c, span) 1648 1649 _, err = c.Vtctld.RunHealthCheck(ctx, &vtctldatapb.RunHealthCheckRequest{ 1650 TabletAlias: tablet.Tablet.Alias, 1651 }) 1652 1653 if err != nil { 1654 return nil, err 1655 } 1656 1657 return &vtadminpb.RunHealthCheckResponse{ 1658 Status: "ok", 1659 Cluster: c.ToProto(), 1660 }, nil 1661 } 1662 1663 // SetReadOnly is part of the vtadminpb.VTAdminServer interface. 1664 func (api *API) SetReadOnly(ctx context.Context, req *vtadminpb.SetReadOnlyRequest) (*vtadminpb.SetReadOnlyResponse, error) { 1665 span, ctx := trace.NewSpan(ctx, "API.SetReadOnly") 1666 defer span.Finish() 1667 1668 tablet, c, err := api.getTabletForAction(ctx, span, rbac.ManageTabletWritabilityAction, req.Alias, req.ClusterIds) 1669 if err != nil { 1670 return nil, err 1671 } 1672 1673 err = c.SetWritable(ctx, &vtctldatapb.SetWritableRequest{ 1674 TabletAlias: tablet.Tablet.Alias, 1675 Writable: false, 1676 }) 1677 if err != nil { 1678 return nil, fmt.Errorf("Error setting tablet to read-only: %w", err) 1679 } 1680 1681 return &vtadminpb.SetReadOnlyResponse{}, nil 1682 } 1683 1684 // SetReadWrite is part of the vtadminpb.VTAdminServer interface. 1685 func (api *API) SetReadWrite(ctx context.Context, req *vtadminpb.SetReadWriteRequest) (*vtadminpb.SetReadWriteResponse, error) { 1686 span, ctx := trace.NewSpan(ctx, "API.SetReadWrite") 1687 defer span.Finish() 1688 1689 tablet, c, err := api.getTabletForAction(ctx, span, rbac.ManageTabletWritabilityAction, req.Alias, req.ClusterIds) 1690 if err != nil { 1691 return nil, err 1692 } 1693 1694 err = c.SetWritable(ctx, &vtctldatapb.SetWritableRequest{ 1695 TabletAlias: tablet.Tablet.Alias, 1696 Writable: true, 1697 }) 1698 if err != nil { 1699 return nil, fmt.Errorf("Error setting tablet to read-write: %w", err) 1700 } 1701 1702 return &vtadminpb.SetReadWriteResponse{}, nil 1703 } 1704 1705 // StartReplication is part of the vtadminpb.VTAdminServer interface. 1706 func (api *API) StartReplication(ctx context.Context, req *vtadminpb.StartReplicationRequest) (*vtadminpb.StartReplicationResponse, error) { 1707 span, ctx := trace.NewSpan(ctx, "API.StartReplication") 1708 defer span.Finish() 1709 1710 tablet, c, err := api.getTabletForAction(ctx, span, rbac.ManageTabletReplicationAction, req.Alias, req.ClusterIds) 1711 if err != nil { 1712 return nil, err 1713 } 1714 1715 start := true 1716 if err := c.ToggleTabletReplication(ctx, tablet, start); err != nil { 1717 return nil, err 1718 } 1719 1720 return &vtadminpb.StartReplicationResponse{ 1721 Status: "ok", 1722 Cluster: c.ToProto(), 1723 }, nil 1724 } 1725 1726 // StopReplication is part of the vtadminpb.VTAdminServer interface. 1727 func (api *API) StopReplication(ctx context.Context, req *vtadminpb.StopReplicationRequest) (*vtadminpb.StopReplicationResponse, error) { 1728 span, ctx := trace.NewSpan(ctx, "API.StopReplication") 1729 defer span.Finish() 1730 1731 tablet, c, err := api.getTabletForAction(ctx, span, rbac.ManageTabletReplicationAction, req.Alias, req.ClusterIds) 1732 if err != nil { 1733 return nil, err 1734 } 1735 1736 start := true 1737 if err := c.ToggleTabletReplication(ctx, tablet, !start); err != nil { 1738 return nil, err 1739 } 1740 1741 return &vtadminpb.StopReplicationResponse{ 1742 Status: "ok", 1743 Cluster: c.ToProto(), 1744 }, nil 1745 } 1746 1747 // TabletExternallyPromoted is part of the vtadminpb.VTAdminServer interface. 1748 func (api *API) TabletExternallyPromoted(ctx context.Context, req *vtadminpb.TabletExternallyPromotedRequest) (*vtadminpb.TabletExternallyPromotedResponse, error) { 1749 span, ctx := trace.NewSpan(ctx, "API.TabletExternallyPromoted") 1750 defer span.Finish() 1751 1752 tablet, c, err := api.getTabletForShardAction(ctx, span, rbac.TabletExternallyPromotedAction, req.Alias, req.ClusterIds) 1753 if err != nil { 1754 return nil, err 1755 } 1756 1757 return c.TabletExternallyPromoted(ctx, tablet) 1758 } 1759 1760 // Validate is part of the vtadminpb.VTAdminServer interface. 1761 func (api *API) Validate(ctx context.Context, req *vtadminpb.ValidateRequest) (*vtctldatapb.ValidateResponse, error) { 1762 span, ctx := trace.NewSpan(ctx, "API.Validate") 1763 defer span.Finish() 1764 1765 c, err := api.getClusterForRequest(req.ClusterId) 1766 if err != nil { 1767 return nil, err 1768 } 1769 1770 if !api.authz.IsAuthorized(ctx, c.ID, rbac.ClusterResource, rbac.PutAction) { 1771 return nil, nil 1772 } 1773 1774 res, err := c.Vtctld.Validate(ctx, &vtctldatapb.ValidateRequest{ 1775 PingTablets: req.PingTablets, 1776 }) 1777 1778 if err != nil { 1779 return nil, err 1780 } 1781 1782 return res, nil 1783 } 1784 1785 // ValidateKeyspace is part of the vtadminpb.VTAdminServer interface. 1786 func (api *API) ValidateKeyspace(ctx context.Context, req *vtadminpb.ValidateKeyspaceRequest) (*vtctldatapb.ValidateKeyspaceResponse, error) { 1787 span, ctx := trace.NewSpan(ctx, "API.ValidateKeyspace") 1788 defer span.Finish() 1789 1790 c, err := api.getClusterForRequest(req.ClusterId) 1791 if err != nil { 1792 return nil, err 1793 } 1794 1795 if !api.authz.IsAuthorized(ctx, c.ID, rbac.KeyspaceResource, rbac.PutAction) { 1796 return nil, nil 1797 } 1798 1799 res, err := c.Vtctld.ValidateKeyspace(ctx, &vtctldatapb.ValidateKeyspaceRequest{ 1800 Keyspace: req.Keyspace, 1801 PingTablets: req.PingTablets, 1802 }) 1803 1804 if err != nil { 1805 return nil, err 1806 } 1807 1808 return res, nil 1809 } 1810 1811 // ValidateSchemaKeyspace is part of the vtadminpb.VTAdminServer interface. 1812 func (api *API) ValidateSchemaKeyspace(ctx context.Context, req *vtadminpb.ValidateSchemaKeyspaceRequest) (*vtctldatapb.ValidateSchemaKeyspaceResponse, error) { 1813 span, ctx := trace.NewSpan(ctx, "API.ValidateSchemaKeyspace") 1814 defer span.Finish() 1815 1816 c, err := api.getClusterForRequest(req.ClusterId) 1817 if err != nil { 1818 return nil, err 1819 } 1820 1821 if !api.authz.IsAuthorized(ctx, c.ID, rbac.KeyspaceResource, rbac.PutAction) { 1822 return nil, nil 1823 } 1824 1825 res, err := c.Vtctld.ValidateSchemaKeyspace(ctx, &vtctldatapb.ValidateSchemaKeyspaceRequest{ 1826 Keyspace: req.Keyspace, 1827 }) 1828 1829 if err != nil { 1830 return nil, err 1831 } 1832 1833 return res, nil 1834 } 1835 1836 // ValidateShard is part of the vtadminpb.VTAdminServer interface. 1837 func (api *API) ValidateShard(ctx context.Context, req *vtadminpb.ValidateShardRequest) (*vtctldatapb.ValidateShardResponse, error) { 1838 span, ctx := trace.NewSpan(ctx, "API.ValidateShard") 1839 defer span.Finish() 1840 1841 c, err := api.getClusterForRequest(req.ClusterId) 1842 if err != nil { 1843 return nil, err 1844 } 1845 1846 if !api.authz.IsAuthorized(ctx, c.ID, rbac.ShardResource, rbac.PutAction) { 1847 return nil, nil 1848 } 1849 1850 res, err := c.Vtctld.ValidateShard(ctx, &vtctldatapb.ValidateShardRequest{ 1851 Keyspace: req.Keyspace, 1852 Shard: req.Shard, 1853 PingTablets: req.PingTablets, 1854 }) 1855 1856 if err != nil { 1857 return nil, err 1858 } 1859 1860 return res, nil 1861 } 1862 1863 // ValidateVersionKeyspace is part of the vtadminpb.VTAdminServer interface. 1864 func (api *API) ValidateVersionKeyspace(ctx context.Context, req *vtadminpb.ValidateVersionKeyspaceRequest) (*vtctldatapb.ValidateVersionKeyspaceResponse, error) { 1865 span, ctx := trace.NewSpan(ctx, "API.ValidateVersionKeyspace") 1866 defer span.Finish() 1867 1868 c, err := api.getClusterForRequest(req.ClusterId) 1869 if err != nil { 1870 return nil, err 1871 } 1872 1873 if !api.authz.IsAuthorized(ctx, c.ID, rbac.KeyspaceResource, rbac.PutAction) { 1874 return nil, nil 1875 } 1876 1877 res, err := c.Vtctld.ValidateVersionKeyspace(ctx, &vtctldatapb.ValidateVersionKeyspaceRequest{ 1878 Keyspace: req.Keyspace, 1879 }) 1880 1881 if err != nil { 1882 return nil, err 1883 } 1884 1885 return res, nil 1886 } 1887 1888 // ValidateVersionShard is part of the vtadminpb.VTAdminServer interface. 1889 func (api *API) ValidateVersionShard(ctx context.Context, req *vtadminpb.ValidateVersionShardRequest) (*vtctldatapb.ValidateVersionShardResponse, error) { 1890 span, ctx := trace.NewSpan(ctx, "API.ValidateVersionShard") 1891 defer span.Finish() 1892 1893 c, err := api.getClusterForRequest(req.ClusterId) 1894 if err != nil { 1895 return nil, err 1896 } 1897 1898 if !api.authz.IsAuthorized(ctx, c.ID, rbac.ShardResource, rbac.PutAction) { 1899 return nil, nil 1900 } 1901 1902 res, err := c.Vtctld.ValidateVersionShard(ctx, &vtctldatapb.ValidateVersionShardRequest{ 1903 Keyspace: req.Keyspace, 1904 Shard: req.Shard, 1905 }) 1906 1907 if err != nil { 1908 return nil, err 1909 } 1910 1911 return res, nil 1912 } 1913 1914 // VTExplain is part of the vtadminpb.VTAdminServer interface. 1915 func (api *API) VTExplain(ctx context.Context, req *vtadminpb.VTExplainRequest) (*vtadminpb.VTExplainResponse, error) { 1916 // TODO (andrew): https://github.com/vitessio/vitess/issues/12161. 1917 log.Warningf("VTAdminServer.VTExplain is deprecated; please use a vexplain query instead. For more details, see https://vitess.io/docs/user-guides/sql/vexplain/.") 1918 1919 span, ctx := trace.NewSpan(ctx, "API.VTExplain") 1920 defer span.Finish() 1921 1922 if req.Cluster == "" { 1923 return nil, fmt.Errorf("%w: cluster ID is required", errors.ErrInvalidRequest) 1924 } 1925 1926 if req.Keyspace == "" { 1927 return nil, fmt.Errorf("%w: keyspace name is required", errors.ErrInvalidRequest) 1928 } 1929 1930 if req.Sql == "" { 1931 return nil, fmt.Errorf("%w: SQL query is required", errors.ErrInvalidRequest) 1932 } 1933 1934 c, err := api.getClusterForRequest(req.Cluster) 1935 if err != nil { 1936 return nil, err 1937 } 1938 1939 span.Annotate("keyspace", req.Keyspace) 1940 cluster.AnnotateSpan(c, span) 1941 1942 if !api.authz.IsAuthorized(ctx, c.ID, rbac.VTExplainResource, rbac.GetAction) { 1943 return nil, nil 1944 } 1945 1946 tablet, err := c.FindTablet(ctx, func(t *vtadminpb.Tablet) bool { 1947 return t.Tablet.Keyspace == req.Keyspace && topo.IsInServingGraph(t.Tablet.Type) && t.Tablet.Type != topodatapb.TabletType_PRIMARY && t.State == vtadminpb.Tablet_SERVING 1948 }) 1949 if err != nil { 1950 return nil, fmt.Errorf("cannot find serving, non-primary tablet in keyspace=%s: %w", req.Keyspace, err) 1951 } 1952 1953 span.Annotate("tablet_alias", topoproto.TabletAliasString(tablet.Tablet.Alias)) 1954 1955 var ( 1956 wg sync.WaitGroup 1957 er concurrency.AllErrorRecorder 1958 1959 // Writes to these three variables are, in the strictest sense, unsafe. 1960 // However, there is one goroutine responsible for writing each of these 1961 // values (so, no concurrent writes), and reads are blocked on the call to 1962 // wg.Wait(), so we guarantee that all writes have finished before attempting 1963 // to read anything. 1964 srvVSchema string 1965 schema string 1966 shardMap string 1967 ) 1968 1969 wg.Add(3) 1970 1971 // GetSchema 1972 go func(c *cluster.Cluster) { 1973 defer wg.Done() 1974 1975 res, err := c.GetSchema(ctx, req.Keyspace, cluster.GetSchemaOptions{}) 1976 if err != nil { 1977 er.RecordError(fmt.Errorf("GetSchema(%s): %w", topoproto.TabletAliasString(tablet.Tablet.Alias), err)) 1978 return 1979 } 1980 1981 schemas := make([]string, len(res.TableDefinitions)) 1982 for i, td := range res.TableDefinitions { 1983 schemas[i] = td.Schema 1984 } 1985 1986 schema = strings.Join(schemas, ";") 1987 }(c) 1988 1989 // GetSrvVSchema 1990 go func(c *cluster.Cluster) { 1991 defer wg.Done() 1992 1993 span, ctx := trace.NewSpan(ctx, "Cluster.GetSrvVSchema") 1994 defer span.Finish() 1995 1996 span.Annotate("cell", tablet.Tablet.Alias.Cell) 1997 cluster.AnnotateSpan(c, span) 1998 1999 res, err := c.Vtctld.GetSrvVSchema(ctx, &vtctldatapb.GetSrvVSchemaRequest{ 2000 Cell: tablet.Tablet.Alias.Cell, 2001 }) 2002 2003 if err != nil { 2004 er.RecordError(fmt.Errorf("GetSrvVSchema(%s): %w", tablet.Tablet.Alias.Cell, err)) 2005 return 2006 } 2007 2008 ksvs, ok := res.SrvVSchema.Keyspaces[req.Keyspace] 2009 if !ok { 2010 er.RecordError(fmt.Errorf("%w: keyspace %s", errors.ErrNoSrvVSchema, req.Keyspace)) 2011 return 2012 } 2013 2014 ksvsb, err := json.Marshal(&ksvs) 2015 if err != nil { 2016 er.RecordError(err) 2017 return 2018 } 2019 2020 srvVSchema = fmt.Sprintf(`{"%s": %s}`, req.Keyspace, string(ksvsb)) 2021 }(c) 2022 2023 // FindAllShardsInKeyspace 2024 go func(c *cluster.Cluster) { 2025 defer wg.Done() 2026 2027 shards, err := c.FindAllShardsInKeyspace(ctx, req.Keyspace, cluster.FindAllShardsInKeyspaceOptions{}) 2028 if err != nil { 2029 er.RecordError(err) 2030 return 2031 } 2032 2033 vtsm := make(map[string]*topodatapb.Shard) 2034 for _, s := range shards { 2035 vtsm[s.Name] = s.Shard 2036 } 2037 2038 vtsb, err := json.Marshal(&vtsm) 2039 if err != nil { 2040 er.RecordError(err) 2041 return 2042 } 2043 2044 shardMap = fmt.Sprintf(`{"%s": %s}`, req.Keyspace, string(vtsb)) 2045 }(c) 2046 2047 wg.Wait() 2048 2049 if er.HasErrors() { 2050 return nil, er.Error() 2051 } 2052 2053 vte, err := vtexplain.Init(srvVSchema, schema, shardMap, &vtexplain.Options{ReplicationMode: "ROW"}) 2054 if err != nil { 2055 return nil, fmt.Errorf("error initilaizing vtexplain: %w", err) 2056 } 2057 defer vte.Stop() 2058 2059 plans, err := vte.Run(req.Sql) 2060 if err != nil { 2061 return nil, fmt.Errorf("error running vtexplain: %w", err) 2062 } 2063 2064 response, err := vte.ExplainsAsText(plans) 2065 if err != nil { 2066 return nil, fmt.Errorf("error converting vtexplain to text output: %w", err) 2067 } 2068 2069 return &vtadminpb.VTExplainResponse{ 2070 Response: response, 2071 }, nil 2072 } 2073 2074 func (api *API) getClusterForRequest(id string) (*cluster.Cluster, error) { 2075 api.clusterMu.Lock() 2076 defer api.clusterMu.Unlock() 2077 2078 c, ok := api.clusterMap[id] 2079 if !ok { 2080 return nil, fmt.Errorf("%w: no cluster with id %s", errors.ErrUnsupportedCluster, id) 2081 } 2082 2083 return c, nil 2084 } 2085 2086 func (api *API) getClustersForRequest(ids []string) ([]*cluster.Cluster, []string) { 2087 api.clusterMu.Lock() 2088 defer api.clusterMu.Unlock() 2089 2090 if len(ids) == 0 { 2091 clusterIDs := make([]string, 0, len(api.clusters)) 2092 2093 for k := range api.clusterMap { 2094 clusterIDs = append(clusterIDs, k) 2095 } 2096 2097 return api.clusters, clusterIDs 2098 } 2099 2100 clusters := make([]*cluster.Cluster, 0, len(ids)) 2101 2102 for _, id := range ids { 2103 if c, ok := api.clusterMap[id]; ok { 2104 clusters = append(clusters, c) 2105 } 2106 } 2107 2108 return clusters, ids 2109 } 2110 2111 func (api *API) getTabletForAction(ctx context.Context, span trace.Span, action rbac.Action, alias *topodatapb.TabletAlias, clusterIDs []string) (*vtadminpb.Tablet, *cluster.Cluster, error) { 2112 return api.getTabletForResourceAndAction(ctx, span, rbac.TabletResource, action, alias, clusterIDs) 2113 } 2114 2115 func (api *API) getTabletForShardAction(ctx context.Context, span trace.Span, action rbac.Action, alias *topodatapb.TabletAlias, clusterIDs []string) (*vtadminpb.Tablet, *cluster.Cluster, error) { 2116 return api.getTabletForResourceAndAction(ctx, span, rbac.ShardResource, action, alias, clusterIDs) 2117 } 2118 2119 func (api *API) getTabletForResourceAndAction( 2120 ctx context.Context, 2121 span trace.Span, 2122 resource rbac.Resource, 2123 action rbac.Action, 2124 alias *topodatapb.TabletAlias, 2125 clusterIDs []string, 2126 ) (*vtadminpb.Tablet, *cluster.Cluster, error) { 2127 span.Annotate("tablet_alias", topoproto.TabletAliasString(alias)) 2128 span.Annotate("tablet_cell", alias.Cell) 2129 span.Annotate("tablet_uid", alias.Uid) 2130 2131 clusters, ids := api.getClustersForRequest(clusterIDs) 2132 2133 var ( 2134 m sync.Mutex 2135 wg sync.WaitGroup 2136 rec concurrency.AllErrorRecorder 2137 2138 tablets []*vtadminpb.Tablet 2139 ) 2140 2141 for _, c := range clusters { 2142 if !api.authz.IsAuthorized(ctx, c.ID, resource, action) { 2143 continue 2144 } 2145 2146 wg.Add(1) 2147 2148 go func(c *cluster.Cluster) { 2149 defer wg.Done() 2150 2151 ts, err := c.FindTablets(ctx, func(t *vtadminpb.Tablet) bool { 2152 return topoproto.TabletAliasEqual(t.Tablet.Alias, alias) 2153 }, -1) 2154 if err != nil { 2155 rec.RecordError(fmt.Errorf("FindTablets(cluster = %s): %w", c.ID, err)) 2156 return 2157 } 2158 2159 m.Lock() 2160 tablets = append(tablets, ts...) 2161 m.Unlock() 2162 }(c) 2163 } 2164 2165 wg.Wait() 2166 2167 if rec.HasErrors() { 2168 return nil, nil, rec.Error() 2169 } 2170 2171 switch len(tablets) { 2172 case 0: 2173 return nil, nil, vterrors.Errorf(vtrpcpb.Code_NOT_FOUND, "%s: %s, searched clusters = %v", errors.ErrNoTablet, alias, ids) 2174 case 1: 2175 t := tablets[0] 2176 for _, c := range clusters { 2177 if c.ID == t.Cluster.Id { 2178 return t, c, nil 2179 } 2180 } 2181 2182 return nil, nil, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "impossible: found tablet from cluster %s but cannot find cluster with that id", t.Cluster.Id) 2183 } 2184 2185 return nil, nil, vterrors.Errorf(vtrpcpb.Code_NOT_FOUND, "%s: %s, searched clusters = %v", errors.ErrAmbiguousTablet, alias, ids) 2186 }