vitess.io/vitess@v0.16.2/go/vt/vtadmin/api_test.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/base64"
    22  	"encoding/json"
    23  	"errors"
    24  	"fmt"
    25  	"net/http"
    26  	"net/http/httptest"
    27  	"net/url"
    28  	"os"
    29  	"testing"
    30  
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  	"google.golang.org/protobuf/proto"
    34  
    35  	_flag "vitess.io/vitess/go/internal/flag"
    36  	"vitess.io/vitess/go/test/utils"
    37  	"vitess.io/vitess/go/vt/topo"
    38  	"vitess.io/vitess/go/vt/topo/memorytopo"
    39  	"vitess.io/vitess/go/vt/topo/topoproto"
    40  	"vitess.io/vitess/go/vt/vtadmin/cluster"
    41  	"vitess.io/vitess/go/vt/vtadmin/cluster/discovery/fakediscovery"
    42  	vtadminerrors "vitess.io/vitess/go/vt/vtadmin/errors"
    43  	vtadmintestutil "vitess.io/vitess/go/vt/vtadmin/testutil"
    44  	"vitess.io/vitess/go/vt/vtadmin/vtctldclient/fakevtctldclient"
    45  	"vitess.io/vitess/go/vt/vtctl/grpcvtctldserver"
    46  	"vitess.io/vitess/go/vt/vtctl/grpcvtctldserver/testutil"
    47  	"vitess.io/vitess/go/vt/vtctl/vtctldclient"
    48  	"vitess.io/vitess/go/vt/vttablet/tmclient"
    49  	"vitess.io/vitess/go/vt/vttablet/tmclienttest"
    50  
    51  	querypb "vitess.io/vitess/go/vt/proto/query"
    52  	tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata"
    53  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    54  	vschemapb "vitess.io/vitess/go/vt/proto/vschema"
    55  	vtadminpb "vitess.io/vitess/go/vt/proto/vtadmin"
    56  	vtctldatapb "vitess.io/vitess/go/vt/proto/vtctldata"
    57  	vtctlservicepb "vitess.io/vitess/go/vt/proto/vtctlservice"
    58  	"vitess.io/vitess/go/vt/proto/vttime"
    59  )
    60  
    61  func TestMain(m *testing.M) {
    62  	_flag.ParseFlagsForTest()
    63  	os.Exit(m.Run())
    64  }
    65  
    66  func TestFindSchema(t *testing.T) {
    67  	t.Parallel()
    68  
    69  	tests := []struct {
    70  		name      string
    71  		clusters  []vtadmintestutil.TestClusterConfig
    72  		req       *vtadminpb.FindSchemaRequest
    73  		expected  *vtadminpb.Schema
    74  		shouldErr bool
    75  	}{
    76  		{
    77  			name: "exact match",
    78  			clusters: []vtadmintestutil.TestClusterConfig{
    79  				{
    80  					Cluster: &vtadminpb.Cluster{
    81  						Id:   "c1",
    82  						Name: "cluster1",
    83  					},
    84  					VtctldClient: &fakevtctldclient.VtctldClient{
    85  						GetKeyspacesResults: &struct {
    86  							Keyspaces []*vtctldatapb.Keyspace
    87  							Error     error
    88  						}{
    89  							Keyspaces: []*vtctldatapb.Keyspace{
    90  								{
    91  									Name: "testkeyspace",
    92  								},
    93  							},
    94  						},
    95  						GetSchemaResults: map[string]struct {
    96  							Response *vtctldatapb.GetSchemaResponse
    97  							Error    error
    98  						}{
    99  							"zone1-0000000100": {
   100  								Response: &vtctldatapb.GetSchemaResponse{
   101  									Schema: &tabletmanagerdatapb.SchemaDefinition{
   102  										TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
   103  											{
   104  												Name: "testtable",
   105  											},
   106  										},
   107  									},
   108  								},
   109  							},
   110  						},
   111  						FindAllShardsInKeyspaceResults: map[string]struct {
   112  							Response *vtctldatapb.FindAllShardsInKeyspaceResponse
   113  							Error    error
   114  						}{
   115  							"testkeyspace": {
   116  								Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
   117  									Shards: map[string]*vtctldatapb.Shard{
   118  										"-": {
   119  											Shard: &topodatapb.Shard{
   120  												IsPrimaryServing: true,
   121  											},
   122  										},
   123  									},
   124  								},
   125  							},
   126  						},
   127  					},
   128  					Tablets: []*vtadminpb.Tablet{
   129  						{
   130  							Tablet: &topodatapb.Tablet{
   131  								Alias: &topodatapb.TabletAlias{
   132  									Cell: "zone1",
   133  									Uid:  100,
   134  								},
   135  								Keyspace: "testkeyspace",
   136  								Shard:    "-",
   137  							},
   138  							State: vtadminpb.Tablet_SERVING,
   139  						},
   140  					},
   141  				},
   142  			},
   143  			req: &vtadminpb.FindSchemaRequest{
   144  				Table: "testtable",
   145  			},
   146  			expected: &vtadminpb.Schema{
   147  				Cluster: &vtadminpb.Cluster{
   148  					Id:   "c1",
   149  					Name: "cluster1",
   150  				},
   151  				Keyspace: "testkeyspace",
   152  				TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
   153  					{
   154  						Name: "testtable",
   155  					},
   156  				},
   157  				TableSizes: map[string]*vtadminpb.Schema_TableSize{},
   158  			},
   159  			shouldErr: false,
   160  		},
   161  		{
   162  			name: "error getting tablets",
   163  			clusters: []vtadmintestutil.TestClusterConfig{
   164  				{
   165  					Cluster: &vtadminpb.Cluster{
   166  						Id:   "c1",
   167  						Name: "cluster1",
   168  					},
   169  
   170  					DBConfig: vtadmintestutil.Dbcfg{
   171  						ShouldErr: true,
   172  					},
   173  					VtctldClient: &fakevtctldclient.VtctldClient{
   174  						GetKeyspacesResults: &struct {
   175  							Keyspaces []*vtctldatapb.Keyspace
   176  							Error     error
   177  						}{
   178  							Keyspaces: []*vtctldatapb.Keyspace{
   179  								{Name: "testkeyspace"},
   180  							},
   181  						},
   182  					},
   183  				},
   184  			},
   185  			req: &vtadminpb.FindSchemaRequest{
   186  				Table: "testtable",
   187  			},
   188  			shouldErr: true,
   189  		},
   190  		{
   191  			name: "error getting keyspaces",
   192  			clusters: []vtadmintestutil.TestClusterConfig{
   193  				{
   194  					Cluster: &vtadminpb.Cluster{
   195  						Id:   "c1",
   196  						Name: "cluster1",
   197  					},
   198  					VtctldClient: &fakevtctldclient.VtctldClient{
   199  						GetKeyspacesResults: &struct {
   200  							Keyspaces []*vtctldatapb.Keyspace
   201  							Error     error
   202  						}{
   203  							Error: fmt.Errorf("GetKeyspaces: %w", assert.AnError),
   204  						},
   205  					},
   206  					Tablets: []*vtadminpb.Tablet{
   207  						{
   208  							Tablet: &topodatapb.Tablet{
   209  								Alias: &topodatapb.TabletAlias{
   210  									Cell: "zone1",
   211  									Uid:  100,
   212  								},
   213  								Keyspace: "testkeyspace",
   214  							},
   215  							State: vtadminpb.Tablet_SERVING,
   216  						},
   217  					},
   218  				},
   219  			},
   220  			req: &vtadminpb.FindSchemaRequest{
   221  				Table: "testtable",
   222  			},
   223  			shouldErr: true,
   224  		},
   225  		{
   226  			name: "error getting schemas",
   227  			clusters: []vtadmintestutil.TestClusterConfig{
   228  				{
   229  					Cluster: &vtadminpb.Cluster{
   230  						Id:   "c1",
   231  						Name: "cluster1",
   232  					},
   233  					VtctldClient: &fakevtctldclient.VtctldClient{
   234  						GetKeyspacesResults: &struct {
   235  							Keyspaces []*vtctldatapb.Keyspace
   236  							Error     error
   237  						}{
   238  							Keyspaces: []*vtctldatapb.Keyspace{
   239  								{
   240  									Name: "testkeyspace",
   241  								},
   242  							},
   243  						},
   244  						GetSchemaResults: map[string]struct {
   245  							Response *vtctldatapb.GetSchemaResponse
   246  							Error    error
   247  						}{
   248  							"zone1-0000000100": {
   249  								Error: fmt.Errorf("GetSchema: %w", assert.AnError),
   250  							},
   251  						},
   252  					},
   253  					Tablets: []*vtadminpb.Tablet{
   254  						{
   255  							Tablet: &topodatapb.Tablet{
   256  								Alias: &topodatapb.TabletAlias{
   257  									Cell: "zone1",
   258  									Uid:  100,
   259  								},
   260  								Keyspace: "testkeyspace",
   261  							},
   262  							State: vtadminpb.Tablet_SERVING,
   263  						},
   264  					},
   265  				},
   266  			},
   267  			req: &vtadminpb.FindSchemaRequest{
   268  				Table: "testtable",
   269  			},
   270  			shouldErr: true,
   271  		},
   272  		{
   273  			name: "no schema found",
   274  			clusters: []vtadmintestutil.TestClusterConfig{
   275  				{
   276  					Cluster: &vtadminpb.Cluster{
   277  						Id:   "c1",
   278  						Name: "cluster1",
   279  					},
   280  					VtctldClient: &fakevtctldclient.VtctldClient{
   281  						GetKeyspacesResults: &struct {
   282  							Keyspaces []*vtctldatapb.Keyspace
   283  							Error     error
   284  						}{
   285  							Keyspaces: []*vtctldatapb.Keyspace{
   286  								{
   287  									Name: "testkeyspace",
   288  								},
   289  							},
   290  						},
   291  						GetSchemaResults: map[string]struct {
   292  							Response *vtctldatapb.GetSchemaResponse
   293  							Error    error
   294  						}{
   295  							"zone1-0000000100": {
   296  								Response: &vtctldatapb.GetSchemaResponse{
   297  									Schema: &tabletmanagerdatapb.SchemaDefinition{
   298  										TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
   299  											{
   300  												Name: "othertable",
   301  											},
   302  										},
   303  									},
   304  								},
   305  							},
   306  						},
   307  					},
   308  					Tablets: []*vtadminpb.Tablet{
   309  						{
   310  							Tablet: &topodatapb.Tablet{
   311  								Alias: &topodatapb.TabletAlias{
   312  									Cell: "zone1",
   313  									Uid:  100,
   314  								},
   315  								Keyspace: "testkeyspace",
   316  							},
   317  							State: vtadminpb.Tablet_SERVING,
   318  						},
   319  					},
   320  				},
   321  			},
   322  			req: &vtadminpb.FindSchemaRequest{
   323  				Table: "testtable",
   324  			},
   325  			shouldErr: true,
   326  		},
   327  		{
   328  			name: "ambiguous schema errors",
   329  			clusters: []vtadmintestutil.TestClusterConfig{
   330  				{
   331  					Cluster: &vtadminpb.Cluster{
   332  						Id:   "c1",
   333  						Name: "cluster1",
   334  					},
   335  					VtctldClient: &fakevtctldclient.VtctldClient{
   336  						GetKeyspacesResults: &struct {
   337  							Keyspaces []*vtctldatapb.Keyspace
   338  							Error     error
   339  						}{
   340  							Keyspaces: []*vtctldatapb.Keyspace{
   341  								{
   342  									Name: "testkeyspace",
   343  								},
   344  							},
   345  						},
   346  						GetSchemaResults: map[string]struct {
   347  							Response *vtctldatapb.GetSchemaResponse
   348  							Error    error
   349  						}{
   350  							"zone1-0000000100": {
   351  								Response: &vtctldatapb.GetSchemaResponse{
   352  									Schema: &tabletmanagerdatapb.SchemaDefinition{
   353  										TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
   354  											{
   355  												Name: "testtable",
   356  											},
   357  										},
   358  									},
   359  								},
   360  							},
   361  						},
   362  					},
   363  					Tablets: []*vtadminpb.Tablet{
   364  						{
   365  							Tablet: &topodatapb.Tablet{
   366  								Alias: &topodatapb.TabletAlias{
   367  									Cell: "zone1",
   368  									Uid:  100,
   369  								},
   370  								Keyspace: "testkeyspace",
   371  							},
   372  							State: vtadminpb.Tablet_SERVING,
   373  						},
   374  					},
   375  				},
   376  				{
   377  					Cluster: &vtadminpb.Cluster{
   378  						Id:   "c2",
   379  						Name: "cluster2",
   380  					},
   381  					VtctldClient: &fakevtctldclient.VtctldClient{
   382  						GetKeyspacesResults: &struct {
   383  							Keyspaces []*vtctldatapb.Keyspace
   384  							Error     error
   385  						}{
   386  							Keyspaces: []*vtctldatapb.Keyspace{
   387  								{
   388  									Name: "testkeyspace",
   389  								},
   390  							},
   391  						},
   392  						GetSchemaResults: map[string]struct {
   393  							Response *vtctldatapb.GetSchemaResponse
   394  							Error    error
   395  						}{
   396  							"zone2-0000000200": {
   397  								Response: &vtctldatapb.GetSchemaResponse{
   398  									Schema: &tabletmanagerdatapb.SchemaDefinition{
   399  										TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
   400  											{
   401  												Name: "testtable",
   402  											},
   403  										},
   404  									},
   405  								},
   406  							},
   407  						},
   408  					},
   409  					Tablets: []*vtadminpb.Tablet{
   410  						{
   411  							Tablet: &topodatapb.Tablet{
   412  								Alias: &topodatapb.TabletAlias{
   413  									Cell: "zone2",
   414  									Uid:  200,
   415  								},
   416  								Keyspace: "testkeyspace",
   417  							},
   418  							State: vtadminpb.Tablet_SERVING,
   419  						},
   420  					},
   421  				},
   422  			},
   423  			req: &vtadminpb.FindSchemaRequest{
   424  				Table: "testtable",
   425  			},
   426  			shouldErr: true,
   427  		},
   428  		{
   429  			name: "ambiguous schema with request scoped to single cluster passes",
   430  			clusters: []vtadmintestutil.TestClusterConfig{
   431  				{
   432  					Cluster: &vtadminpb.Cluster{
   433  						Id:   "c1",
   434  						Name: "cluster1",
   435  					},
   436  					VtctldClient: &fakevtctldclient.VtctldClient{
   437  						GetKeyspacesResults: &struct {
   438  							Keyspaces []*vtctldatapb.Keyspace
   439  							Error     error
   440  						}{
   441  							Keyspaces: []*vtctldatapb.Keyspace{
   442  								{
   443  									Name: "testkeyspace1",
   444  								},
   445  							},
   446  						},
   447  						GetSchemaResults: map[string]struct {
   448  							Response *vtctldatapb.GetSchemaResponse
   449  							Error    error
   450  						}{
   451  							"zone1-0000000100": {
   452  								Response: &vtctldatapb.GetSchemaResponse{
   453  									Schema: &tabletmanagerdatapb.SchemaDefinition{
   454  										TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
   455  											{
   456  												Name: "testtable",
   457  											},
   458  										},
   459  									},
   460  								},
   461  							},
   462  						},
   463  					},
   464  					Tablets: []*vtadminpb.Tablet{
   465  						{
   466  							Tablet: &topodatapb.Tablet{
   467  								Alias: &topodatapb.TabletAlias{
   468  									Cell: "zone1",
   469  									Uid:  100,
   470  								},
   471  								Keyspace: "testkeyspace1",
   472  							},
   473  							State: vtadminpb.Tablet_SERVING,
   474  						},
   475  					},
   476  				},
   477  				{
   478  					Cluster: &vtadminpb.Cluster{
   479  						Id:   "c2",
   480  						Name: "cluster2",
   481  					},
   482  					VtctldClient: &fakevtctldclient.VtctldClient{
   483  						GetKeyspacesResults: &struct {
   484  							Keyspaces []*vtctldatapb.Keyspace
   485  							Error     error
   486  						}{
   487  							Keyspaces: []*vtctldatapb.Keyspace{
   488  								{
   489  									Name: "testkeyspace2",
   490  								},
   491  							},
   492  						},
   493  						GetSchemaResults: map[string]struct {
   494  							Response *vtctldatapb.GetSchemaResponse
   495  							Error    error
   496  						}{
   497  							"zone2-0000000200": {
   498  								Response: &vtctldatapb.GetSchemaResponse{
   499  									Schema: &tabletmanagerdatapb.SchemaDefinition{
   500  										TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
   501  											{
   502  												Name: "testtable",
   503  											},
   504  										},
   505  									},
   506  								},
   507  							},
   508  						},
   509  					},
   510  					Tablets: []*vtadminpb.Tablet{
   511  						{
   512  							Tablet: &topodatapb.Tablet{
   513  								Alias: &topodatapb.TabletAlias{
   514  									Cell: "zone2",
   515  									Uid:  200,
   516  								},
   517  								Keyspace: "testkeyspace2",
   518  							},
   519  							State: vtadminpb.Tablet_SERVING,
   520  						},
   521  					},
   522  				},
   523  			},
   524  			req: &vtadminpb.FindSchemaRequest{
   525  				Table:      "testtable",
   526  				ClusterIds: []string{"c1"},
   527  			},
   528  			expected: &vtadminpb.Schema{
   529  				Cluster: &vtadminpb.Cluster{
   530  					Id:   "c1",
   531  					Name: "cluster1",
   532  				},
   533  				Keyspace: "testkeyspace1",
   534  				TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
   535  					{
   536  						Name: "testtable",
   537  					},
   538  				},
   539  				TableSizes: map[string]*vtadminpb.Schema_TableSize{},
   540  			},
   541  			shouldErr: false,
   542  		},
   543  	}
   544  
   545  	ctx := context.Background()
   546  
   547  	for _, tt := range tests {
   548  		tt := tt
   549  
   550  		t.Run(tt.name, func(t *testing.T) {
   551  			t.Parallel()
   552  
   553  			clusters := make([]*cluster.Cluster, len(tt.clusters))
   554  			for i, cfg := range tt.clusters {
   555  				clusters[i] = vtadmintestutil.BuildCluster(t, cfg)
   556  			}
   557  
   558  			api := NewAPI(clusters, Options{})
   559  			defer api.Close()
   560  
   561  			resp, err := api.FindSchema(ctx, tt.req)
   562  			if tt.shouldErr {
   563  				assert.Error(t, err)
   564  
   565  				return
   566  			}
   567  
   568  			assert.NoError(t, err)
   569  			assert.Truef(t, proto.Equal(tt.expected, resp), "expected %v, got %v", tt.expected, resp)
   570  		})
   571  	}
   572  
   573  	t.Run("size aggregation", func(t *testing.T) {
   574  		t.Parallel()
   575  
   576  		c1pb := &vtadminpb.Cluster{
   577  			Id:   "c1",
   578  			Name: "cluster1",
   579  		}
   580  		c2pb := &vtadminpb.Cluster{
   581  			Id:   "c2",
   582  			Name: "cluster2",
   583  		}
   584  
   585  		c1 := vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{
   586  			Cluster: c1pb,
   587  			VtctldClient: &fakevtctldclient.VtctldClient{
   588  				FindAllShardsInKeyspaceResults: map[string]struct {
   589  					Response *vtctldatapb.FindAllShardsInKeyspaceResponse
   590  					Error    error
   591  				}{
   592  					"testkeyspace": {
   593  						Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
   594  							Shards: map[string]*vtctldatapb.Shard{
   595  								"-80": {
   596  									Keyspace: "testkeyspace",
   597  									Name:     "-80",
   598  									Shard: &topodatapb.Shard{
   599  										IsPrimaryServing: true,
   600  										PrimaryAlias: &topodatapb.TabletAlias{
   601  											Cell: "c1zone1",
   602  											Uid:  100,
   603  										},
   604  									},
   605  								},
   606  								"80-": {
   607  									Keyspace: "testkeyspace",
   608  									Name:     "80-",
   609  									Shard: &topodatapb.Shard{
   610  										IsPrimaryServing: true,
   611  										PrimaryAlias: &topodatapb.TabletAlias{
   612  											Cell: "c1zone1",
   613  											Uid:  200,
   614  										},
   615  									},
   616  								},
   617  							},
   618  						},
   619  					},
   620  					"ks1": {
   621  						Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
   622  							Shards: map[string]*vtctldatapb.Shard{
   623  								"-": {
   624  									Keyspace: "ks1",
   625  									Name:     "-",
   626  									Shard: &topodatapb.Shard{
   627  										IsPrimaryServing: true,
   628  										PrimaryAlias: &topodatapb.TabletAlias{
   629  											Cell: "c1zone1",
   630  											Uid:  300,
   631  										},
   632  									},
   633  								},
   634  							},
   635  						},
   636  					},
   637  				},
   638  				GetKeyspacesResults: &struct {
   639  					Keyspaces []*vtctldatapb.Keyspace
   640  					Error     error
   641  				}{
   642  					Keyspaces: []*vtctldatapb.Keyspace{
   643  						{Name: "testkeyspace"},
   644  						{Name: "ks1"},
   645  					},
   646  				},
   647  				GetSchemaResults: map[string]struct {
   648  					Response *vtctldatapb.GetSchemaResponse
   649  					Error    error
   650  				}{
   651  					"c1zone1-0000000100": {
   652  						Response: &vtctldatapb.GetSchemaResponse{
   653  							Schema: &tabletmanagerdatapb.SchemaDefinition{
   654  								TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
   655  									{
   656  										Name:       "testtable",
   657  										RowCount:   10,
   658  										DataLength: 100,
   659  									},
   660  								},
   661  							},
   662  						},
   663  					},
   664  					"c1zone1-0000000200": {
   665  						Response: &vtctldatapb.GetSchemaResponse{
   666  							Schema: &tabletmanagerdatapb.SchemaDefinition{
   667  								TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
   668  									{
   669  										Name:       "testtable",
   670  										RowCount:   20,
   671  										DataLength: 200,
   672  									},
   673  								},
   674  							},
   675  						},
   676  					},
   677  				},
   678  			},
   679  			Tablets: []*vtadminpb.Tablet{
   680  				{
   681  					Cluster: c1pb,
   682  					Tablet: &topodatapb.Tablet{
   683  						Alias: &topodatapb.TabletAlias{
   684  							Cell: "c1zone1",
   685  							Uid:  100,
   686  						},
   687  						Keyspace: "testkeyspace",
   688  						Shard:    "-80",
   689  					},
   690  					State: vtadminpb.Tablet_SERVING,
   691  				},
   692  				{
   693  					Cluster: c1pb,
   694  					Tablet: &topodatapb.Tablet{
   695  						Alias: &topodatapb.TabletAlias{
   696  							Cell: "c1zone1",
   697  							Uid:  200,
   698  						},
   699  						Keyspace: "testkeyspace",
   700  						Shard:    "80-",
   701  					},
   702  					State: vtadminpb.Tablet_SERVING,
   703  				},
   704  			},
   705  		},
   706  		)
   707  		c2 := vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{
   708  			Cluster: c2pb,
   709  			VtctldClient: &fakevtctldclient.VtctldClient{
   710  				FindAllShardsInKeyspaceResults: map[string]struct {
   711  					Response *vtctldatapb.FindAllShardsInKeyspaceResponse
   712  					Error    error
   713  				}{
   714  					"ks2": {
   715  						Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
   716  							Shards: map[string]*vtctldatapb.Shard{
   717  								"-": {
   718  									Keyspace: "ks2",
   719  									Name:     "-",
   720  									Shard: &topodatapb.Shard{
   721  										IsPrimaryServing: true,
   722  										PrimaryAlias: &topodatapb.TabletAlias{
   723  											Cell: "c2z1",
   724  											Uid:  100,
   725  										},
   726  									},
   727  								},
   728  							},
   729  						},
   730  					},
   731  				},
   732  				GetKeyspacesResults: &struct {
   733  					Keyspaces []*vtctldatapb.Keyspace
   734  					Error     error
   735  				}{
   736  					Keyspaces: []*vtctldatapb.Keyspace{
   737  						{
   738  							Name: "ks2",
   739  						},
   740  					},
   741  				},
   742  				GetSchemaResults: map[string]struct {
   743  					Response *vtctldatapb.GetSchemaResponse
   744  					Error    error
   745  				}{
   746  					"c2z1-0000000100": {
   747  						Response: &vtctldatapb.GetSchemaResponse{},
   748  					},
   749  				},
   750  			},
   751  			Tablets: []*vtadminpb.Tablet{
   752  				{
   753  					Cluster: c2pb,
   754  					Tablet: &topodatapb.Tablet{
   755  						Alias: &topodatapb.TabletAlias{
   756  							Cell: "c2z1",
   757  							Uid:  100,
   758  						},
   759  						Keyspace: "ks2",
   760  						Shard:    "-",
   761  					},
   762  					State: vtadminpb.Tablet_SERVING,
   763  				},
   764  			},
   765  		},
   766  		)
   767  
   768  		api := NewAPI([]*cluster.Cluster{c1, c2}, Options{})
   769  		defer api.Close()
   770  
   771  		schema, err := api.FindSchema(ctx, &vtadminpb.FindSchemaRequest{
   772  			Table: "testtable",
   773  			TableSizeOptions: &vtadminpb.GetSchemaTableSizeOptions{
   774  				AggregateSizes: true,
   775  			},
   776  		})
   777  
   778  		expected := &vtadminpb.Schema{
   779  			Cluster:  c1pb,
   780  			Keyspace: "testkeyspace",
   781  			TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
   782  				{
   783  					Name: "testtable",
   784  				},
   785  			},
   786  			TableSizes: map[string]*vtadminpb.Schema_TableSize{
   787  				"testtable": {
   788  					RowCount:   10 + 20,
   789  					DataLength: 100 + 200,
   790  					ByShard: map[string]*vtadminpb.Schema_ShardTableSize{
   791  						"-80": {
   792  							RowCount:   10,
   793  							DataLength: 100,
   794  						},
   795  						"80-": {
   796  							RowCount:   20,
   797  							DataLength: 200,
   798  						},
   799  					},
   800  				},
   801  			},
   802  		}
   803  
   804  		if schema != nil {
   805  			// Clone so our mutation below doesn't trip the race detector.
   806  			schema = proto.Clone(schema).(*vtadminpb.Schema)
   807  
   808  			for _, td := range schema.TableDefinitions {
   809  				// Zero these out because they're non-deterministic and also not
   810  				// relevant to the final result.
   811  				td.RowCount = 0
   812  				td.DataLength = 0
   813  			}
   814  		}
   815  
   816  		assert.NoError(t, err)
   817  		assert.Truef(t, proto.Equal(expected, schema), "expected %v, got %v", expected, schema)
   818  	})
   819  }
   820  
   821  func TestGetClusters(t *testing.T) {
   822  	t.Parallel()
   823  
   824  	tests := []struct {
   825  		name     string
   826  		clusters []*cluster.Cluster
   827  		expected []*vtadminpb.Cluster
   828  	}{
   829  		{
   830  			name: "multiple clusters",
   831  			clusters: []*cluster.Cluster{
   832  				{
   833  					ID:        "c1",
   834  					Name:      "cluster1",
   835  					Discovery: fakediscovery.New(),
   836  				},
   837  				{
   838  					ID:        "c2",
   839  					Name:      "cluster2",
   840  					Discovery: fakediscovery.New(),
   841  				},
   842  			},
   843  			expected: []*vtadminpb.Cluster{
   844  				{
   845  					Id:   "c1",
   846  					Name: "cluster1",
   847  				},
   848  				{
   849  					Id:   "c2",
   850  					Name: "cluster2",
   851  				},
   852  			},
   853  		},
   854  		{
   855  			name:     "no clusters",
   856  			clusters: []*cluster.Cluster{},
   857  			expected: []*vtadminpb.Cluster{},
   858  		},
   859  	}
   860  
   861  	ctx := context.Background()
   862  
   863  	for _, tt := range tests {
   864  		tt := tt
   865  
   866  		t.Run(tt.name, func(t *testing.T) {
   867  			t.Parallel()
   868  
   869  			api := NewAPI(tt.clusters, Options{})
   870  
   871  			resp, err := api.GetClusters(ctx, &vtadminpb.GetClustersRequest{})
   872  			assert.NoError(t, err)
   873  			assert.ElementsMatch(t, tt.expected, resp.Clusters)
   874  		})
   875  	}
   876  }
   877  
   878  func TestGetGates(t *testing.T) {
   879  	t.Parallel()
   880  
   881  	fakedisco1 := fakediscovery.New()
   882  	cluster1 := &cluster.Cluster{
   883  		ID:        "c1",
   884  		Name:      "cluster1",
   885  		Discovery: fakedisco1,
   886  	}
   887  	cluster1Gates := []*vtadminpb.VTGate{
   888  		{
   889  			Hostname: "cluster1-gate1",
   890  		},
   891  		{
   892  			Hostname: "cluster1-gate2",
   893  		},
   894  		{
   895  			Hostname: "cluster1-gate3",
   896  		},
   897  	}
   898  	fakedisco1.AddTaggedGates(nil, cluster1Gates...)
   899  
   900  	expectedCluster1Gates := []*vtadminpb.VTGate{
   901  		{
   902  			Cluster: &vtadminpb.Cluster{
   903  				Id:   cluster1.ID,
   904  				Name: cluster1.Name,
   905  			},
   906  			Hostname: "cluster1-gate1",
   907  		},
   908  		{
   909  			Cluster: &vtadminpb.Cluster{
   910  				Id:   cluster1.ID,
   911  				Name: cluster1.Name,
   912  			},
   913  			Hostname: "cluster1-gate2",
   914  		},
   915  		{
   916  			Cluster: &vtadminpb.Cluster{
   917  				Id:   cluster1.ID,
   918  				Name: cluster1.Name,
   919  			},
   920  			Hostname: "cluster1-gate3",
   921  		},
   922  	}
   923  
   924  	fakedisco2 := fakediscovery.New()
   925  	cluster2 := &cluster.Cluster{
   926  		ID:        "c2",
   927  		Name:      "cluster2",
   928  		Discovery: fakedisco2,
   929  	}
   930  	cluster2Gates := []*vtadminpb.VTGate{
   931  		{
   932  			Hostname: "cluster2-gate1",
   933  		},
   934  	}
   935  	fakedisco2.AddTaggedGates(nil, cluster2Gates...)
   936  
   937  	expectedCluster2Gates := []*vtadminpb.VTGate{
   938  		{
   939  			Cluster: &vtadminpb.Cluster{
   940  				Id:   cluster2.ID,
   941  				Name: cluster2.Name,
   942  			},
   943  			Hostname: "cluster2-gate1",
   944  		},
   945  	}
   946  
   947  	api := NewAPI([]*cluster.Cluster{cluster1, cluster2}, Options{})
   948  	ctx := context.Background()
   949  
   950  	resp, err := api.GetGates(ctx, &vtadminpb.GetGatesRequest{})
   951  	assert.NoError(t, err)
   952  	assert.ElementsMatch(t, append(expectedCluster1Gates, expectedCluster2Gates...), resp.Gates)
   953  
   954  	resp, err = api.GetGates(ctx, &vtadminpb.GetGatesRequest{ClusterIds: []string{cluster1.ID}})
   955  	assert.NoError(t, err)
   956  	assert.ElementsMatch(t, expectedCluster1Gates, resp.Gates)
   957  
   958  	fakedisco1.SetGatesError(true)
   959  
   960  	resp, err = api.GetGates(ctx, &vtadminpb.GetGatesRequest{})
   961  	assert.Error(t, err)
   962  	assert.Nil(t, resp)
   963  }
   964  
   965  func TestGetKeyspace(t *testing.T) {
   966  	t.Parallel()
   967  
   968  	tests := []struct {
   969  		name          string
   970  		clusterShards [][]*vtctldatapb.Shard
   971  		req           *vtadminpb.GetKeyspaceRequest
   972  		expected      *vtadminpb.Keyspace
   973  		shouldErr     bool
   974  	}{
   975  		{
   976  			clusterShards: [][]*vtctldatapb.Shard{
   977  				// cluster-0
   978  				{
   979  					{
   980  						Keyspace: "ks",
   981  						Name:     "-80",
   982  						Shard:    &topodatapb.Shard{},
   983  					},
   984  					{
   985  						Keyspace: "ks",
   986  						Name:     "80-",
   987  						Shard:    &topodatapb.Shard{},
   988  					},
   989  				},
   990  				// cluster-1
   991  				{
   992  					{
   993  						Keyspace: "ks",
   994  						Name:     "-",
   995  						Shard:    &topodatapb.Shard{},
   996  					},
   997  				},
   998  			},
   999  			req: &vtadminpb.GetKeyspaceRequest{
  1000  				ClusterId: "cluster-1",
  1001  				Keyspace:  "ks",
  1002  			},
  1003  			expected: &vtadminpb.Keyspace{
  1004  				Cluster: &vtadminpb.Cluster{
  1005  					Id:   "cluster-1",
  1006  					Name: "cluster-1",
  1007  				},
  1008  				Keyspace: &vtctldatapb.Keyspace{
  1009  					Name:     "ks",
  1010  					Keyspace: &topodatapb.Keyspace{},
  1011  				},
  1012  				Shards: map[string]*vtctldatapb.Shard{
  1013  					"-": {
  1014  						Keyspace: "ks",
  1015  						Name:     "-",
  1016  						Shard:    &topodatapb.Shard{},
  1017  					},
  1018  				},
  1019  			},
  1020  		},
  1021  		{
  1022  			name: "nonexistent cluster",
  1023  			clusterShards: [][]*vtctldatapb.Shard{
  1024  				// cluster-0
  1025  				{
  1026  					{
  1027  						Keyspace: "ks",
  1028  						Name:     "-80",
  1029  						Shard:    &topodatapb.Shard{},
  1030  					},
  1031  					{
  1032  						Keyspace: "ks",
  1033  						Name:     "80-",
  1034  						Shard:    &topodatapb.Shard{},
  1035  					},
  1036  				},
  1037  				// cluster-1
  1038  				{
  1039  					{
  1040  						Keyspace: "ks",
  1041  						Name:     "-",
  1042  						Shard:    &topodatapb.Shard{},
  1043  					},
  1044  				},
  1045  			},
  1046  			req: &vtadminpb.GetKeyspaceRequest{
  1047  				ClusterId: "cluster-2",
  1048  			},
  1049  			shouldErr: true,
  1050  		},
  1051  	}
  1052  
  1053  	ctx := context.Background()
  1054  
  1055  	for _, tt := range tests {
  1056  		tt := tt
  1057  
  1058  		t.Run(tt.name, func(t *testing.T) {
  1059  			t.Parallel()
  1060  
  1061  			topos := make([]*topo.Server, len(tt.clusterShards))
  1062  			vtctlds := make([]vtctlservicepb.VtctldServer, len(tt.clusterShards))
  1063  
  1064  			for i, shards := range tt.clusterShards {
  1065  				ts := memorytopo.NewServer("cell1")
  1066  				testutil.AddShards(ctx, t, ts, shards...)
  1067  				topos[i] = ts
  1068  				vtctlds[i] = testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  1069  					return grpcvtctldserver.NewVtctldServer(ts)
  1070  				})
  1071  			}
  1072  
  1073  			testutil.WithTestServers(t, func(t *testing.T, clients ...vtctldclient.VtctldClient) {
  1074  				clusters := make([]*cluster.Cluster, len(clients))
  1075  				for i, client := range clients {
  1076  					clusters[i] = vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{
  1077  						Cluster: &vtadminpb.Cluster{
  1078  							Id:   fmt.Sprintf("cluster-%d", i),
  1079  							Name: fmt.Sprintf("cluster-%d", i),
  1080  						},
  1081  						VtctldClient: client,
  1082  					})
  1083  				}
  1084  
  1085  				api := NewAPI(clusters, Options{})
  1086  				ks, err := api.GetKeyspace(ctx, tt.req)
  1087  				if tt.shouldErr {
  1088  					assert.Error(t, err)
  1089  					return
  1090  				}
  1091  
  1092  				assert.NoError(t, err)
  1093  				assert.Truef(t, proto.Equal(tt.expected, ks), "expected %v, got %v", tt.expected, ks)
  1094  			}, vtctlds...)
  1095  		})
  1096  	}
  1097  }
  1098  
  1099  func TestGetKeyspaces(t *testing.T) {
  1100  	t.Parallel()
  1101  
  1102  	tests := []struct {
  1103  		name             string
  1104  		clusterKeyspaces [][]*vtctldatapb.Keyspace
  1105  		clusterShards    [][]*vtctldatapb.Shard
  1106  		req              *vtadminpb.GetKeyspacesRequest
  1107  		expected         *vtadminpb.GetKeyspacesResponse
  1108  	}{
  1109  		{
  1110  			name: "multiple clusters, multiple shards",
  1111  			clusterKeyspaces: [][]*vtctldatapb.Keyspace{
  1112  				// cluster0
  1113  				{
  1114  					{
  1115  						Name:     "c0-ks0",
  1116  						Keyspace: &topodatapb.Keyspace{},
  1117  					},
  1118  				},
  1119  				// cluster1
  1120  				{
  1121  					{
  1122  						Name:     "c1-ks0",
  1123  						Keyspace: &topodatapb.Keyspace{},
  1124  					},
  1125  				},
  1126  			},
  1127  			clusterShards: [][]*vtctldatapb.Shard{
  1128  				// cluster0
  1129  				{
  1130  					{
  1131  						Keyspace: "c0-ks0",
  1132  						Name:     "-80",
  1133  					},
  1134  					{
  1135  						Keyspace: "c0-ks0",
  1136  						Name:     "80-",
  1137  					},
  1138  				},
  1139  				// cluster1
  1140  				{
  1141  					{
  1142  						Keyspace: "c1-ks0",
  1143  						Name:     "-",
  1144  					},
  1145  				},
  1146  			},
  1147  			req: &vtadminpb.GetKeyspacesRequest{},
  1148  			expected: &vtadminpb.GetKeyspacesResponse{
  1149  				Keyspaces: []*vtadminpb.Keyspace{
  1150  					{
  1151  						Cluster: &vtadminpb.Cluster{
  1152  							Id:   "c0",
  1153  							Name: "cluster0",
  1154  						},
  1155  						Keyspace: &vtctldatapb.Keyspace{
  1156  							Name:     "c0-ks0",
  1157  							Keyspace: &topodatapb.Keyspace{},
  1158  						},
  1159  						Shards: map[string]*vtctldatapb.Shard{
  1160  							"-80": {
  1161  								Keyspace: "c0-ks0",
  1162  								Name:     "-80",
  1163  								Shard: &topodatapb.Shard{
  1164  									IsPrimaryServing: true,
  1165  								},
  1166  							},
  1167  							"80-": {
  1168  								Keyspace: "c0-ks0",
  1169  								Name:     "80-",
  1170  								Shard: &topodatapb.Shard{
  1171  									IsPrimaryServing: true,
  1172  								},
  1173  							},
  1174  						},
  1175  					},
  1176  					{
  1177  						Cluster: &vtadminpb.Cluster{
  1178  							Id:   "c1",
  1179  							Name: "cluster1",
  1180  						},
  1181  						Keyspace: &vtctldatapb.Keyspace{
  1182  							Name:     "c1-ks0",
  1183  							Keyspace: &topodatapb.Keyspace{},
  1184  						},
  1185  						Shards: map[string]*vtctldatapb.Shard{
  1186  							"-": {
  1187  								Keyspace: "c1-ks0",
  1188  								Name:     "-",
  1189  								Shard: &topodatapb.Shard{
  1190  									IsPrimaryServing: true,
  1191  								},
  1192  							},
  1193  						},
  1194  					},
  1195  				},
  1196  			},
  1197  		},
  1198  		{
  1199  			name: "with snapshot",
  1200  			clusterKeyspaces: [][]*vtctldatapb.Keyspace{
  1201  				// cluster0
  1202  				{
  1203  					{
  1204  						Name:     "testkeyspace",
  1205  						Keyspace: &topodatapb.Keyspace{},
  1206  					},
  1207  					{
  1208  						Name: "snapshot",
  1209  						Keyspace: &topodatapb.Keyspace{
  1210  							KeyspaceType: topodatapb.KeyspaceType_SNAPSHOT,
  1211  							BaseKeyspace: "testkeyspace",
  1212  							SnapshotTime: &vttime.Time{Seconds: 10, Nanoseconds: 1},
  1213  						},
  1214  					},
  1215  				},
  1216  			},
  1217  			req: &vtadminpb.GetKeyspacesRequest{},
  1218  			expected: &vtadminpb.GetKeyspacesResponse{
  1219  				Keyspaces: []*vtadminpb.Keyspace{
  1220  					{
  1221  						Cluster: &vtadminpb.Cluster{
  1222  							Id:   "c0",
  1223  							Name: "cluster0",
  1224  						},
  1225  						Keyspace: &vtctldatapb.Keyspace{
  1226  							Name:     "testkeyspace",
  1227  							Keyspace: &topodatapb.Keyspace{},
  1228  						},
  1229  					},
  1230  					{
  1231  						Cluster: &vtadminpb.Cluster{
  1232  							Id:   "c0",
  1233  							Name: "cluster0",
  1234  						},
  1235  						Keyspace: &vtctldatapb.Keyspace{
  1236  							Name: "snapshot",
  1237  							Keyspace: &topodatapb.Keyspace{
  1238  								KeyspaceType: topodatapb.KeyspaceType_SNAPSHOT,
  1239  								BaseKeyspace: "testkeyspace",
  1240  								SnapshotTime: &vttime.Time{Seconds: 10, Nanoseconds: 1},
  1241  							},
  1242  						},
  1243  					},
  1244  				},
  1245  			},
  1246  		},
  1247  		{
  1248  			name: "filtered by cluster ID",
  1249  			clusterKeyspaces: [][]*vtctldatapb.Keyspace{
  1250  				// cluster0
  1251  				{
  1252  					{
  1253  						Name:     "c0-ks0",
  1254  						Keyspace: &topodatapb.Keyspace{},
  1255  					},
  1256  				},
  1257  				// cluster1
  1258  				{
  1259  					{
  1260  						Name:     "c1-ks0",
  1261  						Keyspace: &topodatapb.Keyspace{},
  1262  					},
  1263  				},
  1264  			},
  1265  			req: &vtadminpb.GetKeyspacesRequest{
  1266  				ClusterIds: []string{"c1"},
  1267  			},
  1268  			expected: &vtadminpb.GetKeyspacesResponse{
  1269  				Keyspaces: []*vtadminpb.Keyspace{
  1270  					{
  1271  						Cluster: &vtadminpb.Cluster{
  1272  							Id:   "c1",
  1273  							Name: "cluster1",
  1274  						},
  1275  						Keyspace: &vtctldatapb.Keyspace{
  1276  							Name:     "c1-ks0",
  1277  							Keyspace: &topodatapb.Keyspace{},
  1278  						},
  1279  					},
  1280  				},
  1281  			},
  1282  		},
  1283  	}
  1284  
  1285  	ctx := context.Background()
  1286  
  1287  	for _, tt := range tests {
  1288  		tt := tt
  1289  
  1290  		t.Run(tt.name, func(t *testing.T) {
  1291  			t.Parallel()
  1292  
  1293  			// Note that these test cases were written prior to the existence of
  1294  			// WithTestServers, so they are all written with the assumption that
  1295  			// there are exactly 2 clusters.
  1296  			topos := []*topo.Server{
  1297  				memorytopo.NewServer("c0_cell1"),
  1298  				memorytopo.NewServer("c1_cell1"),
  1299  			}
  1300  
  1301  			for cdx, cks := range tt.clusterKeyspaces {
  1302  				for _, ks := range cks {
  1303  					testutil.AddKeyspace(ctx, t, topos[cdx], ks)
  1304  				}
  1305  			}
  1306  
  1307  			for cdx, css := range tt.clusterShards {
  1308  				testutil.AddShards(ctx, t, topos[cdx], css...)
  1309  			}
  1310  
  1311  			servers := []vtctlservicepb.VtctldServer{
  1312  				testutil.NewVtctldServerWithTabletManagerClient(t, topos[0], nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  1313  					return grpcvtctldserver.NewVtctldServer(ts)
  1314  				}),
  1315  				testutil.NewVtctldServerWithTabletManagerClient(t, topos[1], nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  1316  					return grpcvtctldserver.NewVtctldServer(ts)
  1317  				}),
  1318  			}
  1319  
  1320  			testutil.WithTestServers(t, func(t *testing.T, clients ...vtctldclient.VtctldClient) {
  1321  				clusters := []*cluster.Cluster{
  1322  					vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{
  1323  						Cluster: &vtadminpb.Cluster{
  1324  							Id:   "c0",
  1325  							Name: "cluster0",
  1326  						},
  1327  						VtctldClient: clients[0],
  1328  					}),
  1329  					vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{
  1330  						Cluster: &vtadminpb.Cluster{
  1331  							Id:   "c1",
  1332  							Name: "cluster1",
  1333  						},
  1334  						VtctldClient: clients[1],
  1335  					}),
  1336  				}
  1337  
  1338  				api := NewAPI(clusters, Options{})
  1339  				resp, err := api.GetKeyspaces(ctx, tt.req)
  1340  				require.NoError(t, err)
  1341  
  1342  				vtadmintestutil.AssertKeyspaceSlicesEqual(t, tt.expected.Keyspaces, resp.Keyspaces)
  1343  			}, servers...)
  1344  		})
  1345  	}
  1346  }
  1347  
  1348  func TestGetSchema(t *testing.T) {
  1349  	t.Parallel()
  1350  
  1351  	tests := []struct {
  1352  		name      string
  1353  		clusterID int
  1354  		ts        *topo.Server
  1355  		tmc       tmclient.TabletManagerClient
  1356  		tablets   []*vtadminpb.Tablet
  1357  		req       *vtadminpb.GetSchemaRequest
  1358  		expected  *vtadminpb.Schema
  1359  		shouldErr bool
  1360  	}{
  1361  		{
  1362  			name:      "success",
  1363  			clusterID: 1,
  1364  			ts:        memorytopo.NewServer("zone1"),
  1365  			tmc: &testutil.TabletManagerClient{
  1366  				GetSchemaResults: map[string]struct {
  1367  					Schema *tabletmanagerdatapb.SchemaDefinition
  1368  					Error  error
  1369  				}{
  1370  					"zone1-0000000100": {
  1371  						Schema: &tabletmanagerdatapb.SchemaDefinition{
  1372  							TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  1373  								{
  1374  									Name: "testtable",
  1375  								},
  1376  							},
  1377  						},
  1378  					},
  1379  				},
  1380  			},
  1381  			tablets: []*vtadminpb.Tablet{
  1382  				{
  1383  					Cluster: &vtadminpb.Cluster{
  1384  						Id:   "c1",
  1385  						Name: "cluster1",
  1386  					},
  1387  					State: vtadminpb.Tablet_SERVING,
  1388  					Tablet: &topodatapb.Tablet{
  1389  						Alias: &topodatapb.TabletAlias{
  1390  							Cell: "zone1",
  1391  							Uid:  100,
  1392  						},
  1393  						Keyspace: "testkeyspace",
  1394  					},
  1395  				},
  1396  			},
  1397  			req: &vtadminpb.GetSchemaRequest{
  1398  				ClusterId: "c1",
  1399  				Keyspace:  "testkeyspace",
  1400  				Table:     "testtable",
  1401  			},
  1402  			expected: &vtadminpb.Schema{
  1403  				Cluster: &vtadminpb.Cluster{
  1404  					Id:   "c1",
  1405  					Name: "cluster1",
  1406  				},
  1407  				Keyspace: "testkeyspace",
  1408  				TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  1409  					{
  1410  						Name: "testtable",
  1411  					},
  1412  				},
  1413  			},
  1414  			shouldErr: false,
  1415  		},
  1416  		{
  1417  			name:      "cluster not found",
  1418  			clusterID: 1, // results in clusterId == "c1"
  1419  			ts:        memorytopo.NewServer("zone1"),
  1420  			tablets:   nil,
  1421  			req: &vtadminpb.GetSchemaRequest{
  1422  				ClusterId: "c2",
  1423  				Keyspace:  "testkeyspace",
  1424  				Table:     "testtable",
  1425  			},
  1426  			expected:  nil,
  1427  			shouldErr: true,
  1428  		},
  1429  		{
  1430  			name:      "tablet not found for keyspace",
  1431  			clusterID: 1,
  1432  			ts:        memorytopo.NewServer("zone1"),
  1433  			tablets: []*vtadminpb.Tablet{
  1434  				{
  1435  					Cluster: &vtadminpb.Cluster{
  1436  						Id:   "c1",
  1437  						Name: "cluster1",
  1438  					},
  1439  					State: vtadminpb.Tablet_SERVING,
  1440  					Tablet: &topodatapb.Tablet{
  1441  						Alias: &topodatapb.TabletAlias{
  1442  							Cell: "zone1",
  1443  							Uid:  100,
  1444  						},
  1445  						Keyspace: "otherkeyspace",
  1446  					},
  1447  				},
  1448  			},
  1449  			req: &vtadminpb.GetSchemaRequest{
  1450  				ClusterId: "c1",
  1451  				Keyspace:  "testkeyspace",
  1452  				Table:     "testtable",
  1453  			},
  1454  			expected:  nil,
  1455  			shouldErr: true,
  1456  		},
  1457  		{
  1458  			name:      "no serving tablet found for keyspace",
  1459  			clusterID: 1,
  1460  			ts:        memorytopo.NewServer("zone1"),
  1461  			tablets: []*vtadminpb.Tablet{
  1462  				{
  1463  					Cluster: &vtadminpb.Cluster{
  1464  						Id:   "c1",
  1465  						Name: "cluster1",
  1466  					},
  1467  					State: vtadminpb.Tablet_NOT_SERVING,
  1468  					Tablet: &topodatapb.Tablet{
  1469  						Alias: &topodatapb.TabletAlias{
  1470  							Cell: "zone1",
  1471  							Uid:  100,
  1472  						},
  1473  						Keyspace: "testkeyspace",
  1474  					},
  1475  				},
  1476  			},
  1477  			req: &vtadminpb.GetSchemaRequest{
  1478  				ClusterId: "c1",
  1479  				Keyspace:  "testkeyspace",
  1480  				Table:     "testtable",
  1481  			},
  1482  			expected:  nil,
  1483  			shouldErr: true,
  1484  		},
  1485  		{
  1486  			name:      "error in GetSchema call",
  1487  			clusterID: 1,
  1488  			ts:        memorytopo.NewServer("zone1"),
  1489  			tmc: &testutil.TabletManagerClient{
  1490  				GetSchemaResults: map[string]struct {
  1491  					Schema *tabletmanagerdatapb.SchemaDefinition
  1492  					Error  error
  1493  				}{
  1494  					"zone1-0000000100": {
  1495  						Schema: &tabletmanagerdatapb.SchemaDefinition{
  1496  							TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  1497  								{
  1498  									Name: "testtable",
  1499  								},
  1500  								{
  1501  									Name: "table2",
  1502  								},
  1503  								{
  1504  									Name: "table3",
  1505  								},
  1506  							},
  1507  						},
  1508  						Error: assert.AnError,
  1509  					},
  1510  				},
  1511  			},
  1512  			tablets: []*vtadminpb.Tablet{
  1513  				{
  1514  					Cluster: &vtadminpb.Cluster{
  1515  						Id:   "c1",
  1516  						Name: "cluster1",
  1517  					},
  1518  					State: vtadminpb.Tablet_SERVING,
  1519  					Tablet: &topodatapb.Tablet{
  1520  						Alias: &topodatapb.TabletAlias{
  1521  							Cell: "zone1",
  1522  							Uid:  100,
  1523  						},
  1524  						Keyspace: "testkeyspace",
  1525  					},
  1526  				},
  1527  			},
  1528  			req: &vtadminpb.GetSchemaRequest{
  1529  				ClusterId: "c1",
  1530  				Keyspace:  "testkeyspace",
  1531  				Table:     "testtable",
  1532  			},
  1533  			expected:  nil,
  1534  			shouldErr: true,
  1535  		},
  1536  	}
  1537  
  1538  	ctx := context.Background()
  1539  
  1540  	for _, tt := range tests {
  1541  		tt := tt
  1542  
  1543  		t.Run(tt.name, func(t *testing.T) {
  1544  			t.Parallel()
  1545  
  1546  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, tt.ts, tt.tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  1547  				return grpcvtctldserver.NewVtctldServer(ts)
  1548  			})
  1549  
  1550  			testutil.AddTablets(ctx, t, tt.ts, nil, vtadmintestutil.TopodataTabletsFromVTAdminTablets(tt.tablets)...)
  1551  
  1552  			testutil.WithTestServer(t, vtctld, func(t *testing.T, client vtctldclient.VtctldClient) {
  1553  				c := vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{
  1554  					Cluster: &vtadminpb.Cluster{
  1555  						Id:   fmt.Sprintf("c%d", tt.clusterID),
  1556  						Name: fmt.Sprintf("cluster%d", tt.clusterID),
  1557  					},
  1558  					VtctldClient: client,
  1559  					Tablets:      tt.tablets,
  1560  				})
  1561  				api := NewAPI([]*cluster.Cluster{c}, Options{})
  1562  				defer api.Close()
  1563  
  1564  				resp, err := api.GetSchema(ctx, tt.req)
  1565  				if tt.shouldErr {
  1566  					assert.Error(t, err)
  1567  
  1568  					return
  1569  				}
  1570  
  1571  				if resp != nil {
  1572  					// Clone so our mutation below doesn't trip the race detector.
  1573  					resp = proto.Clone(resp).(*vtadminpb.Schema)
  1574  				}
  1575  
  1576  				assert.NoError(t, err)
  1577  				assert.Truef(t, proto.Equal(tt.expected, resp), "expected %v, got %v", tt.expected, resp)
  1578  			})
  1579  		})
  1580  	}
  1581  
  1582  	t.Run("size aggregation", func(t *testing.T) {
  1583  		t.Parallel()
  1584  
  1585  		c1pb := &vtadminpb.Cluster{
  1586  			Id:   "c1",
  1587  			Name: "cluster1",
  1588  		}
  1589  		c1 := vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{
  1590  			Cluster: c1pb,
  1591  			VtctldClient: &fakevtctldclient.VtctldClient{
  1592  				FindAllShardsInKeyspaceResults: map[string]struct {
  1593  					Response *vtctldatapb.FindAllShardsInKeyspaceResponse
  1594  					Error    error
  1595  				}{
  1596  					"testkeyspace": {
  1597  						Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
  1598  							Shards: map[string]*vtctldatapb.Shard{
  1599  								"-80": {
  1600  									Keyspace: "testkeyspace",
  1601  									Name:     "-80",
  1602  									Shard: &topodatapb.Shard{
  1603  										IsPrimaryServing: true,
  1604  										PrimaryAlias: &topodatapb.TabletAlias{
  1605  											Cell: "c1zone1",
  1606  											Uid:  100,
  1607  										},
  1608  									},
  1609  								},
  1610  								"80-": {
  1611  									Keyspace: "testkeyspace",
  1612  									Name:     "80-",
  1613  									Shard: &topodatapb.Shard{
  1614  										IsPrimaryServing: true,
  1615  										PrimaryAlias: &topodatapb.TabletAlias{
  1616  											Cell: "c1zone1",
  1617  											Uid:  200,
  1618  										},
  1619  									},
  1620  								},
  1621  							},
  1622  						},
  1623  					},
  1624  				},
  1625  				GetSchemaResults: map[string]struct {
  1626  					Response *vtctldatapb.GetSchemaResponse
  1627  					Error    error
  1628  				}{
  1629  					"c1zone1-0000000100": {
  1630  						Response: &vtctldatapb.GetSchemaResponse{
  1631  							Schema: &tabletmanagerdatapb.SchemaDefinition{
  1632  								TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  1633  									{
  1634  										Name:       "testtable",
  1635  										RowCount:   10,
  1636  										DataLength: 100,
  1637  									},
  1638  								},
  1639  							},
  1640  						},
  1641  					},
  1642  					"c1zone1-0000000200": {
  1643  						Response: &vtctldatapb.GetSchemaResponse{
  1644  							Schema: &tabletmanagerdatapb.SchemaDefinition{
  1645  								TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  1646  									{
  1647  										Name:       "testtable",
  1648  										RowCount:   20,
  1649  										DataLength: 200,
  1650  									},
  1651  								},
  1652  							},
  1653  						},
  1654  					},
  1655  				},
  1656  			},
  1657  			Tablets: []*vtadminpb.Tablet{
  1658  				{
  1659  					Cluster: c1pb,
  1660  					Tablet: &topodatapb.Tablet{
  1661  						Alias: &topodatapb.TabletAlias{
  1662  							Cell: "c1zone1",
  1663  							Uid:  100,
  1664  						},
  1665  						Keyspace: "testkeyspace",
  1666  						Shard:    "-80",
  1667  					},
  1668  					State: vtadminpb.Tablet_SERVING,
  1669  				},
  1670  				{
  1671  					Cluster: c1pb,
  1672  					Tablet: &topodatapb.Tablet{
  1673  						Alias: &topodatapb.TabletAlias{
  1674  							Cell: "c1zone1",
  1675  							Uid:  200,
  1676  						},
  1677  						Keyspace: "testkeyspace",
  1678  						Shard:    "80-",
  1679  					},
  1680  					State: vtadminpb.Tablet_SERVING,
  1681  				},
  1682  			},
  1683  		},
  1684  		)
  1685  		c2 := vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{
  1686  			Cluster: &vtadminpb.Cluster{
  1687  				Id:   "c2",
  1688  				Name: "cluster2",
  1689  			},
  1690  		},
  1691  		)
  1692  
  1693  		api := NewAPI([]*cluster.Cluster{c1, c2}, Options{})
  1694  		defer api.Close()
  1695  
  1696  		schema, err := api.GetSchema(ctx, &vtadminpb.GetSchemaRequest{
  1697  			ClusterId: c1.ID,
  1698  			Keyspace:  "testkeyspace",
  1699  			Table:     "testtable",
  1700  			TableSizeOptions: &vtadminpb.GetSchemaTableSizeOptions{
  1701  				AggregateSizes: true,
  1702  			},
  1703  		})
  1704  
  1705  		expected := &vtadminpb.Schema{
  1706  			Cluster:  c1pb,
  1707  			Keyspace: "testkeyspace",
  1708  			TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  1709  				{
  1710  					Name: "testtable",
  1711  				},
  1712  			},
  1713  			TableSizes: map[string]*vtadminpb.Schema_TableSize{
  1714  				"testtable": {
  1715  					RowCount:   10 + 20,
  1716  					DataLength: 100 + 200,
  1717  					ByShard: map[string]*vtadminpb.Schema_ShardTableSize{
  1718  						"-80": {
  1719  							RowCount:   10,
  1720  							DataLength: 100,
  1721  						},
  1722  						"80-": {
  1723  							RowCount:   20,
  1724  							DataLength: 200,
  1725  						},
  1726  					},
  1727  				},
  1728  			},
  1729  		}
  1730  
  1731  		if schema != nil {
  1732  			// Clone so our mutation below doesn't trip the race detector.
  1733  			schema = proto.Clone(schema).(*vtadminpb.Schema)
  1734  
  1735  			for _, td := range schema.TableDefinitions {
  1736  				// Zero these out because they're non-deterministic and also not
  1737  				// relevant to the final result.
  1738  				td.RowCount = 0
  1739  				td.DataLength = 0
  1740  			}
  1741  		}
  1742  
  1743  		assert.NoError(t, err)
  1744  		assert.Truef(t, proto.Equal(expected, schema), "expected %v, got %v", expected, schema)
  1745  	})
  1746  }
  1747  
  1748  func TestGetSchemas(t *testing.T) {
  1749  	t.Parallel()
  1750  
  1751  	tests := []struct {
  1752  		name           string
  1753  		clusterTablets [][]*vtadminpb.Tablet
  1754  		// Indexed by tablet alias
  1755  		tabletSchemas map[string]*tabletmanagerdatapb.SchemaDefinition
  1756  		req           *vtadminpb.GetSchemasRequest
  1757  		expected      *vtadminpb.GetSchemasResponse
  1758  	}{
  1759  		{
  1760  			name: "one schema in one cluster",
  1761  			clusterTablets: [][]*vtadminpb.Tablet{
  1762  				// cluster0
  1763  				{
  1764  					{
  1765  						State: vtadminpb.Tablet_SERVING,
  1766  						Tablet: &topodatapb.Tablet{
  1767  							Alias: &topodatapb.TabletAlias{
  1768  								Cell: "c0_cell1",
  1769  								Uid:  100,
  1770  							},
  1771  							Keyspace: "commerce",
  1772  						},
  1773  					},
  1774  				},
  1775  				// cluster1
  1776  				{
  1777  					{
  1778  						State: vtadminpb.Tablet_SERVING,
  1779  						Tablet: &topodatapb.Tablet{
  1780  							Alias: &topodatapb.TabletAlias{
  1781  								Cell: "c1_cell1",
  1782  								Uid:  100,
  1783  							},
  1784  							Keyspace: "commerce",
  1785  						},
  1786  					},
  1787  				},
  1788  			},
  1789  			tabletSchemas: map[string]*tabletmanagerdatapb.SchemaDefinition{
  1790  				"c0_cell1-0000000100": {
  1791  					DatabaseSchema: "CREATE DATABASE vt_testkeyspace",
  1792  					TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  1793  						{
  1794  							Name:       "t1",
  1795  							Schema:     `CREATE TABLE t1 (id int(11) not null,PRIMARY KEY (id));`,
  1796  							Type:       "BASE",
  1797  							Columns:    []string{"id"},
  1798  							DataLength: 100,
  1799  							RowCount:   50,
  1800  							Fields: []*querypb.Field{
  1801  								{
  1802  									Name: "id",
  1803  									Type: querypb.Type_INT32,
  1804  								},
  1805  							},
  1806  						},
  1807  					},
  1808  				},
  1809  			},
  1810  			req: &vtadminpb.GetSchemasRequest{},
  1811  			expected: &vtadminpb.GetSchemasResponse{
  1812  				Schemas: []*vtadminpb.Schema{
  1813  					{
  1814  						Cluster: &vtadminpb.Cluster{
  1815  							Id:   "c0",
  1816  							Name: "cluster0",
  1817  						},
  1818  						Keyspace: "commerce",
  1819  						TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  1820  							{
  1821  								Name:       "t1",
  1822  								Schema:     `CREATE TABLE t1 (id int(11) not null,PRIMARY KEY (id));`,
  1823  								Type:       "BASE",
  1824  								Columns:    []string{"id"},
  1825  								DataLength: 100,
  1826  								RowCount:   50,
  1827  								Fields: []*querypb.Field{
  1828  									{
  1829  										Name: "id",
  1830  										Type: querypb.Type_INT32,
  1831  									},
  1832  								},
  1833  							},
  1834  						},
  1835  						TableSizes: map[string]*vtadminpb.Schema_TableSize{},
  1836  					},
  1837  				},
  1838  			},
  1839  		},
  1840  		{
  1841  			name: "one schema in each cluster",
  1842  			clusterTablets: [][]*vtadminpb.Tablet{
  1843  				// cluster0
  1844  				{
  1845  					{
  1846  						State: vtadminpb.Tablet_SERVING,
  1847  						Tablet: &topodatapb.Tablet{
  1848  							Alias: &topodatapb.TabletAlias{
  1849  								Cell: "c0_cell1",
  1850  								Uid:  100,
  1851  							},
  1852  							Keyspace: "commerce",
  1853  						},
  1854  					},
  1855  				},
  1856  				// cluster1
  1857  				{
  1858  					{
  1859  						State: vtadminpb.Tablet_SERVING,
  1860  						Tablet: &topodatapb.Tablet{
  1861  							Alias: &topodatapb.TabletAlias{
  1862  								Cell: "c1_cell1",
  1863  								Uid:  100,
  1864  							},
  1865  							Keyspace: "commerce",
  1866  						},
  1867  					},
  1868  				},
  1869  			},
  1870  			tabletSchemas: map[string]*tabletmanagerdatapb.SchemaDefinition{
  1871  				"c0_cell1-0000000100": {
  1872  					DatabaseSchema: "CREATE DATABASE vt_testkeyspace",
  1873  					TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  1874  						{
  1875  							Name:       "t1",
  1876  							Schema:     `CREATE TABLE t1 (id int(11) not null,PRIMARY KEY (id));`,
  1877  							Type:       "BASE",
  1878  							Columns:    []string{"id"},
  1879  							DataLength: 100,
  1880  							RowCount:   50,
  1881  							Fields: []*querypb.Field{
  1882  								{
  1883  									Name: "id",
  1884  									Type: querypb.Type_INT32,
  1885  								},
  1886  							},
  1887  						},
  1888  					},
  1889  				},
  1890  				"c1_cell1-0000000100": {
  1891  					DatabaseSchema: "CREATE DATABASE vt_testkeyspace",
  1892  					TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  1893  						{
  1894  							Name:       "t2",
  1895  							Schema:     `CREATE TABLE t2 (id int(11) not null,PRIMARY KEY (id));`,
  1896  							Type:       "BASE",
  1897  							Columns:    []string{"id"},
  1898  							DataLength: 100,
  1899  							RowCount:   50,
  1900  							Fields: []*querypb.Field{
  1901  								{
  1902  									Name: "id",
  1903  									Type: querypb.Type_INT32,
  1904  								},
  1905  							},
  1906  						},
  1907  					},
  1908  				},
  1909  			},
  1910  			req: &vtadminpb.GetSchemasRequest{},
  1911  			expected: &vtadminpb.GetSchemasResponse{
  1912  				Schemas: []*vtadminpb.Schema{
  1913  					{
  1914  						Cluster: &vtadminpb.Cluster{
  1915  							Id:   "c0",
  1916  							Name: "cluster0",
  1917  						},
  1918  						Keyspace: "commerce",
  1919  						TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  1920  							{
  1921  								Name:       "t1",
  1922  								Schema:     `CREATE TABLE t1 (id int(11) not null,PRIMARY KEY (id));`,
  1923  								Type:       "BASE",
  1924  								Columns:    []string{"id"},
  1925  								DataLength: 100,
  1926  								RowCount:   50,
  1927  								Fields: []*querypb.Field{
  1928  									{
  1929  										Name: "id",
  1930  										Type: querypb.Type_INT32,
  1931  									},
  1932  								},
  1933  							},
  1934  						},
  1935  						TableSizes: map[string]*vtadminpb.Schema_TableSize{},
  1936  					},
  1937  					{
  1938  						Cluster: &vtadminpb.Cluster{
  1939  							Id:   "c1",
  1940  							Name: "cluster1",
  1941  						},
  1942  						Keyspace: "commerce",
  1943  						TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  1944  							{
  1945  								Name:       "t2",
  1946  								Schema:     `CREATE TABLE t2 (id int(11) not null,PRIMARY KEY (id));`,
  1947  								Type:       "BASE",
  1948  								Columns:    []string{"id"},
  1949  								DataLength: 100,
  1950  								RowCount:   50,
  1951  								Fields: []*querypb.Field{
  1952  									{
  1953  										Name: "id",
  1954  										Type: querypb.Type_INT32,
  1955  									},
  1956  								},
  1957  							},
  1958  						},
  1959  						TableSizes: map[string]*vtadminpb.Schema_TableSize{},
  1960  					},
  1961  				},
  1962  			},
  1963  		},
  1964  		{
  1965  			name: "filtered by cluster ID",
  1966  			clusterTablets: [][]*vtadminpb.Tablet{
  1967  				// cluster0
  1968  				{
  1969  					{
  1970  						State: vtadminpb.Tablet_SERVING,
  1971  						Tablet: &topodatapb.Tablet{
  1972  							Alias: &topodatapb.TabletAlias{
  1973  								Cell: "c0_cell1",
  1974  								Uid:  100,
  1975  							},
  1976  							Keyspace: "commerce",
  1977  						},
  1978  					},
  1979  				},
  1980  				// cluster1
  1981  				{
  1982  					{
  1983  						State: vtadminpb.Tablet_SERVING,
  1984  						Tablet: &topodatapb.Tablet{
  1985  							Alias: &topodatapb.TabletAlias{
  1986  								Cell: "c1_cell1",
  1987  								Uid:  100,
  1988  							},
  1989  							Keyspace: "commerce",
  1990  						},
  1991  					},
  1992  				},
  1993  			},
  1994  			tabletSchemas: map[string]*tabletmanagerdatapb.SchemaDefinition{
  1995  				"c0_cell1-0000000100": {
  1996  					DatabaseSchema: "CREATE DATABASE vt_testkeyspace",
  1997  					TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  1998  						{
  1999  							Name:       "t1",
  2000  							Schema:     `CREATE TABLE t1 (id int(11) not null,PRIMARY KEY (id));`,
  2001  							Type:       "BASE",
  2002  							Columns:    []string{"id"},
  2003  							DataLength: 100,
  2004  							RowCount:   50,
  2005  							Fields: []*querypb.Field{
  2006  								{
  2007  									Name: "id",
  2008  									Type: querypb.Type_INT32,
  2009  								},
  2010  							},
  2011  						},
  2012  					},
  2013  				},
  2014  				"c1_cell1-0000000100": {
  2015  					DatabaseSchema: "CREATE DATABASE vt_testkeyspace",
  2016  					TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2017  						{
  2018  							Name:       "t2",
  2019  							Schema:     `CREATE TABLE t2 (id int(11) not null,PRIMARY KEY (id));`,
  2020  							Type:       "BASE",
  2021  							Columns:    []string{"id"},
  2022  							DataLength: 100,
  2023  							RowCount:   50,
  2024  							Fields: []*querypb.Field{
  2025  								{
  2026  									Name: "id",
  2027  									Type: querypb.Type_INT32,
  2028  								},
  2029  							},
  2030  						},
  2031  					},
  2032  				},
  2033  			},
  2034  			req: &vtadminpb.GetSchemasRequest{
  2035  				ClusterIds: []string{"c1"},
  2036  			},
  2037  			expected: &vtadminpb.GetSchemasResponse{
  2038  				Schemas: []*vtadminpb.Schema{
  2039  					{
  2040  						Cluster: &vtadminpb.Cluster{
  2041  							Id:   "c1",
  2042  							Name: "cluster1",
  2043  						},
  2044  						Keyspace: "commerce",
  2045  						TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2046  							{
  2047  								Name:       "t2",
  2048  								Schema:     `CREATE TABLE t2 (id int(11) not null,PRIMARY KEY (id));`,
  2049  								Type:       "BASE",
  2050  								Columns:    []string{"id"},
  2051  								DataLength: 100,
  2052  								RowCount:   50,
  2053  								Fields: []*querypb.Field{
  2054  									{
  2055  										Name: "id",
  2056  										Type: querypb.Type_INT32,
  2057  									},
  2058  								},
  2059  							},
  2060  						},
  2061  						TableSizes: map[string]*vtadminpb.Schema_TableSize{},
  2062  					},
  2063  				},
  2064  			},
  2065  		},
  2066  		{
  2067  			name: "filtered by cluster ID that doesn't exist",
  2068  			clusterTablets: [][]*vtadminpb.Tablet{
  2069  				// cluster0
  2070  				{
  2071  					{
  2072  						State: vtadminpb.Tablet_SERVING,
  2073  						Tablet: &topodatapb.Tablet{
  2074  							Alias: &topodatapb.TabletAlias{
  2075  								Cell: "c0_cell1",
  2076  								Uid:  100,
  2077  							},
  2078  							Keyspace: "commerce",
  2079  						},
  2080  					},
  2081  				},
  2082  			},
  2083  			tabletSchemas: map[string]*tabletmanagerdatapb.SchemaDefinition{
  2084  				"c0_cell1-0000000100": {
  2085  					DatabaseSchema: "CREATE DATABASE vt_testkeyspace",
  2086  					TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2087  						{
  2088  							Name:       "t1",
  2089  							Schema:     `CREATE TABLE t1 (id int(11) not null,PRIMARY KEY (id));`,
  2090  							Type:       "BASE",
  2091  							Columns:    []string{"id"},
  2092  							DataLength: 100,
  2093  							RowCount:   50,
  2094  							Fields: []*querypb.Field{
  2095  								{
  2096  									Name: "id",
  2097  									Type: querypb.Type_INT32,
  2098  								},
  2099  							},
  2100  						},
  2101  					},
  2102  				},
  2103  			},
  2104  			req: &vtadminpb.GetSchemasRequest{
  2105  				ClusterIds: []string{"nope"},
  2106  			},
  2107  			expected: &vtadminpb.GetSchemasResponse{
  2108  				Schemas: nil,
  2109  			},
  2110  		},
  2111  		{
  2112  			name: "no schemas for any cluster",
  2113  			clusterTablets: [][]*vtadminpb.Tablet{
  2114  				// cluster0
  2115  				{
  2116  					{
  2117  						State: vtadminpb.Tablet_SERVING,
  2118  						Tablet: &topodatapb.Tablet{
  2119  							Alias: &topodatapb.TabletAlias{
  2120  								Cell: "c0_cell1",
  2121  								Uid:  100,
  2122  							},
  2123  							Keyspace: "commerce",
  2124  						},
  2125  					},
  2126  				},
  2127  			},
  2128  			tabletSchemas: map[string]*tabletmanagerdatapb.SchemaDefinition{},
  2129  			req:           &vtadminpb.GetSchemasRequest{},
  2130  			expected: &vtadminpb.GetSchemasResponse{
  2131  				Schemas: nil,
  2132  			},
  2133  		},
  2134  		{
  2135  			name: "no serving tablets",
  2136  			clusterTablets: [][]*vtadminpb.Tablet{
  2137  				// cluster0
  2138  				{
  2139  					{
  2140  						State: vtadminpb.Tablet_NOT_SERVING,
  2141  						Tablet: &topodatapb.Tablet{
  2142  							Alias: &topodatapb.TabletAlias{
  2143  								Cell: "c0_cell1",
  2144  								Uid:  100,
  2145  							},
  2146  							Keyspace: "commerce",
  2147  						},
  2148  					},
  2149  				},
  2150  			},
  2151  			tabletSchemas: map[string]*tabletmanagerdatapb.SchemaDefinition{
  2152  				"c0_cell1-0000000100": {
  2153  					DatabaseSchema: "CREATE DATABASE vt_testkeyspace",
  2154  					TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2155  						{
  2156  							Name:       "t1",
  2157  							Schema:     `CREATE TABLE t1 (id int(11) not null,PRIMARY KEY (id));`,
  2158  							Type:       "BASE",
  2159  							Columns:    []string{"id"},
  2160  							DataLength: 100,
  2161  							RowCount:   50,
  2162  							Fields: []*querypb.Field{
  2163  								{
  2164  									Name: "id",
  2165  									Type: querypb.Type_INT32,
  2166  								},
  2167  							},
  2168  						},
  2169  					},
  2170  				},
  2171  			},
  2172  			req: &vtadminpb.GetSchemasRequest{},
  2173  			expected: &vtadminpb.GetSchemasResponse{
  2174  				Schemas: nil,
  2175  			},
  2176  		},
  2177  	}
  2178  
  2179  	ctx := context.Background()
  2180  
  2181  	for _, tt := range tests {
  2182  		// Note that these test cases were written prior to the existence of
  2183  		// WithTestServers, so they are all written with the assumption that
  2184  		// there are exactly 2 clusters.
  2185  		tt := tt
  2186  
  2187  		t.Run(tt.name, func(t *testing.T) {
  2188  			t.Parallel()
  2189  
  2190  			topos := []*topo.Server{
  2191  				memorytopo.NewServer("c0_cell1"),
  2192  				memorytopo.NewServer("c1_cell1"),
  2193  			}
  2194  
  2195  			tmc := testutil.TabletManagerClient{
  2196  				GetSchemaResults: map[string]struct {
  2197  					Schema *tabletmanagerdatapb.SchemaDefinition
  2198  					Error  error
  2199  				}{},
  2200  			}
  2201  
  2202  			vtctlds := []vtctlservicepb.VtctldServer{
  2203  				testutil.NewVtctldServerWithTabletManagerClient(t, topos[0], &tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  2204  					return grpcvtctldserver.NewVtctldServer(ts)
  2205  				}),
  2206  				testutil.NewVtctldServerWithTabletManagerClient(t, topos[1], &tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  2207  					return grpcvtctldserver.NewVtctldServer(ts)
  2208  				}),
  2209  			}
  2210  
  2211  			testutil.WithTestServers(t, func(t *testing.T, clients ...vtctldclient.VtctldClient) {
  2212  				clusters := make([]*cluster.Cluster, len(topos))
  2213  				for cdx, toposerver := range topos {
  2214  					// Handle when a test doesn't define any tablets for a given cluster.
  2215  					var cts []*vtadminpb.Tablet
  2216  					if cdx < len(tt.clusterTablets) {
  2217  						cts = tt.clusterTablets[cdx]
  2218  					}
  2219  
  2220  					for _, tablet := range cts {
  2221  						// AddTablet also adds the keyspace + shard for us.
  2222  						testutil.AddTablet(ctx, t, toposerver, tablet.Tablet, nil)
  2223  
  2224  						// Adds each SchemaDefinition to the fake TabletManagerClient, or nil
  2225  						// if there are no schemas for that tablet. (All tablet aliases must
  2226  						// exist in the map. Otherwise, TabletManagerClient will return an error when
  2227  						// looking up the schema with tablet alias that doesn't exist.)
  2228  						alias := topoproto.TabletAliasString(tablet.Tablet.Alias)
  2229  						tmc.GetSchemaResults[alias] = struct {
  2230  							Schema *tabletmanagerdatapb.SchemaDefinition
  2231  							Error  error
  2232  						}{
  2233  							Schema: tt.tabletSchemas[alias],
  2234  							Error:  nil,
  2235  						}
  2236  					}
  2237  
  2238  					clusters[cdx] = vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{
  2239  						Cluster: &vtadminpb.Cluster{
  2240  							Id:   fmt.Sprintf("c%d", cdx),
  2241  							Name: fmt.Sprintf("cluster%d", cdx),
  2242  						},
  2243  						VtctldClient: clients[cdx],
  2244  						Tablets:      cts,
  2245  					})
  2246  				}
  2247  
  2248  				api := NewAPI(clusters, Options{})
  2249  				defer api.Close()
  2250  
  2251  				resp, err := api.GetSchemas(ctx, tt.req)
  2252  				require.NoError(t, err)
  2253  
  2254  				vtadmintestutil.AssertSchemaSlicesEqual(t, tt.expected.Schemas, resp.Schemas)
  2255  			}, vtctlds...)
  2256  		})
  2257  	}
  2258  
  2259  	t.Run("size aggregation", func(t *testing.T) {
  2260  		t.Parallel()
  2261  
  2262  		c1pb := &vtadminpb.Cluster{
  2263  			Id:   "c1",
  2264  			Name: "cluster1",
  2265  		}
  2266  		c2pb := &vtadminpb.Cluster{
  2267  			Id:   "c2",
  2268  			Name: "cluster2",
  2269  		}
  2270  
  2271  		c1 := vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{
  2272  			Cluster: c1pb,
  2273  			VtctldClient: &fakevtctldclient.VtctldClient{
  2274  				FindAllShardsInKeyspaceResults: map[string]struct {
  2275  					Response *vtctldatapb.FindAllShardsInKeyspaceResponse
  2276  					Error    error
  2277  				}{
  2278  					"testkeyspace": {
  2279  						Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
  2280  							Shards: map[string]*vtctldatapb.Shard{
  2281  								"-80": {
  2282  									Keyspace: "testkeyspace",
  2283  									Name:     "-80",
  2284  									Shard: &topodatapb.Shard{
  2285  										IsPrimaryServing: true,
  2286  										PrimaryAlias: &topodatapb.TabletAlias{
  2287  											Cell: "c1zone1",
  2288  											Uid:  100,
  2289  										},
  2290  									},
  2291  								},
  2292  								"80-": {
  2293  									Keyspace: "testkeyspace",
  2294  									Name:     "80-",
  2295  									Shard: &topodatapb.Shard{
  2296  										IsPrimaryServing: true,
  2297  										PrimaryAlias: &topodatapb.TabletAlias{
  2298  											Cell: "c1zone1",
  2299  											Uid:  200,
  2300  										},
  2301  									},
  2302  								},
  2303  							},
  2304  						},
  2305  					},
  2306  					"ks1": {
  2307  						Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
  2308  							Shards: map[string]*vtctldatapb.Shard{
  2309  								"-": {
  2310  									Keyspace: "ks1",
  2311  									Name:     "-",
  2312  									Shard: &topodatapb.Shard{
  2313  										IsPrimaryServing: true,
  2314  										PrimaryAlias: &topodatapb.TabletAlias{
  2315  											Cell: "c1zone2",
  2316  											Uid:  100,
  2317  										},
  2318  									},
  2319  								},
  2320  							},
  2321  						},
  2322  					},
  2323  				},
  2324  				GetKeyspacesResults: &struct {
  2325  					Keyspaces []*vtctldatapb.Keyspace
  2326  					Error     error
  2327  				}{
  2328  					Keyspaces: []*vtctldatapb.Keyspace{
  2329  						{Name: "testkeyspace"},
  2330  						{Name: "ks1"},
  2331  					},
  2332  				},
  2333  				GetSchemaResults: map[string]struct {
  2334  					Response *vtctldatapb.GetSchemaResponse
  2335  					Error    error
  2336  				}{
  2337  					"c1zone1-0000000100": {
  2338  						Response: &vtctldatapb.GetSchemaResponse{
  2339  							Schema: &tabletmanagerdatapb.SchemaDefinition{
  2340  								TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2341  									{
  2342  										Name:       "testtable",
  2343  										RowCount:   10,
  2344  										DataLength: 100,
  2345  									},
  2346  								},
  2347  							},
  2348  						},
  2349  					},
  2350  					"c1zone1-0000000200": {
  2351  						Response: &vtctldatapb.GetSchemaResponse{
  2352  							Schema: &tabletmanagerdatapb.SchemaDefinition{
  2353  								TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2354  									{
  2355  										Name:       "testtable",
  2356  										RowCount:   20,
  2357  										DataLength: 200,
  2358  									},
  2359  								},
  2360  							},
  2361  						},
  2362  					},
  2363  				},
  2364  			},
  2365  			Tablets: []*vtadminpb.Tablet{
  2366  				{
  2367  					Cluster: c1pb,
  2368  					Tablet: &topodatapb.Tablet{
  2369  						Alias: &topodatapb.TabletAlias{
  2370  							Cell: "c1zone1",
  2371  							Uid:  100,
  2372  						},
  2373  						Keyspace: "testkeyspace",
  2374  						Shard:    "-80",
  2375  					},
  2376  					State: vtadminpb.Tablet_SERVING,
  2377  				},
  2378  				{
  2379  					Cluster: c1pb,
  2380  					Tablet: &topodatapb.Tablet{
  2381  						Alias: &topodatapb.TabletAlias{
  2382  							Cell: "c1zone1",
  2383  							Uid:  200,
  2384  						},
  2385  						Keyspace: "testkeyspace",
  2386  						Shard:    "80-",
  2387  					},
  2388  					State: vtadminpb.Tablet_SERVING,
  2389  				},
  2390  			},
  2391  		},
  2392  		)
  2393  		c2 := vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{
  2394  			Cluster: c2pb,
  2395  			VtctldClient: &fakevtctldclient.VtctldClient{
  2396  				FindAllShardsInKeyspaceResults: map[string]struct {
  2397  					Response *vtctldatapb.FindAllShardsInKeyspaceResponse
  2398  					Error    error
  2399  				}{
  2400  					"ks2": {
  2401  						Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
  2402  							Shards: map[string]*vtctldatapb.Shard{
  2403  								"-": {
  2404  									Keyspace: "ks2",
  2405  									Name:     "-",
  2406  									Shard: &topodatapb.Shard{
  2407  										IsPrimaryServing: true,
  2408  										PrimaryAlias: &topodatapb.TabletAlias{
  2409  											Cell: "c2z1",
  2410  											Uid:  100,
  2411  										},
  2412  									},
  2413  								},
  2414  							},
  2415  						},
  2416  					},
  2417  				},
  2418  				GetKeyspacesResults: &struct {
  2419  					Keyspaces []*vtctldatapb.Keyspace
  2420  					Error     error
  2421  				}{
  2422  					Keyspaces: []*vtctldatapb.Keyspace{
  2423  						{
  2424  							Name: "ks2",
  2425  						},
  2426  					},
  2427  				},
  2428  				GetSchemaResults: map[string]struct {
  2429  					Response *vtctldatapb.GetSchemaResponse
  2430  					Error    error
  2431  				}{
  2432  					"c2z1-0000000100": {
  2433  						Response: &vtctldatapb.GetSchemaResponse{
  2434  							Schema: &tabletmanagerdatapb.SchemaDefinition{
  2435  								TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2436  									{
  2437  										Name:       "t2",
  2438  										DataLength: 5,
  2439  										RowCount:   7,
  2440  									},
  2441  									{
  2442  										Name:       "_t2_ghc",
  2443  										DataLength: 5,
  2444  										RowCount:   7,
  2445  									},
  2446  								},
  2447  							},
  2448  						},
  2449  					},
  2450  				},
  2451  			},
  2452  			Tablets: []*vtadminpb.Tablet{
  2453  				{
  2454  					Cluster: c2pb,
  2455  					Tablet: &topodatapb.Tablet{
  2456  						Alias: &topodatapb.TabletAlias{
  2457  							Cell: "c2z1",
  2458  							Uid:  100,
  2459  						},
  2460  						Keyspace: "ks2",
  2461  						Shard:    "-",
  2462  					},
  2463  					State: vtadminpb.Tablet_SERVING,
  2464  				},
  2465  			},
  2466  		},
  2467  		)
  2468  
  2469  		api := NewAPI([]*cluster.Cluster{c1, c2}, Options{})
  2470  		defer api.Close()
  2471  
  2472  		resp, err := api.GetSchemas(ctx, &vtadminpb.GetSchemasRequest{
  2473  			TableSizeOptions: &vtadminpb.GetSchemaTableSizeOptions{
  2474  				AggregateSizes: true,
  2475  			},
  2476  		})
  2477  
  2478  		expected := &vtadminpb.GetSchemasResponse{
  2479  			Schemas: []*vtadminpb.Schema{
  2480  				{
  2481  					Cluster:  c1pb,
  2482  					Keyspace: "testkeyspace",
  2483  					TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2484  						{
  2485  							Name: "testtable",
  2486  						},
  2487  					},
  2488  					TableSizes: map[string]*vtadminpb.Schema_TableSize{
  2489  						"testtable": {
  2490  							RowCount:   10 + 20,
  2491  							DataLength: 100 + 200,
  2492  							ByShard: map[string]*vtadminpb.Schema_ShardTableSize{
  2493  								"-80": {
  2494  									RowCount:   10,
  2495  									DataLength: 100,
  2496  								},
  2497  								"80-": {
  2498  									RowCount:   20,
  2499  									DataLength: 200,
  2500  								},
  2501  							},
  2502  						},
  2503  					},
  2504  				},
  2505  				{
  2506  					Cluster:  c2pb,
  2507  					Keyspace: "ks2",
  2508  					TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2509  						{Name: "t2"},
  2510  						{Name: "_t2_ghc"},
  2511  					},
  2512  					TableSizes: map[string]*vtadminpb.Schema_TableSize{
  2513  						"t2": {
  2514  							DataLength: 5,
  2515  							RowCount:   7,
  2516  							ByShard: map[string]*vtadminpb.Schema_ShardTableSize{
  2517  								"-": {
  2518  									DataLength: 5,
  2519  									RowCount:   7,
  2520  								},
  2521  							},
  2522  						},
  2523  						"_t2_ghc": {
  2524  							DataLength: 5,
  2525  							RowCount:   7,
  2526  							ByShard: map[string]*vtadminpb.Schema_ShardTableSize{
  2527  								"-": {
  2528  									DataLength: 5,
  2529  									RowCount:   7,
  2530  								},
  2531  							},
  2532  						},
  2533  					},
  2534  				},
  2535  			},
  2536  		}
  2537  
  2538  		if resp != nil {
  2539  			// Clone schemas so our mutations below don't trip the race detector.
  2540  			schemas := make([]*vtadminpb.Schema, len(resp.Schemas))
  2541  			for i, schema := range resp.Schemas {
  2542  				schema := proto.Clone(schema).(*vtadminpb.Schema)
  2543  
  2544  				for _, td := range schema.TableDefinitions {
  2545  					// Zero these out because they're non-deterministic and also not
  2546  					// relevant to the final result.
  2547  					td.RowCount = 0
  2548  					td.DataLength = 0
  2549  				}
  2550  
  2551  				schemas[i] = schema
  2552  			}
  2553  
  2554  			resp.Schemas = schemas
  2555  		}
  2556  
  2557  		assert.NoError(t, err)
  2558  		assert.Truef(t, proto.Equal(expected, resp), "expected: %v, got: %v", expected, resp)
  2559  	})
  2560  }
  2561  
  2562  func TestGetSrvVSchema(t *testing.T) {
  2563  	t.Parallel()
  2564  
  2565  	clusterID := "c0"
  2566  	clusterName := "cluster0"
  2567  
  2568  	tests := []struct {
  2569  		name  string
  2570  		cells []string
  2571  
  2572  		// tt.cellSrvVSchemas maps cell name to the SrvVSchema for that cell.
  2573  		// Not all cells in tt.cells necessarily map to a SrvVSchema.
  2574  		cellSrvVSchemas map[string]*vschemapb.SrvVSchema
  2575  		srvVSchema      *vschemapb.SrvVSchema
  2576  		req             *vtadminpb.GetSrvVSchemaRequest
  2577  		expected        *vtadminpb.SrvVSchema
  2578  		shouldErr       bool
  2579  	}{
  2580  		{
  2581  			name:  "success",
  2582  			cells: []string{"zone0"},
  2583  			cellSrvVSchemas: map[string]*vschemapb.SrvVSchema{
  2584  				"zone0": {
  2585  					Keyspaces: map[string]*vschemapb.Keyspace{
  2586  						"commerce": {
  2587  							Tables: map[string]*vschemapb.Table{
  2588  								"customer": {},
  2589  							},
  2590  						},
  2591  						"customer": {
  2592  							Tables: map[string]*vschemapb.Table{
  2593  								"customer": {},
  2594  							},
  2595  						},
  2596  					},
  2597  					RoutingRules: &vschemapb.RoutingRules{
  2598  						Rules: []*vschemapb.RoutingRule{
  2599  							{
  2600  								FromTable: "customer",
  2601  								ToTables:  []string{"commerce.customer"},
  2602  							},
  2603  							{
  2604  								FromTable: "customer@rdonly",
  2605  								ToTables:  []string{"customer.customer"},
  2606  							},
  2607  							{
  2608  								FromTable: "customer.customer",
  2609  								ToTables:  []string{"commerce.customer"},
  2610  							},
  2611  						},
  2612  					},
  2613  				},
  2614  			},
  2615  			req: &vtadminpb.GetSrvVSchemaRequest{
  2616  				Cell:      "zone0",
  2617  				ClusterId: clusterID,
  2618  			},
  2619  			expected: &vtadminpb.SrvVSchema{
  2620  				Cell: "zone0",
  2621  				Cluster: &vtadminpb.Cluster{
  2622  					Id:   clusterID,
  2623  					Name: clusterName,
  2624  				},
  2625  				SrvVSchema: &vschemapb.SrvVSchema{
  2626  					Keyspaces: map[string]*vschemapb.Keyspace{
  2627  						"commerce": {
  2628  							Tables: map[string]*vschemapb.Table{
  2629  								"customer": {},
  2630  							},
  2631  						},
  2632  						"customer": {
  2633  							Tables: map[string]*vschemapb.Table{
  2634  								"customer": {},
  2635  							},
  2636  						},
  2637  					},
  2638  					RoutingRules: &vschemapb.RoutingRules{
  2639  						Rules: []*vschemapb.RoutingRule{
  2640  							{
  2641  								FromTable: "customer",
  2642  								ToTables:  []string{"commerce.customer"},
  2643  							},
  2644  							{
  2645  								FromTable: "customer@rdonly",
  2646  								ToTables:  []string{"customer.customer"},
  2647  							},
  2648  							{
  2649  								FromTable: "customer.customer",
  2650  								ToTables:  []string{"commerce.customer"},
  2651  							},
  2652  						},
  2653  					},
  2654  				},
  2655  			},
  2656  		},
  2657  		{
  2658  			name:       "cluster doesn't exist",
  2659  			srvVSchema: nil,
  2660  			req: &vtadminpb.GetSrvVSchemaRequest{
  2661  				Cell:      "doesnt-matter",
  2662  				ClusterId: "doesnt-exist",
  2663  			},
  2664  			shouldErr: true,
  2665  		},
  2666  		{
  2667  
  2668  			name:       "cell doesn't exist",
  2669  			srvVSchema: nil,
  2670  			req: &vtadminpb.GetSrvVSchemaRequest{
  2671  				Cell:      "doesnt-exist",
  2672  				ClusterId: clusterID,
  2673  			},
  2674  			shouldErr: true,
  2675  		},
  2676  	}
  2677  
  2678  	ctx := context.Background()
  2679  
  2680  	for _, tt := range tests {
  2681  		tt := tt
  2682  
  2683  		t.Run(tt.name, func(t *testing.T) {
  2684  			t.Parallel()
  2685  
  2686  			tmc := testutil.TabletManagerClient{}
  2687  
  2688  			toposerver := memorytopo.NewServer(tt.cells...)
  2689  
  2690  			vtctldserver := testutil.NewVtctldServerWithTabletManagerClient(t, toposerver, &tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  2691  				return grpcvtctldserver.NewVtctldServer(ts)
  2692  			})
  2693  
  2694  			testutil.WithTestServer(t, vtctldserver, func(t *testing.T, vtctldClient vtctldclient.VtctldClient) {
  2695  				for cell, svs := range tt.cellSrvVSchemas {
  2696  					err := toposerver.UpdateSrvVSchema(ctx, cell, svs)
  2697  					require.NoError(t, err)
  2698  				}
  2699  
  2700  				clusters := []*cluster.Cluster{
  2701  					vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{
  2702  						Cluster: &vtadminpb.Cluster{
  2703  							Id:   clusterID,
  2704  							Name: clusterName,
  2705  						},
  2706  						VtctldClient: vtctldClient,
  2707  					}),
  2708  				}
  2709  
  2710  				api := NewAPI(clusters, Options{})
  2711  				resp, err := api.GetSrvVSchema(ctx, tt.req)
  2712  
  2713  				if tt.shouldErr {
  2714  					assert.Error(t, err)
  2715  					return
  2716  				}
  2717  
  2718  				require.NoError(t, err)
  2719  				assert.Truef(t, proto.Equal(tt.expected, resp), "expected %v, got %v", tt.expected, resp)
  2720  			})
  2721  		})
  2722  	}
  2723  }
  2724  
  2725  func TestGetSrvVSchemas(t *testing.T) {
  2726  	t.Parallel()
  2727  
  2728  	clusterID := "c0"
  2729  	clusterName := "cluster0"
  2730  
  2731  	tests := []struct {
  2732  		name  string
  2733  		cells []string
  2734  		// tt.cellSrvVSchemas maps cell name to the SrvVSchema for that cell.
  2735  		// Not all cells in tt.cells necessarily map to a SrvVSchema.
  2736  		cellSrvVSchemas map[string]*vschemapb.SrvVSchema
  2737  		req             *vtadminpb.GetSrvVSchemasRequest
  2738  		expected        *vtadminpb.GetSrvVSchemasResponse
  2739  		shouldErr       bool
  2740  	}{
  2741  		{
  2742  			name:  "returns all cells",
  2743  			cells: []string{"zone0", "zone1"},
  2744  			cellSrvVSchemas: map[string]*vschemapb.SrvVSchema{
  2745  				"zone0": {
  2746  					Keyspaces: map[string]*vschemapb.Keyspace{
  2747  						"commerce": {
  2748  							Tables: map[string]*vschemapb.Table{
  2749  								"customer": {},
  2750  							},
  2751  						},
  2752  						"customer": {
  2753  							Tables: map[string]*vschemapb.Table{
  2754  								"customer": {},
  2755  							},
  2756  						},
  2757  					},
  2758  					RoutingRules: &vschemapb.RoutingRules{
  2759  						Rules: []*vschemapb.RoutingRule{
  2760  							{
  2761  								FromTable: "customer",
  2762  								ToTables:  []string{"commerce.customer"},
  2763  							},
  2764  							{
  2765  								FromTable: "customer@rdonly",
  2766  								ToTables:  []string{"customer.customer"},
  2767  							},
  2768  							{
  2769  								FromTable: "customer.customer",
  2770  								ToTables:  []string{"commerce.customer"},
  2771  							},
  2772  						},
  2773  					},
  2774  				},
  2775  				"zone1": {
  2776  					Keyspaces:    map[string]*vschemapb.Keyspace{},
  2777  					RoutingRules: &vschemapb.RoutingRules{},
  2778  				},
  2779  			},
  2780  			req: &vtadminpb.GetSrvVSchemasRequest{},
  2781  			expected: &vtadminpb.GetSrvVSchemasResponse{
  2782  				SrvVSchemas: []*vtadminpb.SrvVSchema{
  2783  					{
  2784  						Cell: "zone0",
  2785  						Cluster: &vtadminpb.Cluster{
  2786  							Id:   clusterID,
  2787  							Name: clusterName,
  2788  						},
  2789  						SrvVSchema: &vschemapb.SrvVSchema{
  2790  							Keyspaces: map[string]*vschemapb.Keyspace{
  2791  								"commerce": {
  2792  									Tables: map[string]*vschemapb.Table{
  2793  										"customer": {},
  2794  									},
  2795  								},
  2796  								"customer": {
  2797  									Tables: map[string]*vschemapb.Table{
  2798  										"customer": {},
  2799  									},
  2800  								},
  2801  							},
  2802  							RoutingRules: &vschemapb.RoutingRules{
  2803  								Rules: []*vschemapb.RoutingRule{
  2804  									{
  2805  										FromTable: "customer",
  2806  										ToTables:  []string{"commerce.customer"},
  2807  									},
  2808  									{
  2809  										FromTable: "customer@rdonly",
  2810  										ToTables:  []string{"customer.customer"},
  2811  									},
  2812  									{
  2813  										FromTable: "customer.customer",
  2814  										ToTables:  []string{"commerce.customer"},
  2815  									},
  2816  								},
  2817  							},
  2818  						},
  2819  					},
  2820  					{
  2821  						Cell: "zone1",
  2822  						Cluster: &vtadminpb.Cluster{
  2823  							Id:   clusterID,
  2824  							Name: clusterName,
  2825  						},
  2826  						SrvVSchema: &vschemapb.SrvVSchema{
  2827  							RoutingRules: &vschemapb.RoutingRules{},
  2828  						},
  2829  					},
  2830  				},
  2831  			},
  2832  		},
  2833  		{
  2834  			name:  "filtering by cell",
  2835  			cells: []string{"zone0", "zone1"},
  2836  			cellSrvVSchemas: map[string]*vschemapb.SrvVSchema{
  2837  				"zone0": {
  2838  					Keyspaces: map[string]*vschemapb.Keyspace{
  2839  						"commerce": {
  2840  							Tables: map[string]*vschemapb.Table{
  2841  								"customer": {},
  2842  							},
  2843  						},
  2844  						"customer": {
  2845  							Tables: map[string]*vschemapb.Table{
  2846  								"customer": {},
  2847  							},
  2848  						},
  2849  					},
  2850  					RoutingRules: &vschemapb.RoutingRules{
  2851  						Rules: []*vschemapb.RoutingRule{
  2852  							{
  2853  								FromTable: "customer",
  2854  								ToTables:  []string{"commerce.customer"},
  2855  							},
  2856  							{
  2857  								FromTable: "customer@rdonly",
  2858  								ToTables:  []string{"customer.customer"},
  2859  							},
  2860  							{
  2861  								FromTable: "customer.customer",
  2862  								ToTables:  []string{"commerce.customer"},
  2863  							},
  2864  						},
  2865  					},
  2866  				},
  2867  				"zone1": {
  2868  					Keyspaces:    map[string]*vschemapb.Keyspace{},
  2869  					RoutingRules: &vschemapb.RoutingRules{},
  2870  				},
  2871  			},
  2872  			req: &vtadminpb.GetSrvVSchemasRequest{
  2873  				Cells: []string{"zone1"},
  2874  			},
  2875  			expected: &vtadminpb.GetSrvVSchemasResponse{
  2876  				SrvVSchemas: []*vtadminpb.SrvVSchema{
  2877  					{
  2878  						Cell: "zone1",
  2879  						Cluster: &vtadminpb.Cluster{
  2880  							Id:   clusterID,
  2881  							Name: clusterName,
  2882  						},
  2883  						SrvVSchema: &vschemapb.SrvVSchema{
  2884  							RoutingRules: &vschemapb.RoutingRules{},
  2885  						},
  2886  					},
  2887  				},
  2888  			},
  2889  		},
  2890  		{
  2891  			name:  "filtering by nonexistent cell",
  2892  			cells: []string{"zone0"},
  2893  			cellSrvVSchemas: map[string]*vschemapb.SrvVSchema{
  2894  				"zone0": {
  2895  					Keyspaces:    map[string]*vschemapb.Keyspace{},
  2896  					RoutingRules: &vschemapb.RoutingRules{},
  2897  				},
  2898  			},
  2899  			req: &vtadminpb.GetSrvVSchemasRequest{
  2900  				Cells: []string{"doesnt-exist"},
  2901  			},
  2902  			expected: &vtadminpb.GetSrvVSchemasResponse{},
  2903  		},
  2904  		{
  2905  			name:  "filtering with nonexistent cell",
  2906  			cells: []string{"zone0"},
  2907  			cellSrvVSchemas: map[string]*vschemapb.SrvVSchema{
  2908  				"zone0": {
  2909  					Keyspaces:    map[string]*vschemapb.Keyspace{},
  2910  					RoutingRules: &vschemapb.RoutingRules{},
  2911  				},
  2912  			},
  2913  			req: &vtadminpb.GetSrvVSchemasRequest{
  2914  				Cells: []string{"doesnt-exist", "zone0"},
  2915  			},
  2916  			expected: &vtadminpb.GetSrvVSchemasResponse{
  2917  				SrvVSchemas: []*vtadminpb.SrvVSchema{
  2918  					{
  2919  						Cell: "zone0",
  2920  						Cluster: &vtadminpb.Cluster{
  2921  							Id:   clusterID,
  2922  							Name: clusterName,
  2923  						},
  2924  						SrvVSchema: &vschemapb.SrvVSchema{
  2925  							RoutingRules: &vschemapb.RoutingRules{},
  2926  						},
  2927  					},
  2928  				},
  2929  			},
  2930  		},
  2931  		{
  2932  			name:  "existing cell without SrvVSchema",
  2933  			cells: []string{"zone0", "zone1"},
  2934  			cellSrvVSchemas: map[string]*vschemapb.SrvVSchema{
  2935  				"zone0": {
  2936  					Keyspaces:    map[string]*vschemapb.Keyspace{},
  2937  					RoutingRules: &vschemapb.RoutingRules{},
  2938  				},
  2939  			},
  2940  			req: &vtadminpb.GetSrvVSchemasRequest{
  2941  				Cells: []string{"zone1"},
  2942  			},
  2943  			expected: &vtadminpb.GetSrvVSchemasResponse{
  2944  				SrvVSchemas: []*vtadminpb.SrvVSchema{
  2945  					{
  2946  						Cell: "zone1",
  2947  						Cluster: &vtadminpb.Cluster{
  2948  							Id:   clusterID,
  2949  							Name: clusterName,
  2950  						},
  2951  						SrvVSchema: &vschemapb.SrvVSchema{},
  2952  					},
  2953  				},
  2954  			},
  2955  		},
  2956  		{
  2957  			name:  "filtering by nonexistent cluster",
  2958  			cells: []string{"zone0"},
  2959  			cellSrvVSchemas: map[string]*vschemapb.SrvVSchema{
  2960  				"zone0": {
  2961  					Keyspaces:    map[string]*vschemapb.Keyspace{},
  2962  					RoutingRules: &vschemapb.RoutingRules{},
  2963  				},
  2964  			},
  2965  			req: &vtadminpb.GetSrvVSchemasRequest{
  2966  				ClusterIds: []string{"doesnt-exist"},
  2967  			},
  2968  			expected: &vtadminpb.GetSrvVSchemasResponse{},
  2969  		},
  2970  	}
  2971  
  2972  	ctx := context.Background()
  2973  
  2974  	for _, tt := range tests {
  2975  		tt := tt
  2976  
  2977  		t.Run(tt.name, func(t *testing.T) {
  2978  			t.Parallel()
  2979  
  2980  			tmc := testutil.TabletManagerClient{}
  2981  
  2982  			toposerver := memorytopo.NewServer(tt.cells...)
  2983  
  2984  			vtctldserver := testutil.NewVtctldServerWithTabletManagerClient(t, toposerver, &tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  2985  				return grpcvtctldserver.NewVtctldServer(ts)
  2986  			})
  2987  
  2988  			testutil.WithTestServer(t, vtctldserver, func(t *testing.T, vtctldClient vtctldclient.VtctldClient) {
  2989  				for cell, svs := range tt.cellSrvVSchemas {
  2990  					err := toposerver.UpdateSrvVSchema(ctx, cell, svs)
  2991  					require.NoError(t, err)
  2992  				}
  2993  
  2994  				clusters := []*cluster.Cluster{
  2995  					vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{
  2996  						Cluster: &vtadminpb.Cluster{
  2997  							Id:   clusterID,
  2998  							Name: clusterName,
  2999  						},
  3000  						VtctldClient: vtctldClient,
  3001  					}),
  3002  				}
  3003  
  3004  				api := NewAPI(clusters, Options{})
  3005  				resp, err := api.GetSrvVSchemas(ctx, tt.req)
  3006  
  3007  				if tt.shouldErr {
  3008  					assert.Error(t, err)
  3009  					return
  3010  				}
  3011  
  3012  				require.NoError(t, err)
  3013  				vtadmintestutil.AssertSrvVSchemaSlicesEqual(t, tt.expected.SrvVSchemas, resp.SrvVSchemas)
  3014  			})
  3015  		})
  3016  	}
  3017  }
  3018  
  3019  func TestGetTablet(t *testing.T) {
  3020  	t.Parallel()
  3021  
  3022  	tests := []struct {
  3023  		name           string
  3024  		clusterTablets [][]*vtadminpb.Tablet
  3025  		dbconfigs      map[string]vtadmintestutil.Dbcfg
  3026  		req            *vtadminpb.GetTabletRequest
  3027  		expected       *vtadminpb.Tablet
  3028  		shouldErr      bool
  3029  	}{
  3030  		{
  3031  			name: "single cluster",
  3032  			clusterTablets: [][]*vtadminpb.Tablet{
  3033  				{
  3034  					/* cluster 0 */
  3035  					{
  3036  						State: vtadminpb.Tablet_SERVING,
  3037  						Tablet: &topodatapb.Tablet{
  3038  							Alias: &topodatapb.TabletAlias{
  3039  								Uid:  100,
  3040  								Cell: "zone1",
  3041  							},
  3042  							Hostname: "ks1-00-00-zone1-a",
  3043  							Keyspace: "ks1",
  3044  							Shard:    "-",
  3045  							Type:     topodatapb.TabletType_PRIMARY,
  3046  						},
  3047  					},
  3048  				},
  3049  			},
  3050  			dbconfigs: map[string]vtadmintestutil.Dbcfg{},
  3051  			req: &vtadminpb.GetTabletRequest{
  3052  				Alias: &topodatapb.TabletAlias{
  3053  					Cell: "zone1",
  3054  					Uid:  100,
  3055  				},
  3056  			},
  3057  			expected: &vtadminpb.Tablet{
  3058  				Cluster: &vtadminpb.Cluster{
  3059  					Id:   "c0",
  3060  					Name: "cluster0",
  3061  				},
  3062  				State: vtadminpb.Tablet_SERVING,
  3063  				Tablet: &topodatapb.Tablet{
  3064  					Alias: &topodatapb.TabletAlias{
  3065  						Uid:  100,
  3066  						Cell: "zone1",
  3067  					},
  3068  					Hostname: "ks1-00-00-zone1-a",
  3069  					Keyspace: "ks1",
  3070  					Shard:    "-",
  3071  					Type:     topodatapb.TabletType_PRIMARY,
  3072  				},
  3073  			},
  3074  			shouldErr: false,
  3075  		},
  3076  		{
  3077  			name: "one cluster errors",
  3078  			clusterTablets: [][]*vtadminpb.Tablet{
  3079  				/* cluster 0 */
  3080  				{
  3081  					{
  3082  						State: vtadminpb.Tablet_SERVING,
  3083  						Tablet: &topodatapb.Tablet{
  3084  							Alias: &topodatapb.TabletAlias{
  3085  								Uid:  100,
  3086  								Cell: "zone1",
  3087  							},
  3088  							Hostname: "ks1-00-00-zone1-a",
  3089  							Keyspace: "ks1",
  3090  							Shard:    "-",
  3091  							Type:     topodatapb.TabletType_PRIMARY,
  3092  						},
  3093  					},
  3094  				},
  3095  				/* cluster 1 */
  3096  				{
  3097  					{
  3098  						State: vtadminpb.Tablet_SERVING,
  3099  						Tablet: &topodatapb.Tablet{
  3100  							Alias: &topodatapb.TabletAlias{
  3101  								Uid:  200,
  3102  								Cell: "zone1",
  3103  							},
  3104  							Hostname: "ks2-00-00-zone1-a",
  3105  							Keyspace: "ks2",
  3106  							Shard:    "-",
  3107  							Type:     topodatapb.TabletType_PRIMARY,
  3108  						},
  3109  					},
  3110  				},
  3111  			},
  3112  			dbconfigs: map[string]vtadmintestutil.Dbcfg{
  3113  				"c1": {ShouldErr: true},
  3114  			},
  3115  			req: &vtadminpb.GetTabletRequest{
  3116  				Alias: &topodatapb.TabletAlias{
  3117  					Cell: "doesntmatter",
  3118  					Uid:  100,
  3119  				},
  3120  			},
  3121  			expected:  nil,
  3122  			shouldErr: true,
  3123  		},
  3124  		{
  3125  			name: "multi cluster, selecting one with tablet",
  3126  			clusterTablets: [][]*vtadminpb.Tablet{
  3127  				/* cluster 0 */
  3128  				{
  3129  					{
  3130  						State: vtadminpb.Tablet_SERVING,
  3131  						Tablet: &topodatapb.Tablet{
  3132  							Alias: &topodatapb.TabletAlias{
  3133  								Uid:  100,
  3134  								Cell: "zone1",
  3135  							},
  3136  							Hostname: "ks1-00-00-zone1-a",
  3137  							Keyspace: "ks1",
  3138  							Shard:    "-",
  3139  							Type:     topodatapb.TabletType_PRIMARY,
  3140  						},
  3141  					},
  3142  				},
  3143  				/* cluster 1 */
  3144  				{
  3145  					{
  3146  						State: vtadminpb.Tablet_SERVING,
  3147  						Tablet: &topodatapb.Tablet{
  3148  							Alias: &topodatapb.TabletAlias{
  3149  								Uid:  200,
  3150  								Cell: "zone1",
  3151  							},
  3152  							Hostname: "ks2-00-00-zone1-a",
  3153  							Keyspace: "ks2",
  3154  							Shard:    "-",
  3155  							Type:     topodatapb.TabletType_PRIMARY,
  3156  						},
  3157  					},
  3158  				},
  3159  			},
  3160  			dbconfigs: map[string]vtadmintestutil.Dbcfg{},
  3161  			req: &vtadminpb.GetTabletRequest{
  3162  				Alias: &topodatapb.TabletAlias{
  3163  					Cell: "zone1",
  3164  					Uid:  100,
  3165  				},
  3166  				ClusterIds: []string{"c0"},
  3167  			},
  3168  			expected: &vtadminpb.Tablet{
  3169  				Cluster: &vtadminpb.Cluster{
  3170  					Id:   "c0",
  3171  					Name: "cluster0",
  3172  				},
  3173  				State: vtadminpb.Tablet_SERVING,
  3174  				Tablet: &topodatapb.Tablet{
  3175  					Alias: &topodatapb.TabletAlias{
  3176  						Uid:  100,
  3177  						Cell: "zone1",
  3178  					},
  3179  					Hostname: "ks1-00-00-zone1-a",
  3180  					Keyspace: "ks1",
  3181  					Shard:    "-",
  3182  					Type:     topodatapb.TabletType_PRIMARY,
  3183  				},
  3184  			},
  3185  			shouldErr: false,
  3186  		},
  3187  		{
  3188  			name: "multi cluster, multiple results",
  3189  			clusterTablets: [][]*vtadminpb.Tablet{
  3190  				/* cluster 0 */
  3191  				{
  3192  					{
  3193  						State: vtadminpb.Tablet_SERVING,
  3194  						Tablet: &topodatapb.Tablet{
  3195  							Alias: &topodatapb.TabletAlias{
  3196  								Uid:  100,
  3197  								Cell: "zone1",
  3198  							},
  3199  							Hostname: "ks1-00-00-zone1-a",
  3200  							Keyspace: "ks1",
  3201  							Shard:    "-",
  3202  							Type:     topodatapb.TabletType_PRIMARY,
  3203  						},
  3204  					},
  3205  				},
  3206  				/* cluster 1 */
  3207  				{
  3208  					{
  3209  						State: vtadminpb.Tablet_SERVING,
  3210  						Tablet: &topodatapb.Tablet{
  3211  							Alias: &topodatapb.TabletAlias{
  3212  								Uid:  100,
  3213  								Cell: "zone1",
  3214  							},
  3215  							Hostname: "ks1-00-00-zone1-a",
  3216  							Keyspace: "ks1",
  3217  							Shard:    "-",
  3218  							Type:     topodatapb.TabletType_PRIMARY,
  3219  						},
  3220  					},
  3221  				},
  3222  			},
  3223  			dbconfigs: map[string]vtadmintestutil.Dbcfg{},
  3224  			req: &vtadminpb.GetTabletRequest{
  3225  				Alias: &topodatapb.TabletAlias{
  3226  					Cell: "zone1",
  3227  					Uid:  100,
  3228  				},
  3229  			},
  3230  			expected:  nil,
  3231  			shouldErr: true,
  3232  		},
  3233  		{
  3234  			name: "no results",
  3235  			clusterTablets: [][]*vtadminpb.Tablet{
  3236  				/* cluster 0 */
  3237  				{},
  3238  			},
  3239  			dbconfigs: map[string]vtadmintestutil.Dbcfg{},
  3240  			req: &vtadminpb.GetTabletRequest{
  3241  				Alias: &topodatapb.TabletAlias{
  3242  					Cell: "zone1",
  3243  					Uid:  100,
  3244  				},
  3245  			},
  3246  			expected:  nil,
  3247  			shouldErr: true,
  3248  		},
  3249  	}
  3250  
  3251  	ctx := context.Background()
  3252  
  3253  	for _, tt := range tests {
  3254  		tt := tt
  3255  
  3256  		t.Run(tt.name, func(t *testing.T) {
  3257  			t.Parallel()
  3258  
  3259  			clusters := make([]*cluster.Cluster, len(tt.clusterTablets))
  3260  
  3261  			for i, tablets := range tt.clusterTablets {
  3262  				cid := fmt.Sprintf("c%d", i)
  3263  				dbconfigs := tt.dbconfigs[cid]
  3264  
  3265  				clusters[i] = vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{
  3266  					Cluster: &vtadminpb.Cluster{
  3267  						Id:   cid,
  3268  						Name: fmt.Sprintf("cluster%d", i),
  3269  					},
  3270  					Tablets:  tablets,
  3271  					DBConfig: dbconfigs,
  3272  				})
  3273  			}
  3274  
  3275  			api := NewAPI(clusters, Options{})
  3276  			resp, err := api.GetTablet(ctx, tt.req)
  3277  			if tt.shouldErr {
  3278  				assert.Error(t, err)
  3279  				return
  3280  			}
  3281  
  3282  			assert.NoError(t, err)
  3283  			utils.MustMatch(t, tt.expected, resp)
  3284  		})
  3285  	}
  3286  }
  3287  
  3288  func TestGetTablets(t *testing.T) {
  3289  	t.Parallel()
  3290  
  3291  	tests := []struct {
  3292  		name           string
  3293  		clusterTablets [][]*vtadminpb.Tablet
  3294  		dbconfigs      map[string]vtadmintestutil.Dbcfg
  3295  		req            *vtadminpb.GetTabletsRequest
  3296  		expected       []*vtadminpb.Tablet
  3297  		shouldErr      bool
  3298  	}{
  3299  		{
  3300  			name: "single cluster",
  3301  			clusterTablets: [][]*vtadminpb.Tablet{
  3302  				{
  3303  					/* cluster 0 */
  3304  					{
  3305  						State: vtadminpb.Tablet_SERVING,
  3306  						Tablet: &topodatapb.Tablet{
  3307  							Alias: &topodatapb.TabletAlias{
  3308  								Uid:  100,
  3309  								Cell: "zone1",
  3310  							},
  3311  							Hostname: "ks1-00-00-zone1-a",
  3312  							Keyspace: "ks1",
  3313  							Shard:    "-",
  3314  							Type:     topodatapb.TabletType_PRIMARY,
  3315  						},
  3316  					},
  3317  				},
  3318  			},
  3319  			dbconfigs: map[string]vtadmintestutil.Dbcfg{},
  3320  			req:       &vtadminpb.GetTabletsRequest{},
  3321  			expected: []*vtadminpb.Tablet{
  3322  				{
  3323  					Cluster: &vtadminpb.Cluster{
  3324  						Id:   "c0",
  3325  						Name: "cluster0",
  3326  					},
  3327  					State: vtadminpb.Tablet_SERVING,
  3328  					Tablet: &topodatapb.Tablet{
  3329  						Alias: &topodatapb.TabletAlias{
  3330  							Uid:  100,
  3331  							Cell: "zone1",
  3332  						},
  3333  						Hostname: "ks1-00-00-zone1-a",
  3334  						Keyspace: "ks1",
  3335  						Shard:    "-",
  3336  						Type:     topodatapb.TabletType_PRIMARY,
  3337  					},
  3338  				},
  3339  			},
  3340  			shouldErr: false,
  3341  		},
  3342  		{
  3343  			name: "one cluster errors",
  3344  			clusterTablets: [][]*vtadminpb.Tablet{
  3345  				/* cluster 0 */
  3346  				{
  3347  					{
  3348  						State: vtadminpb.Tablet_SERVING,
  3349  						Tablet: &topodatapb.Tablet{
  3350  							Alias: &topodatapb.TabletAlias{
  3351  								Uid:  100,
  3352  								Cell: "zone1",
  3353  							},
  3354  							Hostname: "ks1-00-00-zone1-a",
  3355  							Keyspace: "ks1",
  3356  							Shard:    "-",
  3357  							Type:     topodatapb.TabletType_PRIMARY,
  3358  						},
  3359  					},
  3360  				},
  3361  				/* cluster 1 */
  3362  				{
  3363  					{
  3364  						State: vtadminpb.Tablet_SERVING,
  3365  						Tablet: &topodatapb.Tablet{
  3366  							Alias: &topodatapb.TabletAlias{
  3367  								Uid:  200,
  3368  								Cell: "zone1",
  3369  							},
  3370  							Hostname: "ks2-00-00-zone1-a",
  3371  							Keyspace: "ks2",
  3372  							Shard:    "-",
  3373  							Type:     topodatapb.TabletType_PRIMARY,
  3374  						},
  3375  					},
  3376  				},
  3377  			},
  3378  			dbconfigs: map[string]vtadmintestutil.Dbcfg{
  3379  				"c1": {ShouldErr: true},
  3380  			},
  3381  			req:       &vtadminpb.GetTabletsRequest{},
  3382  			expected:  nil,
  3383  			shouldErr: true,
  3384  		},
  3385  		{
  3386  			name: "multi cluster, selecting one",
  3387  			clusterTablets: [][]*vtadminpb.Tablet{
  3388  				/* cluster 0 */
  3389  				{
  3390  					{
  3391  						State: vtadminpb.Tablet_SERVING,
  3392  						Tablet: &topodatapb.Tablet{
  3393  							Alias: &topodatapb.TabletAlias{
  3394  								Uid:  100,
  3395  								Cell: "zone1",
  3396  							},
  3397  							Hostname: "ks1-00-00-zone1-a",
  3398  							Keyspace: "ks1",
  3399  							Shard:    "-",
  3400  							Type:     topodatapb.TabletType_PRIMARY,
  3401  						},
  3402  					},
  3403  				},
  3404  				/* cluster 1 */
  3405  				{
  3406  					{
  3407  						State: vtadminpb.Tablet_SERVING,
  3408  						Tablet: &topodatapb.Tablet{
  3409  							Alias: &topodatapb.TabletAlias{
  3410  								Uid:  200,
  3411  								Cell: "zone1",
  3412  							},
  3413  							Hostname: "ks2-00-00-zone1-a",
  3414  							Keyspace: "ks2",
  3415  							Shard:    "-",
  3416  							Type:     topodatapb.TabletType_PRIMARY,
  3417  						},
  3418  					},
  3419  				},
  3420  			},
  3421  			dbconfigs: map[string]vtadmintestutil.Dbcfg{},
  3422  			req:       &vtadminpb.GetTabletsRequest{ClusterIds: []string{"c0"}},
  3423  			expected: []*vtadminpb.Tablet{
  3424  				{
  3425  					Cluster: &vtadminpb.Cluster{
  3426  						Id:   "c0",
  3427  						Name: "cluster0",
  3428  					},
  3429  					State: vtadminpb.Tablet_SERVING,
  3430  					Tablet: &topodatapb.Tablet{
  3431  						Alias: &topodatapb.TabletAlias{
  3432  							Uid:  100,
  3433  							Cell: "zone1",
  3434  						},
  3435  						Hostname: "ks1-00-00-zone1-a",
  3436  						Keyspace: "ks1",
  3437  						Shard:    "-",
  3438  						Type:     topodatapb.TabletType_PRIMARY,
  3439  					},
  3440  				},
  3441  			},
  3442  			shouldErr: false,
  3443  		},
  3444  	}
  3445  
  3446  	ctx := context.Background()
  3447  
  3448  	for _, tt := range tests {
  3449  		tt := tt
  3450  
  3451  		t.Run(tt.name, func(t *testing.T) {
  3452  			t.Parallel()
  3453  
  3454  			clusters := make([]*cluster.Cluster, len(tt.clusterTablets))
  3455  
  3456  			for i, tablets := range tt.clusterTablets {
  3457  				cid := fmt.Sprintf("c%d", i)
  3458  				dbconfigs := tt.dbconfigs[cid]
  3459  
  3460  				clusters[i] = vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{
  3461  					Cluster: &vtadminpb.Cluster{
  3462  						Id:   cid,
  3463  						Name: fmt.Sprintf("cluster%d", i),
  3464  					},
  3465  					Tablets:  tablets,
  3466  					DBConfig: dbconfigs,
  3467  				})
  3468  			}
  3469  
  3470  			api := NewAPI(clusters, Options{})
  3471  			resp, err := api.GetTablets(ctx, tt.req)
  3472  			if tt.shouldErr {
  3473  				assert.Error(t, err)
  3474  				return
  3475  			}
  3476  
  3477  			assert.NoError(t, err)
  3478  			assert.ElementsMatch(t, tt.expected, resp.Tablets)
  3479  		})
  3480  	}
  3481  }
  3482  
  3483  func TestGetVSchema(t *testing.T) {
  3484  	t.Parallel()
  3485  
  3486  	tests := []struct {
  3487  		name       string
  3488  		clusterCfg vtadmintestutil.TestClusterConfig
  3489  		req        *vtadminpb.GetVSchemaRequest
  3490  		expected   *vtadminpb.VSchema
  3491  		shouldErr  bool
  3492  	}{
  3493  		{
  3494  			name: "success",
  3495  			clusterCfg: vtadmintestutil.TestClusterConfig{
  3496  				Cluster: &vtadminpb.Cluster{
  3497  					Id:   "c1",
  3498  					Name: "cluster1",
  3499  				},
  3500  				VtctldClient: &fakevtctldclient.VtctldClient{
  3501  					GetVSchemaResults: map[string]struct {
  3502  						Response *vtctldatapb.GetVSchemaResponse
  3503  						Error    error
  3504  					}{
  3505  						"testkeyspace": {
  3506  							Response: &vtctldatapb.GetVSchemaResponse{
  3507  								VSchema: &vschemapb.Keyspace{
  3508  									Sharded: true,
  3509  									Vindexes: map[string]*vschemapb.Vindex{
  3510  										"hash": {
  3511  											Type: "md5hash",
  3512  										},
  3513  									},
  3514  								},
  3515  							},
  3516  						},
  3517  					},
  3518  				},
  3519  			},
  3520  			req: &vtadminpb.GetVSchemaRequest{
  3521  				ClusterId: "c1",
  3522  				Keyspace:  "testkeyspace",
  3523  			},
  3524  			expected: &vtadminpb.VSchema{
  3525  				Cluster: &vtadminpb.Cluster{
  3526  					Id:   "c1",
  3527  					Name: "cluster1",
  3528  				},
  3529  				Name: "testkeyspace",
  3530  				VSchema: &vschemapb.Keyspace{
  3531  					Sharded: true,
  3532  					Vindexes: map[string]*vschemapb.Vindex{
  3533  						"hash": {
  3534  							Type: "md5hash",
  3535  						},
  3536  					},
  3537  				},
  3538  			},
  3539  			shouldErr: false,
  3540  		},
  3541  		{
  3542  			name: "no vschema for keyspace",
  3543  			clusterCfg: vtadmintestutil.TestClusterConfig{
  3544  				Cluster: &vtadminpb.Cluster{
  3545  					Id:   "c1",
  3546  					Name: "cluster1",
  3547  				},
  3548  				VtctldClient: &fakevtctldclient.VtctldClient{
  3549  					GetVSchemaResults: map[string]struct {
  3550  						Response *vtctldatapb.GetVSchemaResponse
  3551  						Error    error
  3552  					}{
  3553  						"testkeyspace": {
  3554  							Response: &vtctldatapb.GetVSchemaResponse{
  3555  								VSchema: &vschemapb.Keyspace{
  3556  									Sharded: true,
  3557  									Vindexes: map[string]*vschemapb.Vindex{
  3558  										"hash": {
  3559  											Type: "md5hash",
  3560  										},
  3561  									},
  3562  								},
  3563  							},
  3564  						},
  3565  					},
  3566  				},
  3567  			},
  3568  			req: &vtadminpb.GetVSchemaRequest{
  3569  				ClusterId: "c1",
  3570  				Keyspace:  "otherkeyspace",
  3571  			},
  3572  			expected:  nil,
  3573  			shouldErr: true,
  3574  		},
  3575  		{
  3576  			name: "cluster not found",
  3577  			clusterCfg: vtadmintestutil.TestClusterConfig{
  3578  				Cluster: &vtadminpb.Cluster{
  3579  					Id:   "c1",
  3580  					Name: "cluster1",
  3581  				},
  3582  			},
  3583  			req: &vtadminpb.GetVSchemaRequest{
  3584  				ClusterId: "c2",
  3585  				Keyspace:  "testkeyspace",
  3586  			},
  3587  			expected:  nil,
  3588  			shouldErr: true,
  3589  		},
  3590  	}
  3591  
  3592  	ctx := context.Background()
  3593  
  3594  	for _, tt := range tests {
  3595  		tt := tt
  3596  
  3597  		t.Run(tt.name, func(t *testing.T) {
  3598  			t.Parallel()
  3599  
  3600  			clusters := []*cluster.Cluster{vtadmintestutil.BuildCluster(t, tt.clusterCfg)}
  3601  			api := NewAPI(clusters, Options{})
  3602  
  3603  			resp, err := api.GetVSchema(ctx, tt.req)
  3604  			if tt.shouldErr {
  3605  				assert.Error(t, err)
  3606  
  3607  				return
  3608  			}
  3609  
  3610  			assert.NoError(t, err)
  3611  			assert.Truef(t, proto.Equal(tt.expected, resp), "expected %v, got %v", tt.expected, resp)
  3612  		})
  3613  	}
  3614  }
  3615  
  3616  func TestGetVSchemas(t *testing.T) {
  3617  	t.Parallel()
  3618  
  3619  	tests := []struct {
  3620  		name        string
  3621  		clusterCfgs []vtadmintestutil.TestClusterConfig
  3622  		req         *vtadminpb.GetVSchemasRequest
  3623  		expected    *vtadminpb.GetVSchemasResponse
  3624  		shouldErr   bool
  3625  	}{
  3626  		{
  3627  			name: "success",
  3628  			clusterCfgs: []vtadmintestutil.TestClusterConfig{
  3629  				{
  3630  					Cluster: &vtadminpb.Cluster{
  3631  						Id:   "c1",
  3632  						Name: "cluster1",
  3633  					},
  3634  					VtctldClient: &fakevtctldclient.VtctldClient{
  3635  						GetKeyspacesResults: &struct {
  3636  							Keyspaces []*vtctldatapb.Keyspace
  3637  							Error     error
  3638  						}{
  3639  							Keyspaces: []*vtctldatapb.Keyspace{
  3640  								{
  3641  									Name: "testkeyspace",
  3642  								},
  3643  							},
  3644  						},
  3645  						GetVSchemaResults: map[string]struct {
  3646  							Response *vtctldatapb.GetVSchemaResponse
  3647  							Error    error
  3648  						}{
  3649  							"testkeyspace": {
  3650  								Response: &vtctldatapb.GetVSchemaResponse{
  3651  									VSchema: &vschemapb.Keyspace{},
  3652  								},
  3653  							},
  3654  						},
  3655  					},
  3656  				},
  3657  				{
  3658  					Cluster: &vtadminpb.Cluster{
  3659  						Id:   "c2",
  3660  						Name: "cluster2",
  3661  					},
  3662  					VtctldClient: &fakevtctldclient.VtctldClient{
  3663  						GetKeyspacesResults: &struct {
  3664  							Keyspaces []*vtctldatapb.Keyspace
  3665  							Error     error
  3666  						}{
  3667  							Keyspaces: []*vtctldatapb.Keyspace{
  3668  								{
  3669  									Name: "k2",
  3670  								},
  3671  							},
  3672  						},
  3673  						GetVSchemaResults: map[string]struct {
  3674  							Response *vtctldatapb.GetVSchemaResponse
  3675  							Error    error
  3676  						}{
  3677  							"k2": {
  3678  								Response: &vtctldatapb.GetVSchemaResponse{
  3679  									VSchema: &vschemapb.Keyspace{},
  3680  								},
  3681  							},
  3682  						},
  3683  					},
  3684  				},
  3685  			},
  3686  			req: &vtadminpb.GetVSchemasRequest{},
  3687  			expected: &vtadminpb.GetVSchemasResponse{
  3688  				VSchemas: []*vtadminpb.VSchema{
  3689  					{
  3690  						Cluster: &vtadminpb.Cluster{
  3691  							Id:   "c1",
  3692  							Name: "cluster1",
  3693  						},
  3694  						Name:    "testkeyspace",
  3695  						VSchema: &vschemapb.Keyspace{},
  3696  					},
  3697  					{
  3698  						Cluster: &vtadminpb.Cluster{
  3699  							Id:   "c2",
  3700  							Name: "cluster2",
  3701  						},
  3702  						Name:    "k2",
  3703  						VSchema: &vschemapb.Keyspace{},
  3704  					},
  3705  				},
  3706  			},
  3707  			shouldErr: false,
  3708  		},
  3709  		{
  3710  			name: "requesting specific clusters",
  3711  			clusterCfgs: []vtadmintestutil.TestClusterConfig{
  3712  				{
  3713  					Cluster: &vtadminpb.Cluster{
  3714  						Id:   "c1",
  3715  						Name: "cluster1",
  3716  					},
  3717  					VtctldClient: &fakevtctldclient.VtctldClient{
  3718  						GetKeyspacesResults: &struct {
  3719  							Keyspaces []*vtctldatapb.Keyspace
  3720  							Error     error
  3721  						}{
  3722  							Keyspaces: []*vtctldatapb.Keyspace{
  3723  								{
  3724  									Name: "testkeyspace",
  3725  								},
  3726  							},
  3727  						},
  3728  						GetVSchemaResults: map[string]struct {
  3729  							Response *vtctldatapb.GetVSchemaResponse
  3730  							Error    error
  3731  						}{
  3732  							"testkeyspace": {
  3733  								Response: &vtctldatapb.GetVSchemaResponse{
  3734  									VSchema: &vschemapb.Keyspace{},
  3735  								},
  3736  							},
  3737  						},
  3738  					},
  3739  				},
  3740  				{
  3741  					Cluster: &vtadminpb.Cluster{
  3742  						Id:   "c2",
  3743  						Name: "cluster2",
  3744  					},
  3745  					VtctldClient: &fakevtctldclient.VtctldClient{
  3746  						GetKeyspacesResults: &struct {
  3747  							Keyspaces []*vtctldatapb.Keyspace
  3748  							Error     error
  3749  						}{
  3750  							Keyspaces: []*vtctldatapb.Keyspace{
  3751  								{
  3752  									Name: "k2",
  3753  								},
  3754  							},
  3755  						},
  3756  						GetVSchemaResults: map[string]struct {
  3757  							Response *vtctldatapb.GetVSchemaResponse
  3758  							Error    error
  3759  						}{
  3760  							"k2": {
  3761  								Response: &vtctldatapb.GetVSchemaResponse{
  3762  									VSchema: &vschemapb.Keyspace{},
  3763  								},
  3764  							},
  3765  						},
  3766  					},
  3767  				},
  3768  			},
  3769  			req: &vtadminpb.GetVSchemasRequest{
  3770  				ClusterIds: []string{"c2"},
  3771  			},
  3772  			expected: &vtadminpb.GetVSchemasResponse{
  3773  				VSchemas: []*vtadminpb.VSchema{
  3774  					{
  3775  						Cluster: &vtadminpb.Cluster{
  3776  							Id:   "c2",
  3777  							Name: "cluster2",
  3778  						},
  3779  						Name:    "k2",
  3780  						VSchema: &vschemapb.Keyspace{},
  3781  					},
  3782  				},
  3783  			},
  3784  			shouldErr: false,
  3785  		},
  3786  		{
  3787  			name: "GetKeyspaces failure",
  3788  			clusterCfgs: []vtadmintestutil.TestClusterConfig{
  3789  				{
  3790  					Cluster: &vtadminpb.Cluster{
  3791  						Id:   "c1",
  3792  						Name: "cluster1",
  3793  					},
  3794  					VtctldClient: &fakevtctldclient.VtctldClient{
  3795  						GetKeyspacesResults: &struct {
  3796  							Keyspaces []*vtctldatapb.Keyspace
  3797  							Error     error
  3798  						}{
  3799  							Keyspaces: []*vtctldatapb.Keyspace{
  3800  								{
  3801  									Name: "testkeyspace",
  3802  								},
  3803  							},
  3804  						},
  3805  						GetVSchemaResults: map[string]struct {
  3806  							Response *vtctldatapb.GetVSchemaResponse
  3807  							Error    error
  3808  						}{
  3809  							"testkeyspace": {
  3810  								Response: &vtctldatapb.GetVSchemaResponse{
  3811  									VSchema: &vschemapb.Keyspace{},
  3812  								},
  3813  							},
  3814  						},
  3815  					},
  3816  				},
  3817  				{
  3818  					Cluster: &vtadminpb.Cluster{
  3819  						Id:   "c2",
  3820  						Name: "cluster2",
  3821  					},
  3822  					VtctldClient: &fakevtctldclient.VtctldClient{
  3823  						GetKeyspacesResults: &struct {
  3824  							Keyspaces []*vtctldatapb.Keyspace
  3825  							Error     error
  3826  						}{
  3827  							Error: assert.AnError,
  3828  						},
  3829  					},
  3830  				},
  3831  			},
  3832  			req:       &vtadminpb.GetVSchemasRequest{},
  3833  			expected:  nil,
  3834  			shouldErr: true,
  3835  		},
  3836  		{
  3837  			name: "GetVSchema failure",
  3838  			clusterCfgs: []vtadmintestutil.TestClusterConfig{
  3839  				{
  3840  					Cluster: &vtadminpb.Cluster{
  3841  						Id:   "c1",
  3842  						Name: "cluster1",
  3843  					},
  3844  					VtctldClient: &fakevtctldclient.VtctldClient{
  3845  						GetKeyspacesResults: &struct {
  3846  							Keyspaces []*vtctldatapb.Keyspace
  3847  							Error     error
  3848  						}{
  3849  							Keyspaces: []*vtctldatapb.Keyspace{
  3850  								{
  3851  									Name: "testkeyspace",
  3852  								},
  3853  							},
  3854  						},
  3855  						GetVSchemaResults: map[string]struct {
  3856  							Response *vtctldatapb.GetVSchemaResponse
  3857  							Error    error
  3858  						}{
  3859  							"testkeyspace": {
  3860  								Error: assert.AnError,
  3861  							},
  3862  						},
  3863  					},
  3864  				},
  3865  				{
  3866  					Cluster: &vtadminpb.Cluster{
  3867  						Id:   "c2",
  3868  						Name: "cluster2",
  3869  					},
  3870  					VtctldClient: &fakevtctldclient.VtctldClient{
  3871  						GetKeyspacesResults: &struct {
  3872  							Keyspaces []*vtctldatapb.Keyspace
  3873  							Error     error
  3874  						}{
  3875  							Keyspaces: []*vtctldatapb.Keyspace{
  3876  								{
  3877  									Name: "k2",
  3878  								},
  3879  							},
  3880  						},
  3881  						GetVSchemaResults: map[string]struct {
  3882  							Response *vtctldatapb.GetVSchemaResponse
  3883  							Error    error
  3884  						}{
  3885  							"k2": {
  3886  								Response: &vtctldatapb.GetVSchemaResponse{
  3887  									VSchema: &vschemapb.Keyspace{},
  3888  								},
  3889  							},
  3890  						},
  3891  					},
  3892  				},
  3893  			},
  3894  			req:       &vtadminpb.GetVSchemasRequest{},
  3895  			expected:  nil,
  3896  			shouldErr: true,
  3897  		},
  3898  		{
  3899  			name:        "no clusters specified",
  3900  			clusterCfgs: []vtadmintestutil.TestClusterConfig{},
  3901  			req:         &vtadminpb.GetVSchemasRequest{},
  3902  			expected: &vtadminpb.GetVSchemasResponse{
  3903  				VSchemas: []*vtadminpb.VSchema{},
  3904  			},
  3905  			shouldErr: false,
  3906  		},
  3907  		{
  3908  			name:        "requested invalid cluster",
  3909  			clusterCfgs: []vtadmintestutil.TestClusterConfig{},
  3910  			req: &vtadminpb.GetVSchemasRequest{
  3911  				ClusterIds: []string{"c1"},
  3912  			},
  3913  			expected:  nil,
  3914  			shouldErr: true,
  3915  		},
  3916  	}
  3917  
  3918  	ctx := context.Background()
  3919  
  3920  	for _, tt := range tests {
  3921  		tt := tt
  3922  
  3923  		t.Run(tt.name, func(t *testing.T) {
  3924  			t.Parallel()
  3925  
  3926  			if tt.req == nil {
  3927  				t.SkipNow()
  3928  			}
  3929  
  3930  			clusters := vtadmintestutil.BuildClusters(t, tt.clusterCfgs...)
  3931  			api := NewAPI(clusters, Options{})
  3932  
  3933  			resp, err := api.GetVSchemas(ctx, tt.req)
  3934  			if tt.shouldErr {
  3935  				assert.Error(t, err)
  3936  
  3937  				return
  3938  			}
  3939  
  3940  			assert.NoError(t, err)
  3941  			assert.ElementsMatch(t, tt.expected.VSchemas, resp.VSchemas)
  3942  		})
  3943  	}
  3944  }
  3945  
  3946  func TestGetVtctlds(t *testing.T) {
  3947  	t.Parallel()
  3948  
  3949  	fakedisco1 := fakediscovery.New()
  3950  	cluster1 := &cluster.Cluster{
  3951  		ID:        "c1",
  3952  		Name:      "cluster1",
  3953  		Discovery: fakedisco1,
  3954  	}
  3955  	cluster1Vtctlds := []*vtadminpb.Vtctld{
  3956  		{
  3957  			Hostname: "cluster1-vtctld1",
  3958  		},
  3959  		{
  3960  			Hostname: "cluster1-vtctld2",
  3961  		},
  3962  		{
  3963  			Hostname: "cluster1-vtctld3",
  3964  		},
  3965  	}
  3966  	fakedisco1.AddTaggedVtctlds(nil, cluster1Vtctlds...)
  3967  
  3968  	expectedCluster1Vtctlds := []*vtadminpb.Vtctld{
  3969  		{
  3970  			Cluster: &vtadminpb.Cluster{
  3971  				Id:   cluster1.ID,
  3972  				Name: cluster1.Name,
  3973  			},
  3974  			Hostname: "cluster1-vtctld1",
  3975  		},
  3976  		{
  3977  			Cluster: &vtadminpb.Cluster{
  3978  				Id:   cluster1.ID,
  3979  				Name: cluster1.Name,
  3980  			},
  3981  			Hostname: "cluster1-vtctld2",
  3982  		},
  3983  		{
  3984  			Cluster: &vtadminpb.Cluster{
  3985  				Id:   cluster1.ID,
  3986  				Name: cluster1.Name,
  3987  			},
  3988  			Hostname: "cluster1-vtctld3",
  3989  		},
  3990  	}
  3991  
  3992  	fakedisco2 := fakediscovery.New()
  3993  	cluster2 := &cluster.Cluster{
  3994  		ID:        "c2",
  3995  		Name:      "cluster2",
  3996  		Discovery: fakedisco2,
  3997  	}
  3998  	cluster2Vtctlds := []*vtadminpb.Vtctld{
  3999  		{
  4000  			Hostname: "cluster2-vtctld1",
  4001  		},
  4002  	}
  4003  	fakedisco2.AddTaggedVtctlds(nil, cluster2Vtctlds...)
  4004  
  4005  	expectedCluster2Vtctlds := []*vtadminpb.Vtctld{
  4006  		{
  4007  			Cluster: &vtadminpb.Cluster{
  4008  				Id:   cluster2.ID,
  4009  				Name: cluster2.Name,
  4010  			},
  4011  			Hostname: "cluster2-vtctld1",
  4012  		},
  4013  	}
  4014  
  4015  	api := NewAPI([]*cluster.Cluster{cluster1, cluster2}, Options{})
  4016  	ctx := context.Background()
  4017  
  4018  	resp, err := api.GetVtctlds(ctx, &vtadminpb.GetVtctldsRequest{})
  4019  	assert.NoError(t, err)
  4020  	assert.ElementsMatch(t, append(expectedCluster1Vtctlds, expectedCluster2Vtctlds...), resp.Vtctlds)
  4021  
  4022  	resp, err = api.GetVtctlds(ctx, &vtadminpb.GetVtctldsRequest{ClusterIds: []string{cluster1.ID}})
  4023  	assert.NoError(t, err)
  4024  	assert.ElementsMatch(t, expectedCluster1Vtctlds, resp.Vtctlds)
  4025  
  4026  	fakedisco1.SetVtctldsError(true)
  4027  
  4028  	resp, err = api.GetVtctlds(ctx, &vtadminpb.GetVtctldsRequest{})
  4029  	assert.Error(t, err)
  4030  	assert.Nil(t, resp)
  4031  }
  4032  
  4033  func TestGetWorkflow(t *testing.T) {
  4034  	t.Parallel()
  4035  
  4036  	tests := []struct {
  4037  		name      string
  4038  		cfgs      []vtadmintestutil.TestClusterConfig
  4039  		req       *vtadminpb.GetWorkflowRequest
  4040  		expected  *vtadminpb.Workflow
  4041  		shouldErr bool
  4042  	}{
  4043  		{
  4044  			name: "success",
  4045  			cfgs: []vtadmintestutil.TestClusterConfig{
  4046  				{
  4047  					Cluster: &vtadminpb.Cluster{
  4048  						Id:   "c1",
  4049  						Name: "cluster1",
  4050  					},
  4051  					VtctldClient: &fakevtctldclient.VtctldClient{
  4052  						GetWorkflowsResults: map[string]struct {
  4053  							Response *vtctldatapb.GetWorkflowsResponse
  4054  							Error    error
  4055  						}{
  4056  							"testkeyspace": {
  4057  								Response: &vtctldatapb.GetWorkflowsResponse{
  4058  									Workflows: []*vtctldatapb.Workflow{
  4059  										{
  4060  											Name: "workflow1",
  4061  										},
  4062  										{
  4063  											Name: "workflow2",
  4064  										},
  4065  									},
  4066  								},
  4067  							},
  4068  						},
  4069  					},
  4070  				},
  4071  			},
  4072  			req: &vtadminpb.GetWorkflowRequest{
  4073  				ClusterId: "c1",
  4074  				Keyspace:  "testkeyspace",
  4075  				Name:      "workflow1",
  4076  			},
  4077  			expected: &vtadminpb.Workflow{
  4078  				Cluster: &vtadminpb.Cluster{
  4079  					Id:   "c1",
  4080  					Name: "cluster1",
  4081  				},
  4082  				Keyspace: "testkeyspace",
  4083  				Workflow: &vtctldatapb.Workflow{
  4084  					Name: "workflow1",
  4085  				},
  4086  			},
  4087  			shouldErr: false,
  4088  		},
  4089  		{
  4090  			name: "no such workflow",
  4091  			cfgs: []vtadmintestutil.TestClusterConfig{
  4092  				{
  4093  					Cluster: &vtadminpb.Cluster{
  4094  						Id:   "c1",
  4095  						Name: "cluster1",
  4096  					},
  4097  					VtctldClient: &fakevtctldclient.VtctldClient{
  4098  						GetWorkflowsResults: map[string]struct {
  4099  							Response *vtctldatapb.GetWorkflowsResponse
  4100  							Error    error
  4101  						}{
  4102  							"testkeyspace": {
  4103  								Response: &vtctldatapb.GetWorkflowsResponse{
  4104  									Workflows: []*vtctldatapb.Workflow{
  4105  										{
  4106  											Name: "workflow1",
  4107  										},
  4108  										{
  4109  											Name: "workflow2",
  4110  										},
  4111  									},
  4112  								},
  4113  							},
  4114  						},
  4115  					},
  4116  				},
  4117  			},
  4118  			req: &vtadminpb.GetWorkflowRequest{
  4119  				ClusterId: "c1",
  4120  				Keyspace:  "testkeyspace",
  4121  				Name:      "workflow3",
  4122  			},
  4123  			expected:  nil,
  4124  			shouldErr: true,
  4125  		},
  4126  		{
  4127  			name: "no such cluster",
  4128  			cfgs: []vtadmintestutil.TestClusterConfig{},
  4129  			req: &vtadminpb.GetWorkflowRequest{
  4130  				ClusterId: "c1",
  4131  				Keyspace:  "testkeyspace",
  4132  				Name:      "workflow1",
  4133  			},
  4134  			expected:  nil,
  4135  			shouldErr: true,
  4136  		},
  4137  	}
  4138  
  4139  	ctx := context.Background()
  4140  
  4141  	for _, tt := range tests {
  4142  		tt := tt
  4143  
  4144  		t.Run(tt.name, func(t *testing.T) {
  4145  			t.Parallel()
  4146  
  4147  			api := NewAPI(vtadmintestutil.BuildClusters(t, tt.cfgs...), Options{})
  4148  
  4149  			resp, err := api.GetWorkflow(ctx, tt.req)
  4150  			if tt.shouldErr {
  4151  				assert.Error(t, err)
  4152  
  4153  				return
  4154  			}
  4155  
  4156  			assert.NoError(t, err)
  4157  			assert.Truef(t, proto.Equal(tt.expected, resp), "expected %v, got %v", tt.expected, resp)
  4158  		})
  4159  	}
  4160  }
  4161  
  4162  func TestGetWorkflows(t *testing.T) {
  4163  	t.Parallel()
  4164  
  4165  	tests := []struct {
  4166  		name      string
  4167  		cfgs      []vtadmintestutil.TestClusterConfig
  4168  		req       *vtadminpb.GetWorkflowsRequest
  4169  		expected  *vtadminpb.GetWorkflowsResponse
  4170  		shouldErr bool
  4171  	}{
  4172  		{
  4173  			name: "success",
  4174  			cfgs: []vtadmintestutil.TestClusterConfig{
  4175  				{
  4176  					Cluster: &vtadminpb.Cluster{
  4177  						Id:   "c1",
  4178  						Name: "cluster1",
  4179  					},
  4180  					VtctldClient: &fakevtctldclient.VtctldClient{
  4181  						GetKeyspacesResults: &struct {
  4182  							Keyspaces []*vtctldatapb.Keyspace
  4183  							Error     error
  4184  						}{
  4185  							Keyspaces: []*vtctldatapb.Keyspace{
  4186  								{
  4187  									Name: "testkeyspace",
  4188  								},
  4189  							},
  4190  						},
  4191  						GetWorkflowsResults: map[string]struct {
  4192  							Response *vtctldatapb.GetWorkflowsResponse
  4193  							Error    error
  4194  						}{
  4195  							"testkeyspace": {
  4196  								Response: &vtctldatapb.GetWorkflowsResponse{
  4197  									Workflows: []*vtctldatapb.Workflow{
  4198  										{
  4199  											Name: "workflow1",
  4200  										},
  4201  										{
  4202  											Name: "workflow2",
  4203  										},
  4204  									},
  4205  								},
  4206  							},
  4207  						},
  4208  					},
  4209  				},
  4210  				{
  4211  					Cluster: &vtadminpb.Cluster{
  4212  						Id:   "c2",
  4213  						Name: "cluster2",
  4214  					},
  4215  					VtctldClient: &fakevtctldclient.VtctldClient{
  4216  						GetKeyspacesResults: &struct {
  4217  							Keyspaces []*vtctldatapb.Keyspace
  4218  							Error     error
  4219  						}{
  4220  							Keyspaces: []*vtctldatapb.Keyspace{
  4221  								{
  4222  									Name: "otherkeyspace",
  4223  								},
  4224  							},
  4225  						},
  4226  						GetWorkflowsResults: map[string]struct {
  4227  							Response *vtctldatapb.GetWorkflowsResponse
  4228  							Error    error
  4229  						}{
  4230  							"otherkeyspace": {
  4231  								Response: &vtctldatapb.GetWorkflowsResponse{
  4232  									Workflows: []*vtctldatapb.Workflow{
  4233  										{
  4234  											Name: "workflow1",
  4235  										},
  4236  									},
  4237  								},
  4238  							},
  4239  						},
  4240  					},
  4241  				},
  4242  			},
  4243  			req: &vtadminpb.GetWorkflowsRequest{},
  4244  			expected: &vtadminpb.GetWorkflowsResponse{
  4245  				WorkflowsByCluster: map[string]*vtadminpb.ClusterWorkflows{
  4246  					"c1": {
  4247  						Workflows: []*vtadminpb.Workflow{
  4248  							{
  4249  								Cluster: &vtadminpb.Cluster{
  4250  									Id:   "c1",
  4251  									Name: "cluster1",
  4252  								},
  4253  								Keyspace: "testkeyspace",
  4254  								Workflow: &vtctldatapb.Workflow{
  4255  									Name: "workflow1",
  4256  								},
  4257  							},
  4258  							{
  4259  								Cluster: &vtadminpb.Cluster{
  4260  									Id:   "c1",
  4261  									Name: "cluster1",
  4262  								},
  4263  								Keyspace: "testkeyspace",
  4264  								Workflow: &vtctldatapb.Workflow{
  4265  									Name: "workflow2",
  4266  								},
  4267  							},
  4268  						},
  4269  					},
  4270  					"c2": {
  4271  						Workflows: []*vtadminpb.Workflow{
  4272  							{
  4273  								Cluster: &vtadminpb.Cluster{
  4274  									Id:   "c2",
  4275  									Name: "cluster2",
  4276  								},
  4277  								Keyspace: "otherkeyspace",
  4278  								Workflow: &vtctldatapb.Workflow{
  4279  									Name: "workflow1",
  4280  								},
  4281  							},
  4282  						},
  4283  					},
  4284  				},
  4285  			},
  4286  			shouldErr: false,
  4287  		},
  4288  		{
  4289  			name: "one cluster has partial error then request succeeds",
  4290  			cfgs: []vtadmintestutil.TestClusterConfig{
  4291  				{
  4292  					Cluster: &vtadminpb.Cluster{
  4293  						Id:   "c1",
  4294  						Name: "cluster1",
  4295  					},
  4296  					VtctldClient: &fakevtctldclient.VtctldClient{
  4297  						GetKeyspacesResults: &struct {
  4298  							Keyspaces []*vtctldatapb.Keyspace
  4299  							Error     error
  4300  						}{
  4301  							Keyspaces: []*vtctldatapb.Keyspace{
  4302  								{
  4303  									Name: "testkeyspace",
  4304  								},
  4305  							},
  4306  						},
  4307  						GetWorkflowsResults: map[string]struct {
  4308  							Response *vtctldatapb.GetWorkflowsResponse
  4309  							Error    error
  4310  						}{
  4311  							"testkeyspace": {
  4312  								Response: &vtctldatapb.GetWorkflowsResponse{
  4313  									Workflows: []*vtctldatapb.Workflow{
  4314  										{
  4315  											Name: "workflow1",
  4316  										},
  4317  										{
  4318  											Name: "workflow2",
  4319  										},
  4320  									},
  4321  								},
  4322  							},
  4323  						},
  4324  					},
  4325  				},
  4326  				{
  4327  					Cluster: &vtadminpb.Cluster{
  4328  						Id:   "c2",
  4329  						Name: "cluster2",
  4330  					},
  4331  					VtctldClient: &fakevtctldclient.VtctldClient{
  4332  						GetKeyspacesResults: &struct {
  4333  							Keyspaces []*vtctldatapb.Keyspace
  4334  							Error     error
  4335  						}{
  4336  							Keyspaces: []*vtctldatapb.Keyspace{
  4337  								{
  4338  									Name: "otherkeyspace",
  4339  								},
  4340  								{
  4341  									Name: "badkeyspace",
  4342  								},
  4343  							},
  4344  						},
  4345  						GetWorkflowsResults: map[string]struct {
  4346  							Response *vtctldatapb.GetWorkflowsResponse
  4347  							Error    error
  4348  						}{
  4349  							"otherkeyspace": {
  4350  								Response: &vtctldatapb.GetWorkflowsResponse{
  4351  									Workflows: []*vtctldatapb.Workflow{
  4352  										{
  4353  											Name: "workflow1",
  4354  										},
  4355  									},
  4356  								},
  4357  							},
  4358  							"badkeyspace": {
  4359  								Error: assert.AnError,
  4360  							},
  4361  						},
  4362  					},
  4363  				},
  4364  			},
  4365  			req: &vtadminpb.GetWorkflowsRequest{},
  4366  			expected: &vtadminpb.GetWorkflowsResponse{
  4367  				WorkflowsByCluster: map[string]*vtadminpb.ClusterWorkflows{
  4368  					"c1": {
  4369  						Workflows: []*vtadminpb.Workflow{
  4370  							{
  4371  								Cluster: &vtadminpb.Cluster{
  4372  									Id:   "c1",
  4373  									Name: "cluster1",
  4374  								},
  4375  								Keyspace: "testkeyspace",
  4376  								Workflow: &vtctldatapb.Workflow{
  4377  									Name: "workflow1",
  4378  								},
  4379  							},
  4380  							{
  4381  								Cluster: &vtadminpb.Cluster{
  4382  									Id:   "c1",
  4383  									Name: "cluster1",
  4384  								},
  4385  								Keyspace: "testkeyspace",
  4386  								Workflow: &vtctldatapb.Workflow{
  4387  									Name: "workflow2",
  4388  								},
  4389  							},
  4390  						},
  4391  						Warnings: []string{},
  4392  					},
  4393  					"c2": {
  4394  						Workflows: []*vtadminpb.Workflow{
  4395  							{
  4396  								Cluster: &vtadminpb.Cluster{
  4397  									Id:   "c2",
  4398  									Name: "cluster2",
  4399  								},
  4400  								Keyspace: "otherkeyspace",
  4401  								Workflow: &vtctldatapb.Workflow{
  4402  									Name: "workflow1",
  4403  								},
  4404  							},
  4405  						},
  4406  						Warnings: []string{"some warning about badkeyspace"},
  4407  					},
  4408  				},
  4409  			},
  4410  			shouldErr: false,
  4411  		},
  4412  		{
  4413  			name: "IgnoreKeyspaces applies across clusters",
  4414  			cfgs: []vtadmintestutil.TestClusterConfig{
  4415  				{
  4416  					Cluster: &vtadminpb.Cluster{
  4417  						Id:   "c1",
  4418  						Name: "cluster1",
  4419  					},
  4420  					VtctldClient: &fakevtctldclient.VtctldClient{
  4421  						GetKeyspacesResults: &struct {
  4422  							Keyspaces []*vtctldatapb.Keyspace
  4423  							Error     error
  4424  						}{
  4425  							Keyspaces: []*vtctldatapb.Keyspace{
  4426  								{
  4427  									Name: "testkeyspace",
  4428  								},
  4429  							},
  4430  						},
  4431  						GetWorkflowsResults: map[string]struct {
  4432  							Response *vtctldatapb.GetWorkflowsResponse
  4433  							Error    error
  4434  						}{
  4435  							"testkeyspace": {
  4436  								Response: &vtctldatapb.GetWorkflowsResponse{
  4437  									Workflows: []*vtctldatapb.Workflow{
  4438  										{
  4439  											Name: "workflow1",
  4440  										},
  4441  										{
  4442  											Name: "workflow2",
  4443  										},
  4444  									},
  4445  								},
  4446  							},
  4447  						},
  4448  					},
  4449  				},
  4450  				{
  4451  					Cluster: &vtadminpb.Cluster{
  4452  						Id:   "c2",
  4453  						Name: "cluster2",
  4454  					},
  4455  					VtctldClient: &fakevtctldclient.VtctldClient{
  4456  						GetKeyspacesResults: &struct {
  4457  							Keyspaces []*vtctldatapb.Keyspace
  4458  							Error     error
  4459  						}{
  4460  							Keyspaces: []*vtctldatapb.Keyspace{
  4461  								{
  4462  									Name: "testkeyspace",
  4463  								},
  4464  								{
  4465  									Name: "otherkeyspace",
  4466  								},
  4467  							},
  4468  						},
  4469  						GetWorkflowsResults: map[string]struct {
  4470  							Response *vtctldatapb.GetWorkflowsResponse
  4471  							Error    error
  4472  						}{
  4473  							"testkeyspace": {
  4474  								Response: &vtctldatapb.GetWorkflowsResponse{
  4475  									Workflows: []*vtctldatapb.Workflow{
  4476  										{
  4477  											Name: "workflow1",
  4478  										},
  4479  									},
  4480  								},
  4481  							},
  4482  							"otherkeyspace": {
  4483  								Response: &vtctldatapb.GetWorkflowsResponse{
  4484  									Workflows: []*vtctldatapb.Workflow{
  4485  										{
  4486  											Name: "workflow1",
  4487  										},
  4488  									},
  4489  								},
  4490  							},
  4491  						},
  4492  					},
  4493  				},
  4494  			},
  4495  			req: &vtadminpb.GetWorkflowsRequest{
  4496  				IgnoreKeyspaces: []string{"testkeyspace"},
  4497  			},
  4498  			expected: &vtadminpb.GetWorkflowsResponse{
  4499  				WorkflowsByCluster: map[string]*vtadminpb.ClusterWorkflows{
  4500  					"c1": {},
  4501  					"c2": {
  4502  						Workflows: []*vtadminpb.Workflow{
  4503  							{
  4504  								Cluster: &vtadminpb.Cluster{
  4505  									Id:   "c2",
  4506  									Name: "cluster2",
  4507  								},
  4508  								Keyspace: "otherkeyspace",
  4509  								Workflow: &vtctldatapb.Workflow{
  4510  									Name: "workflow1",
  4511  								},
  4512  							},
  4513  						},
  4514  					},
  4515  				},
  4516  			},
  4517  			shouldErr: false,
  4518  		},
  4519  		{
  4520  			name: "one cluster has fatal error, request fails",
  4521  			cfgs: []vtadmintestutil.TestClusterConfig{
  4522  				{
  4523  					Cluster: &vtadminpb.Cluster{
  4524  						Id:   "c1",
  4525  						Name: "cluster1",
  4526  					},
  4527  					VtctldClient: &fakevtctldclient.VtctldClient{
  4528  						GetKeyspacesResults: &struct {
  4529  							Keyspaces []*vtctldatapb.Keyspace
  4530  							Error     error
  4531  						}{
  4532  							Keyspaces: []*vtctldatapb.Keyspace{
  4533  								{
  4534  									Name: "testkeyspace",
  4535  								},
  4536  							},
  4537  						},
  4538  						GetWorkflowsResults: map[string]struct {
  4539  							Response *vtctldatapb.GetWorkflowsResponse
  4540  							Error    error
  4541  						}{
  4542  							"testkeyspace": {
  4543  								Response: &vtctldatapb.GetWorkflowsResponse{
  4544  									Workflows: []*vtctldatapb.Workflow{
  4545  										{
  4546  											Name: "workflow1",
  4547  										},
  4548  										{
  4549  											Name: "workflow2",
  4550  										},
  4551  									},
  4552  								},
  4553  							},
  4554  						},
  4555  					},
  4556  				},
  4557  				{
  4558  					Cluster: &vtadminpb.Cluster{
  4559  						Id:   "c2",
  4560  						Name: "cluster2",
  4561  					},
  4562  					VtctldClient: &fakevtctldclient.VtctldClient{
  4563  						GetKeyspacesResults: &struct {
  4564  							Keyspaces []*vtctldatapb.Keyspace
  4565  							Error     error
  4566  						}{
  4567  							Error: assert.AnError, // GetKeyspaces is a fatal error
  4568  						},
  4569  					},
  4570  				},
  4571  			},
  4572  			req:       &vtadminpb.GetWorkflowsRequest{},
  4573  			expected:  nil,
  4574  			shouldErr: true,
  4575  		},
  4576  	}
  4577  
  4578  	ctx := context.Background()
  4579  
  4580  	for _, tt := range tests {
  4581  		tt := tt
  4582  
  4583  		t.Run(tt.name, func(t *testing.T) {
  4584  			t.Parallel()
  4585  
  4586  			api := NewAPI(vtadmintestutil.BuildClusters(t, tt.cfgs...), Options{})
  4587  
  4588  			resp, err := api.GetWorkflows(ctx, tt.req)
  4589  			if tt.shouldErr {
  4590  				assert.Error(t, err)
  4591  
  4592  				return
  4593  			}
  4594  
  4595  			assert.NoError(t, err)
  4596  			require.NotNil(t, resp)
  4597  
  4598  			vtadmintestutil.AssertGetWorkflowsResponsesEqual(t, tt.expected, resp)
  4599  		})
  4600  	}
  4601  }
  4602  
  4603  func TestVTExplain(t *testing.T) {
  4604  	t.Parallel()
  4605  
  4606  	tests := []struct {
  4607  		name          string
  4608  		keyspaces     []*vtctldatapb.Keyspace
  4609  		shards        []*vtctldatapb.Shard
  4610  		srvVSchema    *vschemapb.SrvVSchema
  4611  		tabletSchemas map[string]*tabletmanagerdatapb.SchemaDefinition
  4612  		tablets       []*vtadminpb.Tablet
  4613  		req           *vtadminpb.VTExplainRequest
  4614  		expectedError error
  4615  	}{
  4616  		{
  4617  			name: "runs VTExplain given a valid request in a valid topology",
  4618  			keyspaces: []*vtctldatapb.Keyspace{
  4619  				{
  4620  					Name:     "commerce",
  4621  					Keyspace: &topodatapb.Keyspace{},
  4622  				},
  4623  			},
  4624  			shards: []*vtctldatapb.Shard{
  4625  				{
  4626  					Name:     "-",
  4627  					Keyspace: "commerce",
  4628  				},
  4629  			},
  4630  			srvVSchema: &vschemapb.SrvVSchema{
  4631  				Keyspaces: map[string]*vschemapb.Keyspace{
  4632  					"commerce": {
  4633  						Sharded: false,
  4634  						Tables: map[string]*vschemapb.Table{
  4635  							"customers": {},
  4636  						},
  4637  					},
  4638  				},
  4639  				RoutingRules: &vschemapb.RoutingRules{
  4640  					Rules: []*vschemapb.RoutingRule{},
  4641  				},
  4642  			},
  4643  			tabletSchemas: map[string]*tabletmanagerdatapb.SchemaDefinition{
  4644  				"c0_cell1-0000000100": {
  4645  					DatabaseSchema: "CREATE DATABASE commerce",
  4646  					TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  4647  						{
  4648  							Name:       "t1",
  4649  							Schema:     `CREATE TABLE customers (id int(11) not null,PRIMARY KEY (id));`,
  4650  							Type:       "BASE",
  4651  							Columns:    []string{"id"},
  4652  							DataLength: 100,
  4653  							RowCount:   50,
  4654  							Fields: []*querypb.Field{
  4655  								{
  4656  									Name: "id",
  4657  									Type: querypb.Type_INT32,
  4658  								},
  4659  							},
  4660  						},
  4661  					},
  4662  				},
  4663  			},
  4664  			tablets: []*vtadminpb.Tablet{
  4665  				{
  4666  					Cluster: &vtadminpb.Cluster{
  4667  						Id:   "c0",
  4668  						Name: "cluster0",
  4669  					},
  4670  					State: vtadminpb.Tablet_SERVING,
  4671  					Tablet: &topodatapb.Tablet{
  4672  						Alias: &topodatapb.TabletAlias{
  4673  							Uid:  100,
  4674  							Cell: "c0_cell1",
  4675  						},
  4676  						Hostname: "tablet-cell1-a",
  4677  						Keyspace: "commerce",
  4678  						Shard:    "-",
  4679  						Type:     topodatapb.TabletType_REPLICA,
  4680  					},
  4681  				},
  4682  			},
  4683  			req: &vtadminpb.VTExplainRequest{
  4684  				Cluster:  "c0",
  4685  				Keyspace: "commerce",
  4686  				Sql:      "select * from customers",
  4687  			},
  4688  		},
  4689  		{
  4690  			name: "returns an error if no appropriate tablet found in keyspace",
  4691  			keyspaces: []*vtctldatapb.Keyspace{
  4692  				{
  4693  					Name:     "commerce",
  4694  					Keyspace: &topodatapb.Keyspace{},
  4695  				},
  4696  			},
  4697  			shards: []*vtctldatapb.Shard{
  4698  				{
  4699  					Name:     "-",
  4700  					Keyspace: "commerce",
  4701  				},
  4702  			},
  4703  			srvVSchema: &vschemapb.SrvVSchema{
  4704  				Keyspaces: map[string]*vschemapb.Keyspace{
  4705  					"commerce": {
  4706  						Sharded: false,
  4707  						Tables: map[string]*vschemapb.Table{
  4708  							"customers": {},
  4709  						},
  4710  					},
  4711  				},
  4712  				RoutingRules: &vschemapb.RoutingRules{
  4713  					Rules: []*vschemapb.RoutingRule{},
  4714  				},
  4715  			},
  4716  			tabletSchemas: map[string]*tabletmanagerdatapb.SchemaDefinition{
  4717  				"c0_cell1-0000000102": {
  4718  					DatabaseSchema: "CREATE DATABASE commerce",
  4719  					TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  4720  						{
  4721  							Name:       "t1",
  4722  							Schema:     `CREATE TABLE customers (id int(11) not null,PRIMARY KEY (id));`,
  4723  							Type:       "BASE",
  4724  							Columns:    []string{"id"},
  4725  							DataLength: 100,
  4726  							RowCount:   50,
  4727  							Fields: []*querypb.Field{
  4728  								{
  4729  									Name: "id",
  4730  									Type: querypb.Type_INT32,
  4731  								},
  4732  							},
  4733  						},
  4734  					},
  4735  				},
  4736  			},
  4737  			tablets: []*vtadminpb.Tablet{
  4738  				{
  4739  					Cluster: &vtadminpb.Cluster{
  4740  						Id:   "c0",
  4741  						Name: "cluster0",
  4742  					},
  4743  					State: vtadminpb.Tablet_SERVING,
  4744  					Tablet: &topodatapb.Tablet{
  4745  						Alias: &topodatapb.TabletAlias{
  4746  							Uid:  100,
  4747  							Cell: "c0_cell1",
  4748  						},
  4749  						Hostname: "tablet-cell1-a",
  4750  						Keyspace: "commerce",
  4751  						Shard:    "-",
  4752  						Type:     topodatapb.TabletType_PRIMARY,
  4753  					},
  4754  				},
  4755  				{
  4756  					Cluster: &vtadminpb.Cluster{
  4757  						Id:   "c0",
  4758  						Name: "cluster0",
  4759  					},
  4760  					State: vtadminpb.Tablet_SERVING,
  4761  					Tablet: &topodatapb.Tablet{
  4762  						Alias: &topodatapb.TabletAlias{
  4763  							Uid:  101,
  4764  							Cell: "c0_cell1",
  4765  						},
  4766  						Hostname: "tablet-cell1-b",
  4767  						Keyspace: "commerce",
  4768  						Shard:    "-",
  4769  						Type:     topodatapb.TabletType_DRAINED,
  4770  					},
  4771  				},
  4772  				{
  4773  					Cluster: &vtadminpb.Cluster{
  4774  						Id:   "c0",
  4775  						Name: "cluster0",
  4776  					},
  4777  					State: vtadminpb.Tablet_NOT_SERVING,
  4778  					Tablet: &topodatapb.Tablet{
  4779  						Alias: &topodatapb.TabletAlias{
  4780  							Uid:  102,
  4781  							Cell: "c0_cell1",
  4782  						},
  4783  						Hostname: "tablet-cell1-c",
  4784  						Keyspace: "commerce",
  4785  						Shard:    "-",
  4786  						Type:     topodatapb.TabletType_REPLICA,
  4787  					},
  4788  				},
  4789  			},
  4790  			req: &vtadminpb.VTExplainRequest{
  4791  				Cluster:  "c0",
  4792  				Keyspace: "commerce",
  4793  				Sql:      "select * from customers",
  4794  			},
  4795  			expectedError: vtadminerrors.ErrNoTablet,
  4796  		},
  4797  		{
  4798  			name: "returns an error if cluster unspecified in request",
  4799  			req: &vtadminpb.VTExplainRequest{
  4800  				Keyspace: "commerce",
  4801  				Sql:      "select * from customers",
  4802  			},
  4803  			expectedError: vtadminerrors.ErrInvalidRequest,
  4804  		},
  4805  		{
  4806  			name: "returns an error if keyspace unspecified in request",
  4807  			req: &vtadminpb.VTExplainRequest{
  4808  				Cluster: "c0",
  4809  				Sql:     "select * from customers",
  4810  			},
  4811  			expectedError: vtadminerrors.ErrInvalidRequest,
  4812  		},
  4813  		{
  4814  			name: "returns an error if SQL unspecified in request",
  4815  			req: &vtadminpb.VTExplainRequest{
  4816  				Cluster:  "c0",
  4817  				Keyspace: "commerce",
  4818  			},
  4819  			expectedError: vtadminerrors.ErrInvalidRequest,
  4820  		},
  4821  	}
  4822  
  4823  	ctx := context.Background()
  4824  
  4825  	for _, tt := range tests {
  4826  		tt := tt
  4827  
  4828  		t.Run(tt.name, func(t *testing.T) {
  4829  			t.Parallel()
  4830  
  4831  			toposerver := memorytopo.NewServer("c0_cell1")
  4832  
  4833  			tmc := testutil.TabletManagerClient{
  4834  				GetSchemaResults: map[string]struct {
  4835  					Schema *tabletmanagerdatapb.SchemaDefinition
  4836  					Error  error
  4837  				}{},
  4838  			}
  4839  
  4840  			vtctldserver := testutil.NewVtctldServerWithTabletManagerClient(t, toposerver, &tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  4841  				return grpcvtctldserver.NewVtctldServer(ts)
  4842  			})
  4843  
  4844  			testutil.WithTestServer(t, vtctldserver, func(t *testing.T, vtctldClient vtctldclient.VtctldClient) {
  4845  				if tt.srvVSchema != nil {
  4846  					err := toposerver.UpdateSrvVSchema(ctx, "c0_cell1", tt.srvVSchema)
  4847  					require.NoError(t, err)
  4848  				}
  4849  				testutil.AddKeyspaces(ctx, t, toposerver, tt.keyspaces...)
  4850  				testutil.AddShards(ctx, t, toposerver, tt.shards...)
  4851  
  4852  				for _, tablet := range tt.tablets {
  4853  					testutil.AddTablet(ctx, t, toposerver, tablet.Tablet, nil)
  4854  
  4855  					// Adds each SchemaDefinition to the fake TabletManagerClient, or nil
  4856  					// if there are no schemas for that tablet. (All tablet aliases must
  4857  					// exist in the map. Otherwise, TabletManagerClient will return an error when
  4858  					// looking up the schema with tablet alias that doesn't exist.)
  4859  					alias := topoproto.TabletAliasString(tablet.Tablet.Alias)
  4860  					tmc.GetSchemaResults[alias] = struct {
  4861  						Schema *tabletmanagerdatapb.SchemaDefinition
  4862  						Error  error
  4863  					}{
  4864  						Schema: tt.tabletSchemas[alias],
  4865  						Error:  nil,
  4866  					}
  4867  				}
  4868  
  4869  				clusters := []*cluster.Cluster{
  4870  					vtadmintestutil.BuildCluster(t, vtadmintestutil.TestClusterConfig{
  4871  						Cluster: &vtadminpb.Cluster{
  4872  							Id:   "c0",
  4873  							Name: "cluster0",
  4874  						},
  4875  						VtctldClient: vtctldClient,
  4876  						Tablets:      tt.tablets,
  4877  					}),
  4878  				}
  4879  
  4880  				api := NewAPI(clusters, Options{})
  4881  				resp, err := api.VTExplain(ctx, tt.req)
  4882  
  4883  				if tt.expectedError != nil {
  4884  					assert.True(t, errors.Is(err, tt.expectedError), "expected error type %w does not match actual error type %w", err, tt.expectedError)
  4885  				} else {
  4886  					require.NoError(t, err)
  4887  
  4888  					// We don't particularly care to test the contents of the VTExplain response,
  4889  					// just that it exists.
  4890  					assert.NotEmpty(t, resp.Response)
  4891  				}
  4892  			})
  4893  		})
  4894  	}
  4895  }
  4896  
  4897  type ServeHTTPVtctldResponse struct {
  4898  	Result ServeHTTPVtctldResult `json:"result"`
  4899  	Ok     bool                  `json:"ok"`
  4900  }
  4901  
  4902  type ServeHTTPVtctldResult struct {
  4903  	Vtctlds []*vtadminpb.Vtctld `json:"vtctlds"`
  4904  }
  4905  
  4906  type ServeHTTPResponse struct {
  4907  	Result ServeHTTPResult `json:"result"`
  4908  	Ok     bool            `json:"ok"`
  4909  }
  4910  
  4911  type ServeHTTPResult struct {
  4912  	Clusters []*vtadminpb.Cluster `json:"clusters"`
  4913  }
  4914  
  4915  func TestServeHTTP(t *testing.T) {
  4916  	t.Parallel()
  4917  
  4918  	testCluster, _ := cluster.Config{
  4919  		ID:            "dynamiccluster1",
  4920  		Name:          "dynamiccluster1",
  4921  		DiscoveryImpl: "dynamic",
  4922  		DiscoveryFlagsByImpl: cluster.FlagsByImpl{
  4923  			"dynamic": {
  4924  				"discovery": "{\"vtctlds\": [{\"host\":{\"fqdn\": \"localhost:15000\", \"hostname\": \"localhost:15999\"}}], \"vtgates\": [{\"host\": {\"hostname\": \"localhost:15991\"}}]}",
  4925  			},
  4926  		},
  4927  	}.Cluster(context.Background())
  4928  
  4929  	tests := []struct {
  4930  		name                  string
  4931  		cookie                string
  4932  		enableDynamicClusters bool
  4933  		testClusterVtctld     string
  4934  		clusters              []*cluster.Cluster
  4935  		expected              []*vtadminpb.Cluster
  4936  		expectedVtctlds       []*vtadminpb.Vtctld
  4937  		repeat                bool
  4938  	}{
  4939  		{
  4940  			name:                  "multiple clusters without dynamic clusters",
  4941  			enableDynamicClusters: false,
  4942  			clusters: []*cluster.Cluster{
  4943  				{
  4944  					ID:        "c1",
  4945  					Name:      "cluster1",
  4946  					Discovery: fakediscovery.New(),
  4947  				},
  4948  				{
  4949  					ID:        "c2",
  4950  					Name:      "cluster2",
  4951  					Discovery: fakediscovery.New(),
  4952  				},
  4953  			},
  4954  			expected: []*vtadminpb.Cluster{
  4955  				{
  4956  					Id:   "c1",
  4957  					Name: "cluster1",
  4958  				},
  4959  				{
  4960  					Id:   "c2",
  4961  					Name: "cluster2",
  4962  				},
  4963  			},
  4964  		},
  4965  		{
  4966  			name:                  "no clusters without dynamic clusters",
  4967  			enableDynamicClusters: false,
  4968  			clusters:              []*cluster.Cluster{},
  4969  			expected:              []*vtadminpb.Cluster{},
  4970  		},
  4971  		{
  4972  			name:                  "multiple clusters with dynamic clusters",
  4973  			enableDynamicClusters: true,
  4974  			cookie:                `{"id": "dynamiccluster1", "name": "dynamiccluster1", "discovery": "dynamic", "discovery-dynamic-discovery": "{\"vtctlds\": [{\"host\":{\"fqdn\": \"localhost:15000\", \"hostname\": \"localhost:15999\"}}], \"vtgates\": [{\"host\": {\"hostname\": \"localhost:15991\"}}]}"}`,
  4975  			clusters: []*cluster.Cluster{
  4976  				{
  4977  					ID:        "c1",
  4978  					Name:      "cluster1",
  4979  					Discovery: fakediscovery.New(),
  4980  				},
  4981  				{
  4982  					ID:        "c2",
  4983  					Name:      "cluster2",
  4984  					Discovery: fakediscovery.New(),
  4985  				},
  4986  			},
  4987  			expected: []*vtadminpb.Cluster{
  4988  				{
  4989  					Id:   "dynamiccluster1",
  4990  					Name: "dynamiccluster1",
  4991  				},
  4992  			},
  4993  		},
  4994  		{
  4995  			name:                  "dynamic clusters - cluster is updated when values change",
  4996  			enableDynamicClusters: true,
  4997  			cookie:                `{"id": "dynamiccluster1", "name": "dynamiccluster1", "discovery": "dynamic", "discovery-dynamic-discovery": "{\"vtctlds\": [{\"host\":{\"fqdn\": \"localhost:15001\", \"hostname\": \"localhost:15998\"}}], \"vtgates\": [{\"host\": {\"hostname\": \"localhost:15991\"}}]}"}`,
  4998  			clusters: []*cluster.Cluster{
  4999  				testCluster,
  5000  			},
  5001  			expected: []*vtadminpb.Cluster{
  5002  				{
  5003  					Id:   "dynamiccluster1",
  5004  					Name: "dynamiccluster1",
  5005  				},
  5006  			},
  5007  			testClusterVtctld: "dynamiccluster1",
  5008  			expectedVtctlds: []*vtadminpb.Vtctld{
  5009  				{
  5010  					Hostname: "localhost:15998",
  5011  					Cluster:  &vtadminpb.Cluster{Id: "dynamiccluster1", Name: "dynamiccluster1"},
  5012  					FQDN:     "localhost:15001",
  5013  				},
  5014  			},
  5015  		},
  5016  		{
  5017  			name:                  "multiple clusters with dynamic clusters - no duplicates",
  5018  			enableDynamicClusters: true,
  5019  			cookie:                `{"id": "dynamiccluster1", "name": "dynamiccluster1", "discovery": "dynamic", "discovery-dynamic-discovery": "{\"vtctlds\": [{\"host\":{\"fqdn\": \"localhost:15000\", \"hostname\": \"localhost:15999\"}}], \"vtgates\": [{\"host\": {\"hostname\": \"localhost:15991\"}}]}"}`,
  5020  			clusters: []*cluster.Cluster{
  5021  				{
  5022  					ID:        "c1",
  5023  					Name:      "cluster1",
  5024  					Discovery: fakediscovery.New(),
  5025  				},
  5026  				{
  5027  					ID:        "c2",
  5028  					Name:      "cluster2",
  5029  					Discovery: fakediscovery.New(),
  5030  				},
  5031  			},
  5032  			expected: []*vtadminpb.Cluster{
  5033  				{
  5034  					Id:   "dynamiccluster1",
  5035  					Name: "dynamiccluster1",
  5036  				},
  5037  			},
  5038  			repeat: true,
  5039  		},
  5040  		{
  5041  			name:                  "multiple clusters with invalid json cookie and dynamic clusters",
  5042  			enableDynamicClusters: true,
  5043  			cookie:                `{"id "dynamiccluster1", "name": "dynamiccluster1", "discovery": "dynamic", "discovery-dynamic-discovery": "{\"vtctlds\": [{\"host\":{\"fqdn\": \"localhost:15000\", \"hostname\": \"localhost:15999\"}}], \"vtgates\": [{\"host\": {\"hostname\": \"localhost:15991\"}}]}"}`,
  5044  			clusters: []*cluster.Cluster{
  5045  				{
  5046  					ID:        "c1",
  5047  					Name:      "cluster1",
  5048  					Discovery: fakediscovery.New(),
  5049  				},
  5050  				{
  5051  					ID:        "c2",
  5052  					Name:      "cluster2",
  5053  					Discovery: fakediscovery.New(),
  5054  				},
  5055  			},
  5056  			expected: []*vtadminpb.Cluster{
  5057  				{
  5058  					Id:   "c1",
  5059  					Name: "cluster1",
  5060  				},
  5061  				{
  5062  					Id:   "c2",
  5063  					Name: "cluster2",
  5064  				},
  5065  			},
  5066  		},
  5067  		{
  5068  			name:                  "no clusters with dynamic clusters",
  5069  			enableDynamicClusters: true,
  5070  			clusters:              []*cluster.Cluster{},
  5071  			expected:              []*vtadminpb.Cluster{},
  5072  		},
  5073  	}
  5074  
  5075  	for _, tt := range tests {
  5076  		tt := tt
  5077  
  5078  		t.Run(tt.name, func(t *testing.T) {
  5079  			t.Parallel()
  5080  
  5081  			api := NewAPI(tt.clusters, Options{EnableDynamicClusters: tt.enableDynamicClusters})
  5082  
  5083  			// Copy the Cookie over to a new Request
  5084  			req := httptest.NewRequest(http.MethodGet, "/api/clusters", nil)
  5085  			req.AddCookie(&http.Cookie{Name: "cluster", Value: url.QueryEscape(base64.StdEncoding.EncodeToString([]byte(tt.cookie)))})
  5086  
  5087  			w := httptest.NewRecorder()
  5088  
  5089  			api.ServeHTTP(w, req)
  5090  
  5091  			if tt.repeat {
  5092  				api.ServeHTTP(w, req)
  5093  			}
  5094  
  5095  			res := w.Result()
  5096  			defer res.Body.Close()
  5097  
  5098  			dec := json.NewDecoder(res.Body)
  5099  			dec.DisallowUnknownFields()
  5100  			var clustersResponse ServeHTTPResponse
  5101  			err := dec.Decode(&clustersResponse)
  5102  
  5103  			assert.NoError(t, err)
  5104  			assert.ElementsMatch(t, tt.expected, clustersResponse.Result.Clusters)
  5105  
  5106  			if tt.testClusterVtctld != "" {
  5107  				req := httptest.NewRequest(http.MethodGet, "/api/vtctlds?cluster="+tt.testClusterVtctld, nil)
  5108  				req.AddCookie(&http.Cookie{Name: "cluster", Value: url.QueryEscape(base64.StdEncoding.EncodeToString([]byte(tt.cookie)))})
  5109  
  5110  				w := httptest.NewRecorder()
  5111  
  5112  				api.ServeHTTP(w, req)
  5113  
  5114  				if tt.repeat {
  5115  					api.ServeHTTP(w, req)
  5116  				}
  5117  
  5118  				res := w.Result()
  5119  				defer res.Body.Close()
  5120  
  5121  				dec := json.NewDecoder(res.Body)
  5122  				dec.DisallowUnknownFields()
  5123  				var vtctldsResponse ServeHTTPVtctldResponse
  5124  				err := dec.Decode(&vtctldsResponse)
  5125  
  5126  				assert.NoError(t, err)
  5127  				assert.ElementsMatch(t, tt.expectedVtctlds, vtctldsResponse.Result.Vtctlds)
  5128  			}
  5129  		})
  5130  	}
  5131  }
  5132  
  5133  func init() {
  5134  	// For tests that don't actually care about mocking the tmclient (i.e. they
  5135  	// call grpcvtctldserver.NewVtctldServer to initialize the unit under test),
  5136  	// this needs to be set.
  5137  	//
  5138  	// Tests that do care about the tmclient should use
  5139  	// testutil.NewVtctldServerWithTabletManagerClient to initialize their
  5140  	// VtctldServer.
  5141  	tmclienttest.SetProtocol("go.vt.vtadmin", "vtadmin.test")
  5142  	tmclient.RegisterTabletManagerClientFactory("vtadmin.test", func() tmclient.TabletManagerClient {
  5143  		return nil
  5144  	})
  5145  }
  5146  
  5147  //go:generate -command authztestgen go run ./testutil/authztestgen
  5148  //go:generate authztestgen -c ./testutil/authztestgen/config.json -o ./api_authz_test.go