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  }