vitess.io/vitess@v0.16.2/go/vt/vtadmin/cluster/cluster_test.go (about)

     1  /*
     2  Copyright 2021 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package cluster_test
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"sort"
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/require"
    30  	"google.golang.org/protobuf/proto"
    31  	"k8s.io/apimachinery/pkg/util/sets"
    32  
    33  	"vitess.io/vitess/go/mysql"
    34  	"vitess.io/vitess/go/protoutil"
    35  	"vitess.io/vitess/go/test/utils"
    36  	"vitess.io/vitess/go/vt/topo"
    37  	"vitess.io/vitess/go/vt/vtadmin/cluster"
    38  	vtadminerrors "vitess.io/vitess/go/vt/vtadmin/errors"
    39  	"vitess.io/vitess/go/vt/vtadmin/testutil"
    40  	"vitess.io/vitess/go/vt/vtadmin/vtctldclient/fakevtctldclient"
    41  	"vitess.io/vitess/go/vt/vtctl/vtctldclient"
    42  
    43  	replicationdatapb "vitess.io/vitess/go/vt/proto/replicationdata"
    44  	tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata"
    45  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    46  	vschemapb "vitess.io/vitess/go/vt/proto/vschema"
    47  	vtadminpb "vitess.io/vitess/go/vt/proto/vtadmin"
    48  	vtctldatapb "vitess.io/vitess/go/vt/proto/vtctldata"
    49  )
    50  
    51  func TestCreateKeyspace(t *testing.T) {
    52  	t.Parallel()
    53  
    54  	ctx := context.Background()
    55  	tests := []struct {
    56  		name      string
    57  		cfg       testutil.TestClusterConfig
    58  		req       *vtctldatapb.CreateKeyspaceRequest
    59  		expected  *vtadminpb.Keyspace
    60  		shouldErr bool
    61  	}{
    62  		{
    63  			name: "success",
    64  			cfg: testutil.TestClusterConfig{
    65  				Cluster: &vtadminpb.Cluster{
    66  					Id:   "c1",
    67  					Name: "cluster1",
    68  				},
    69  				VtctldClient: &fakevtctldclient.VtctldClient{},
    70  			},
    71  			req: &vtctldatapb.CreateKeyspaceRequest{
    72  				Name: "testkeyspace",
    73  			},
    74  			expected: &vtadminpb.Keyspace{
    75  				Cluster: &vtadminpb.Cluster{
    76  					Id:   "c1",
    77  					Name: "cluster1",
    78  				},
    79  				Keyspace: &vtctldatapb.Keyspace{
    80  					Name:     "testkeyspace",
    81  					Keyspace: &topodatapb.Keyspace{},
    82  				},
    83  				Shards: map[string]*vtctldatapb.Shard{},
    84  			},
    85  		},
    86  		{
    87  			name: "snapshot",
    88  			cfg: testutil.TestClusterConfig{
    89  				Cluster: &vtadminpb.Cluster{
    90  					Id:   "c1",
    91  					Name: "cluster1",
    92  				},
    93  				VtctldClient: &fakevtctldclient.VtctldClient{},
    94  			},
    95  			req: &vtctldatapb.CreateKeyspaceRequest{
    96  				Name:         "testkeyspace_snapshot",
    97  				Type:         topodatapb.KeyspaceType_SNAPSHOT,
    98  				BaseKeyspace: "testkeyspace",
    99  				SnapshotTime: protoutil.TimeToProto(time.Date(2006, time.January, 2, 3, 4, 5, 0, time.UTC)),
   100  			},
   101  			expected: &vtadminpb.Keyspace{
   102  				Cluster: &vtadminpb.Cluster{
   103  					Id:   "c1",
   104  					Name: "cluster1",
   105  				},
   106  				Keyspace: &vtctldatapb.Keyspace{
   107  					Name: "testkeyspace_snapshot",
   108  					Keyspace: &topodatapb.Keyspace{
   109  						KeyspaceType: topodatapb.KeyspaceType_SNAPSHOT,
   110  						BaseKeyspace: "testkeyspace",
   111  						SnapshotTime: protoutil.TimeToProto(time.Date(2006, time.January, 2, 3, 4, 5, 0, time.UTC)),
   112  					},
   113  				},
   114  				Shards: map[string]*vtctldatapb.Shard{},
   115  			},
   116  		},
   117  		{
   118  			name: "nil request",
   119  			cfg: testutil.TestClusterConfig{
   120  				Cluster: &vtadminpb.Cluster{
   121  					Id:   "c1",
   122  					Name: "cluster1",
   123  				},
   124  				VtctldClient: &fakevtctldclient.VtctldClient{},
   125  			},
   126  			req:       nil,
   127  			shouldErr: true,
   128  		},
   129  		{
   130  			name: "missing name",
   131  			cfg: testutil.TestClusterConfig{
   132  				Cluster: &vtadminpb.Cluster{
   133  					Id:   "c1",
   134  					Name: "cluster1",
   135  				},
   136  				VtctldClient: &fakevtctldclient.VtctldClient{},
   137  			},
   138  			req:       &vtctldatapb.CreateKeyspaceRequest{},
   139  			shouldErr: true,
   140  		},
   141  		{
   142  			name: "failure",
   143  			cfg: testutil.TestClusterConfig{
   144  				Cluster: &vtadminpb.Cluster{
   145  					Id:   "c1",
   146  					Name: "cluster1",
   147  				},
   148  				VtctldClient: &fakevtctldclient.VtctldClient{
   149  					CreateKeyspaceShouldErr: true,
   150  				},
   151  			},
   152  			req: &vtctldatapb.CreateKeyspaceRequest{
   153  				Name: "testkeyspace",
   154  			},
   155  			shouldErr: true,
   156  		},
   157  	}
   158  
   159  	for _, tt := range tests {
   160  		tt := tt
   161  		t.Run(tt.name, func(t *testing.T) {
   162  			t.Parallel()
   163  
   164  			cluster := testutil.BuildCluster(t, tt.cfg)
   165  
   166  			resp, err := cluster.CreateKeyspace(ctx, tt.req)
   167  			if tt.shouldErr {
   168  				assert.Error(t, err)
   169  				return
   170  			}
   171  
   172  			assert.NoError(t, err)
   173  			assert.Equal(t, tt.expected, resp)
   174  		})
   175  	}
   176  }
   177  
   178  func TestCreateShard(t *testing.T) {
   179  	t.Parallel()
   180  
   181  	type test struct {
   182  		name      string
   183  		tc        *testutil.IntegrationTestCluster
   184  		req       *vtctldatapb.CreateShardRequest
   185  		shouldErr bool
   186  		assertion func(t *testing.T, tt *test)
   187  	}
   188  	ctx := context.Background()
   189  	tests := []*test{
   190  		{
   191  			name: "ok",
   192  			tc: testutil.BuildIntegrationTestCluster(t, &vtadminpb.Cluster{
   193  				Id:   "local",
   194  				Name: "local",
   195  			}, "zone1"),
   196  			req: &vtctldatapb.CreateShardRequest{
   197  				Keyspace:      "ks1",
   198  				ShardName:     "-",
   199  				IncludeParent: true,
   200  			},
   201  			assertion: func(t *testing.T, tt *test) {
   202  				shard, err := tt.tc.Topo.GetShard(ctx, "ks1", "-")
   203  				require.NoError(t, err, "topo.GetShard(ks1/-) failed")
   204  
   205  				utils.MustMatch(t, &topodatapb.Shard{
   206  					KeyRange:         &topodatapb.KeyRange{},
   207  					IsPrimaryServing: true,
   208  				}, shard.Shard)
   209  			},
   210  		},
   211  		{
   212  			name: "nil request",
   213  			tc: testutil.BuildIntegrationTestCluster(t, &vtadminpb.Cluster{
   214  				Id:   "local",
   215  				Name: "local",
   216  			}, "zone1"),
   217  			req:       nil,
   218  			shouldErr: true,
   219  		},
   220  		{
   221  			name: "no keyspace in request",
   222  			tc: testutil.BuildIntegrationTestCluster(t, &vtadminpb.Cluster{
   223  				Id:   "local",
   224  				Name: "local",
   225  			}, "zone1"),
   226  			req: &vtctldatapb.CreateShardRequest{
   227  				Keyspace:  "",
   228  				ShardName: "-",
   229  			},
   230  			shouldErr: true,
   231  		},
   232  		{
   233  			name: "no shard name in request",
   234  			tc: testutil.BuildIntegrationTestCluster(t, &vtadminpb.Cluster{
   235  				Id:   "local",
   236  				Name: "local",
   237  			}, "zone1"),
   238  			req: &vtctldatapb.CreateShardRequest{
   239  				Keyspace:  "ks1",
   240  				ShardName: "",
   241  			},
   242  			shouldErr: true,
   243  		},
   244  		{
   245  			name: "vtctld.CreateShard fails",
   246  			tc: testutil.BuildIntegrationTestCluster(t, &vtadminpb.Cluster{
   247  				Id:   "local",
   248  				Name: "local",
   249  			}, "zone1"),
   250  			req: &vtctldatapb.CreateShardRequest{
   251  				Keyspace:  "ks1", // because IncludeParent=false and ks1 does not exist, we fail
   252  				ShardName: "-",
   253  			},
   254  			shouldErr: true,
   255  		},
   256  	}
   257  
   258  	for _, tt := range tests {
   259  		tt := tt
   260  		t.Run(tt.name, func(t *testing.T) {
   261  			_, err := tt.tc.Cluster.CreateShard(ctx, tt.req)
   262  			if tt.shouldErr {
   263  				assert.Error(t, err)
   264  			} else {
   265  				require.NoError(t, err)
   266  			}
   267  
   268  			if tt.assertion != nil {
   269  				func() {
   270  					t.Helper()
   271  					tt.assertion(t, tt)
   272  				}()
   273  			}
   274  		})
   275  	}
   276  }
   277  
   278  func TestDeleteKeyspace(t *testing.T) {
   279  	t.Parallel()
   280  
   281  	ctx := context.Background()
   282  	tests := []struct {
   283  		name      string
   284  		cfg       testutil.TestClusterConfig
   285  		req       *vtctldatapb.DeleteKeyspaceRequest
   286  		expected  *vtctldatapb.DeleteKeyspaceResponse
   287  		shouldErr bool
   288  	}{
   289  		{
   290  			name: "success",
   291  			cfg: testutil.TestClusterConfig{
   292  				Cluster: &vtadminpb.Cluster{
   293  					Id:   "c1",
   294  					Name: "cluster1",
   295  				},
   296  				VtctldClient: &fakevtctldclient.VtctldClient{},
   297  			},
   298  			req: &vtctldatapb.DeleteKeyspaceRequest{
   299  				Keyspace: "ks1",
   300  			},
   301  			expected: &vtctldatapb.DeleteKeyspaceResponse{},
   302  		},
   303  		{
   304  			name: "nil request",
   305  			cfg: testutil.TestClusterConfig{
   306  				Cluster: &vtadminpb.Cluster{
   307  					Id:   "c1",
   308  					Name: "cluster1",
   309  				},
   310  				VtctldClient: &fakevtctldclient.VtctldClient{},
   311  			},
   312  			req:       nil,
   313  			shouldErr: true,
   314  		},
   315  		{
   316  			name: "missing name",
   317  			cfg: testutil.TestClusterConfig{
   318  				Cluster: &vtadminpb.Cluster{
   319  					Id:   "c1",
   320  					Name: "cluster1",
   321  				},
   322  				VtctldClient: &fakevtctldclient.VtctldClient{},
   323  			},
   324  			req:       &vtctldatapb.DeleteKeyspaceRequest{},
   325  			shouldErr: true,
   326  		},
   327  		{
   328  			name: "failure",
   329  			cfg: testutil.TestClusterConfig{
   330  				Cluster: &vtadminpb.Cluster{
   331  					Id:   "c1",
   332  					Name: "cluster1",
   333  				},
   334  				VtctldClient: &fakevtctldclient.VtctldClient{
   335  					DeleteKeyspaceShouldErr: true,
   336  				},
   337  			},
   338  			req: &vtctldatapb.DeleteKeyspaceRequest{
   339  				Keyspace: "ks1",
   340  			},
   341  			shouldErr: true,
   342  		},
   343  	}
   344  
   345  	for _, tt := range tests {
   346  		tt := tt
   347  		t.Run(tt.name, func(t *testing.T) {
   348  			t.Parallel()
   349  
   350  			cluster := testutil.BuildCluster(t, tt.cfg)
   351  
   352  			resp, err := cluster.DeleteKeyspace(ctx, tt.req)
   353  			if tt.shouldErr {
   354  				assert.Error(t, err)
   355  				return
   356  			}
   357  
   358  			assert.NoError(t, err)
   359  			assert.Equal(t, tt.expected, resp)
   360  		})
   361  	}
   362  }
   363  
   364  func TestDeleteShards(t *testing.T) {
   365  	t.Parallel()
   366  
   367  	type test struct {
   368  		name      string
   369  		tc        *testutil.IntegrationTestCluster
   370  		setup     func(t *testing.T, tt *test)
   371  		req       *vtctldatapb.DeleteShardsRequest
   372  		shouldErr bool
   373  		assertion func(t *testing.T, tt *test)
   374  	}
   375  	ctx := context.Background()
   376  	tests := []*test{
   377  		{
   378  			name: "ok",
   379  			tc: testutil.BuildIntegrationTestCluster(t, &vtadminpb.Cluster{
   380  				Id:   "local",
   381  				Name: "local",
   382  			}, "zone1"),
   383  			setup: func(t *testing.T, tt *test) {
   384  				ctx := context.Background()
   385  				shards := []string{"-80", "80-"}
   386  				for _, shard := range shards {
   387  					_, err := tt.tc.Cluster.CreateShard(ctx, &vtctldatapb.CreateShardRequest{
   388  						Keyspace:      "ks1",
   389  						ShardName:     shard,
   390  						IncludeParent: true,
   391  						Force:         true,
   392  					})
   393  					require.NoError(t, err)
   394  				}
   395  			},
   396  			req: &vtctldatapb.DeleteShardsRequest{
   397  				Shards: []*vtctldatapb.Shard{
   398  					{
   399  						Keyspace: "ks1",
   400  						Name:     "80-",
   401  					},
   402  				},
   403  			},
   404  			assertion: func(t *testing.T, tt *test) {
   405  				shard, err := tt.tc.Topo.GetShard(ctx, "ks1", "-80")
   406  				require.NoError(t, err, "topo.GetShard(ks1/-80) failed")
   407  
   408  				utils.MustMatch(t, &topodatapb.Shard{
   409  					KeyRange: &topodatapb.KeyRange{
   410  						End: []byte{0x80},
   411  					},
   412  					IsPrimaryServing: true,
   413  				}, shard.Shard)
   414  
   415  				shard2, err2 := tt.tc.Topo.GetShard(ctx, "ks1", "80-")
   416  				assert.True(t, topo.IsErrType(err2, topo.NoNode), "expected ks1/80- to be deleted, found %+v", shard2)
   417  			},
   418  		},
   419  		{
   420  			name: "nil request",
   421  			tc: testutil.BuildIntegrationTestCluster(t, &vtadminpb.Cluster{
   422  				Id:   "local",
   423  				Name: "local",
   424  			}, "zone1"),
   425  			req:       nil,
   426  			shouldErr: true,
   427  		},
   428  		{
   429  			name: "vtctld.DeleteShards fails",
   430  			tc: testutil.BuildIntegrationTestCluster(t, &vtadminpb.Cluster{
   431  				Id:   "local",
   432  				Name: "local",
   433  			}, "zone1"),
   434  			setup: func(t *testing.T, tt *test) {
   435  				_, err := tt.tc.Cluster.Vtctld.CreateShard(ctx, &vtctldatapb.CreateShardRequest{
   436  					Keyspace:      "ks1",
   437  					ShardName:     "-",
   438  					IncludeParent: true,
   439  				})
   440  				require.NoError(t, err, "CreateShard(ks1/-) failed")
   441  
   442  				srvks := &topodatapb.SrvKeyspace{
   443  					Partitions: []*topodatapb.SrvKeyspace_KeyspacePartition{
   444  						{
   445  							ServedType: topodatapb.TabletType_PRIMARY,
   446  							ShardReferences: []*topodatapb.ShardReference{
   447  								{
   448  									Name: "-",
   449  								},
   450  							},
   451  						},
   452  					},
   453  				}
   454  				err = tt.tc.Topo.UpdateSrvKeyspace(ctx, "zone1", "ks1", srvks)
   455  				require.NoError(t, err, "UpdateSrvKeyspace(zone1, ks1, %+v) failed", srvks)
   456  			},
   457  			req: &vtctldatapb.DeleteShardsRequest{
   458  				Shards: []*vtctldatapb.Shard{
   459  					{
   460  						Keyspace: "ks1",
   461  						Name:     "-",
   462  					},
   463  				},
   464  			},
   465  			shouldErr: true,
   466  			assertion: func(t *testing.T, tt *test) {
   467  				shard, err := tt.tc.Topo.GetShard(ctx, "ks1", "-")
   468  				require.NoError(t, err, "GetShard(ks1/-) failed")
   469  				utils.MustMatch(t, &topodatapb.Shard{
   470  					IsPrimaryServing: true,
   471  					KeyRange:         &topodatapb.KeyRange{},
   472  				}, shard.Shard)
   473  			},
   474  		},
   475  	}
   476  
   477  	for _, tt := range tests {
   478  		tt := tt
   479  		t.Run(tt.name, func(t *testing.T) {
   480  			if tt.setup != nil {
   481  				func() {
   482  					t.Helper()
   483  					tt.setup(t, tt)
   484  				}()
   485  			}
   486  
   487  			_, err := tt.tc.Cluster.DeleteShards(ctx, tt.req)
   488  			if tt.shouldErr {
   489  				assert.Error(t, err)
   490  			} else {
   491  				require.NoError(t, err)
   492  			}
   493  
   494  			if tt.assertion != nil {
   495  				func() {
   496  					t.Helper()
   497  					tt.assertion(t, tt)
   498  				}()
   499  			}
   500  		})
   501  	}
   502  }
   503  
   504  func TestFindTablet(t *testing.T) {
   505  	t.Parallel()
   506  
   507  	tests := []struct {
   508  		name          string
   509  		tablets       []*vtadminpb.Tablet
   510  		filter        func(*vtadminpb.Tablet) bool
   511  		expected      *vtadminpb.Tablet
   512  		expectedError error
   513  	}{
   514  		{
   515  			name: "returns the first matching tablet",
   516  			tablets: []*vtadminpb.Tablet{
   517  				{
   518  					State: vtadminpb.Tablet_NOT_SERVING,
   519  					Tablet: &topodatapb.Tablet{
   520  						Alias: &topodatapb.TabletAlias{
   521  							Cell: "c0_cell1",
   522  							Uid:  100,
   523  						},
   524  						Keyspace: "commerce",
   525  					},
   526  				},
   527  				{
   528  					State: vtadminpb.Tablet_SERVING,
   529  					Tablet: &topodatapb.Tablet{
   530  						Alias: &topodatapb.TabletAlias{
   531  							Cell: "c0_cell1",
   532  							Uid:  101,
   533  						},
   534  						Keyspace: "commerce",
   535  					},
   536  				},
   537  				{
   538  					State: vtadminpb.Tablet_SERVING,
   539  					Tablet: &topodatapb.Tablet{
   540  						Alias: &topodatapb.TabletAlias{
   541  							Cell: "c0_cell1",
   542  							Uid:  102,
   543  						},
   544  						Keyspace: "commerce",
   545  					},
   546  				},
   547  			},
   548  
   549  			filter: func(t *vtadminpb.Tablet) bool {
   550  				return t.State == vtadminpb.Tablet_SERVING
   551  			},
   552  			expected: &vtadminpb.Tablet{
   553  				Cluster: &vtadminpb.Cluster{
   554  					Id:   "c0",
   555  					Name: "cluster0",
   556  				},
   557  				State: vtadminpb.Tablet_SERVING,
   558  				Tablet: &topodatapb.Tablet{
   559  					Alias: &topodatapb.TabletAlias{
   560  						Cell: "c0_cell1",
   561  						Uid:  101,
   562  					},
   563  					Keyspace: "commerce",
   564  				},
   565  			},
   566  		},
   567  		{
   568  			name: "returns an error if no match found",
   569  			tablets: []*vtadminpb.Tablet{
   570  				{
   571  					State: vtadminpb.Tablet_NOT_SERVING,
   572  					Tablet: &topodatapb.Tablet{
   573  						Alias: &topodatapb.TabletAlias{
   574  							Cell: "c0_cell1",
   575  							Uid:  100,
   576  						},
   577  						Keyspace: "commerce",
   578  					},
   579  				},
   580  				{
   581  					State: vtadminpb.Tablet_NOT_SERVING,
   582  					Tablet: &topodatapb.Tablet{
   583  						Alias: &topodatapb.TabletAlias{
   584  							Cell: "c0_cell1",
   585  							Uid:  101,
   586  						},
   587  						Keyspace: "commerce",
   588  					},
   589  				},
   590  			},
   591  			filter: func(t *vtadminpb.Tablet) bool {
   592  				return t.State == vtadminpb.Tablet_SERVING
   593  			},
   594  			expectedError: vtadminerrors.ErrNoTablet,
   595  		},
   596  	}
   597  
   598  	ctx := context.Background()
   599  
   600  	for _, tt := range tests {
   601  		tt := tt
   602  
   603  		t.Run(tt.name, func(t *testing.T) {
   604  			t.Parallel()
   605  
   606  			cluster := testutil.BuildCluster(t, testutil.TestClusterConfig{
   607  				Cluster: &vtadminpb.Cluster{
   608  					Id:   "c0",
   609  					Name: "cluster0",
   610  				},
   611  				Tablets: tt.tablets,
   612  			})
   613  			tablet, err := cluster.FindTablet(ctx, tt.filter)
   614  
   615  			if tt.expectedError != nil {
   616  				assert.True(t, errors.Is(err, tt.expectedError), "expected error type %w does not match actual error type %w", err, tt.expectedError)
   617  			} else {
   618  				assert.NoError(t, err)
   619  				utils.MustMatch(t, tt.expected, tablet)
   620  			}
   621  		})
   622  	}
   623  }
   624  
   625  func TestFindTablets(t *testing.T) {
   626  	t.Parallel()
   627  
   628  	tests := []struct {
   629  		name     string
   630  		tablets  []*vtadminpb.Tablet
   631  		filter   func(*vtadminpb.Tablet) bool
   632  		n        int
   633  		expected []*vtadminpb.Tablet
   634  	}{
   635  		{
   636  			name: "returns n filtered tablets",
   637  			tablets: []*vtadminpb.Tablet{
   638  				{
   639  					State: vtadminpb.Tablet_SERVING,
   640  					Tablet: &topodatapb.Tablet{
   641  						Alias: &topodatapb.TabletAlias{
   642  							Cell: "c0_cell1",
   643  							Uid:  100,
   644  						},
   645  						Keyspace: "commerce",
   646  					},
   647  				},
   648  				{
   649  					State: vtadminpb.Tablet_NOT_SERVING,
   650  					Tablet: &topodatapb.Tablet{
   651  						Alias: &topodatapb.TabletAlias{
   652  							Cell: "c0_cell1",
   653  							Uid:  101,
   654  						},
   655  						Keyspace: "commerce",
   656  					},
   657  				},
   658  				{
   659  					State: vtadminpb.Tablet_SERVING,
   660  					Tablet: &topodatapb.Tablet{
   661  						Alias: &topodatapb.TabletAlias{
   662  							Cell: "c0_cell1",
   663  							Uid:  102,
   664  						},
   665  						Keyspace: "commerce",
   666  					},
   667  				},
   668  				{
   669  					State: vtadminpb.Tablet_SERVING,
   670  					Tablet: &topodatapb.Tablet{
   671  						Alias: &topodatapb.TabletAlias{
   672  							Cell: "c0_cell1",
   673  							Uid:  103,
   674  						},
   675  						Keyspace: "commerce",
   676  					},
   677  				},
   678  			},
   679  			filter: func(t *vtadminpb.Tablet) bool {
   680  				return t.State == vtadminpb.Tablet_SERVING
   681  			},
   682  			n: 2,
   683  			expected: []*vtadminpb.Tablet{
   684  				{
   685  					Cluster: &vtadminpb.Cluster{
   686  						Id:   "c0",
   687  						Name: "cluster0",
   688  					},
   689  					State: vtadminpb.Tablet_SERVING,
   690  					Tablet: &topodatapb.Tablet{
   691  						Alias: &topodatapb.TabletAlias{
   692  							Cell: "c0_cell1",
   693  							Uid:  100,
   694  						},
   695  						Keyspace: "commerce",
   696  					},
   697  				},
   698  				{
   699  					Cluster: &vtadminpb.Cluster{
   700  						Id:   "c0",
   701  						Name: "cluster0",
   702  					},
   703  					State: vtadminpb.Tablet_SERVING,
   704  					Tablet: &topodatapb.Tablet{
   705  						Alias: &topodatapb.TabletAlias{
   706  							Cell: "c0_cell1",
   707  							Uid:  102,
   708  						},
   709  						Keyspace: "commerce",
   710  					},
   711  				},
   712  			},
   713  		},
   714  		{
   715  			name: "returns all filtered tablets when n == -1",
   716  			tablets: []*vtadminpb.Tablet{
   717  				{
   718  					State: vtadminpb.Tablet_SERVING,
   719  					Tablet: &topodatapb.Tablet{
   720  						Alias: &topodatapb.TabletAlias{
   721  							Cell: "c0_cell1",
   722  							Uid:  100,
   723  						},
   724  						Keyspace: "commerce",
   725  					},
   726  				},
   727  				{
   728  					State: vtadminpb.Tablet_NOT_SERVING,
   729  					Tablet: &topodatapb.Tablet{
   730  						Alias: &topodatapb.TabletAlias{
   731  							Cell: "c0_cell1",
   732  							Uid:  101,
   733  						},
   734  						Keyspace: "commerce",
   735  					},
   736  				},
   737  				{
   738  					State: vtadminpb.Tablet_SERVING,
   739  					Tablet: &topodatapb.Tablet{
   740  						Alias: &topodatapb.TabletAlias{
   741  							Cell: "c0_cell1",
   742  							Uid:  102,
   743  						},
   744  						Keyspace: "commerce",
   745  					},
   746  				},
   747  				{
   748  					State: vtadminpb.Tablet_SERVING,
   749  					Tablet: &topodatapb.Tablet{
   750  						Alias: &topodatapb.TabletAlias{
   751  							Cell: "c0_cell1",
   752  							Uid:  103,
   753  						},
   754  						Keyspace: "commerce",
   755  					},
   756  				},
   757  			},
   758  			filter: func(t *vtadminpb.Tablet) bool {
   759  				return t.State == vtadminpb.Tablet_SERVING
   760  			},
   761  			n: -1,
   762  			expected: []*vtadminpb.Tablet{
   763  				{
   764  					Cluster: &vtadminpb.Cluster{
   765  						Id:   "c0",
   766  						Name: "cluster0",
   767  					},
   768  					State: vtadminpb.Tablet_SERVING,
   769  					Tablet: &topodatapb.Tablet{
   770  						Alias: &topodatapb.TabletAlias{
   771  							Cell: "c0_cell1",
   772  							Uid:  100,
   773  						},
   774  						Keyspace: "commerce",
   775  					},
   776  				},
   777  				{
   778  					Cluster: &vtadminpb.Cluster{
   779  						Id:   "c0",
   780  						Name: "cluster0",
   781  					},
   782  					State: vtadminpb.Tablet_SERVING,
   783  					Tablet: &topodatapb.Tablet{
   784  						Alias: &topodatapb.TabletAlias{
   785  							Cell: "c0_cell1",
   786  							Uid:  102,
   787  						},
   788  						Keyspace: "commerce",
   789  					},
   790  				},
   791  				{
   792  					Cluster: &vtadminpb.Cluster{
   793  						Id:   "c0",
   794  						Name: "cluster0",
   795  					},
   796  					State: vtadminpb.Tablet_SERVING,
   797  					Tablet: &topodatapb.Tablet{
   798  						Alias: &topodatapb.TabletAlias{
   799  							Cell: "c0_cell1",
   800  							Uid:  103,
   801  						},
   802  						Keyspace: "commerce",
   803  					},
   804  				},
   805  			},
   806  		},
   807  	}
   808  
   809  	ctx := context.Background()
   810  
   811  	for _, tt := range tests {
   812  		tt := tt
   813  
   814  		t.Run(tt.name, func(t *testing.T) {
   815  			t.Parallel()
   816  
   817  			cluster := testutil.BuildCluster(t, testutil.TestClusterConfig{
   818  				Cluster: &vtadminpb.Cluster{
   819  					Id:   "c0",
   820  					Name: "cluster0",
   821  				},
   822  				Tablets: tt.tablets,
   823  			})
   824  			tablets, err := cluster.FindTablets(ctx, tt.filter, tt.n)
   825  
   826  			assert.NoError(t, err)
   827  			testutil.AssertTabletSlicesEqual(t, tt.expected, tablets)
   828  		})
   829  	}
   830  }
   831  
   832  func TestFindWorkflows(t *testing.T) {
   833  	t.Parallel()
   834  
   835  	tests := []struct {
   836  		name      string
   837  		cfg       testutil.TestClusterConfig
   838  		keyspaces []string
   839  		opts      cluster.FindWorkflowsOptions
   840  		expected  *vtadminpb.ClusterWorkflows
   841  		shouldErr bool
   842  	}{
   843  		{
   844  			name: "success",
   845  			cfg: testutil.TestClusterConfig{
   846  				Cluster: &vtadminpb.Cluster{
   847  					Id:   "c1",
   848  					Name: "cluster1",
   849  				},
   850  				VtctldClient: &fakevtctldclient.VtctldClient{
   851  					GetWorkflowsResults: map[string]struct {
   852  						Response *vtctldatapb.GetWorkflowsResponse
   853  						Error    error
   854  					}{
   855  						"ks1": {
   856  							Response: &vtctldatapb.GetWorkflowsResponse{
   857  								Workflows: []*vtctldatapb.Workflow{
   858  									{
   859  										Name: "workflow1",
   860  									},
   861  								},
   862  							},
   863  						},
   864  					},
   865  				},
   866  			},
   867  			keyspaces: []string{"ks1"},
   868  			expected: &vtadminpb.ClusterWorkflows{
   869  				Workflows: []*vtadminpb.Workflow{
   870  					{
   871  						Cluster: &vtadminpb.Cluster{
   872  							Id:   "c1",
   873  							Name: "cluster1",
   874  						},
   875  						Keyspace: "ks1",
   876  						Workflow: &vtctldatapb.Workflow{
   877  							Name: "workflow1",
   878  						},
   879  					},
   880  				},
   881  			},
   882  			shouldErr: false,
   883  		},
   884  		{
   885  			name: "error getting keyspaces is fatal",
   886  			cfg: testutil.TestClusterConfig{
   887  				Cluster: &vtadminpb.Cluster{
   888  					Id:   "c1",
   889  					Name: "cluster1",
   890  				},
   891  				VtctldClient: &fakevtctldclient.VtctldClient{
   892  					GetKeyspacesResults: &struct {
   893  						Keyspaces []*vtctldatapb.Keyspace
   894  						Error     error
   895  					}{
   896  						Keyspaces: nil,
   897  						Error:     assert.AnError,
   898  					},
   899  				},
   900  			},
   901  			keyspaces: nil,
   902  			expected:  nil,
   903  			shouldErr: true,
   904  		},
   905  		{
   906  			name: "no keyspaces found",
   907  			cfg: testutil.TestClusterConfig{
   908  				Cluster: &vtadminpb.Cluster{
   909  					Id:   "c1",
   910  					Name: "cluster1",
   911  				},
   912  				VtctldClient: &fakevtctldclient.VtctldClient{
   913  					GetKeyspacesResults: &struct {
   914  						Keyspaces []*vtctldatapb.Keyspace
   915  						Error     error
   916  					}{
   917  						Keyspaces: []*vtctldatapb.Keyspace{},
   918  						Error:     nil,
   919  					},
   920  				},
   921  			},
   922  			keyspaces: nil,
   923  			expected: &vtadminpb.ClusterWorkflows{
   924  				Workflows: []*vtadminpb.Workflow{},
   925  			},
   926  			shouldErr: false,
   927  		},
   928  		{
   929  			name: "when specifying keyspaces and IgnoreKeyspaces, IgnoreKeyspaces is discarded",
   930  			cfg: testutil.TestClusterConfig{
   931  				Cluster: &vtadminpb.Cluster{
   932  					Id:   "c1",
   933  					Name: "cluster1",
   934  				},
   935  				VtctldClient: &fakevtctldclient.VtctldClient{
   936  					GetKeyspacesResults: &struct {
   937  						Keyspaces []*vtctldatapb.Keyspace
   938  						Error     error
   939  					}{
   940  						Keyspaces: []*vtctldatapb.Keyspace{
   941  							{
   942  								Name: "ks1",
   943  							},
   944  							{
   945  								Name: "ks2",
   946  							},
   947  						},
   948  						Error: nil,
   949  					},
   950  					GetWorkflowsResults: map[string]struct {
   951  						Response *vtctldatapb.GetWorkflowsResponse
   952  						Error    error
   953  					}{
   954  						"ks1": {
   955  							Response: &vtctldatapb.GetWorkflowsResponse{
   956  								Workflows: []*vtctldatapb.Workflow{
   957  									{
   958  										Name: "workflow1",
   959  									},
   960  									{
   961  										Name: "workflow2",
   962  									},
   963  								},
   964  							},
   965  						},
   966  						"ks2": {
   967  							Response: &vtctldatapb.GetWorkflowsResponse{
   968  								Workflows: []*vtctldatapb.Workflow{
   969  									{
   970  										Name: "workflow_a",
   971  									},
   972  								},
   973  							},
   974  						},
   975  					},
   976  				},
   977  			},
   978  			keyspaces: []string{"ks2"},
   979  			opts: cluster.FindWorkflowsOptions{
   980  				IgnoreKeyspaces: sets.New[string]("ks2"),
   981  			},
   982  			expected: &vtadminpb.ClusterWorkflows{
   983  				Workflows: []*vtadminpb.Workflow{
   984  					{
   985  						Cluster: &vtadminpb.Cluster{
   986  							Id:   "c1",
   987  							Name: "cluster1",
   988  						},
   989  						Keyspace: "ks2",
   990  						Workflow: &vtctldatapb.Workflow{
   991  							Name: "workflow_a",
   992  						},
   993  					},
   994  				},
   995  			},
   996  			shouldErr: false,
   997  		},
   998  		{
   999  			name: "ignore keyspaces",
  1000  			cfg: testutil.TestClusterConfig{
  1001  				Cluster: &vtadminpb.Cluster{
  1002  					Id:   "c1",
  1003  					Name: "cluster1",
  1004  				},
  1005  				VtctldClient: &fakevtctldclient.VtctldClient{
  1006  					GetKeyspacesResults: &struct {
  1007  						Keyspaces []*vtctldatapb.Keyspace
  1008  						Error     error
  1009  					}{
  1010  						Keyspaces: []*vtctldatapb.Keyspace{
  1011  							{
  1012  								Name: "ks1",
  1013  							},
  1014  							{
  1015  								Name: "ks2",
  1016  							},
  1017  						},
  1018  						Error: nil,
  1019  					},
  1020  					GetWorkflowsResults: map[string]struct {
  1021  						Response *vtctldatapb.GetWorkflowsResponse
  1022  						Error    error
  1023  					}{
  1024  						"ks1": {
  1025  							Response: &vtctldatapb.GetWorkflowsResponse{
  1026  								Workflows: []*vtctldatapb.Workflow{
  1027  									{
  1028  										Name: "workflow1",
  1029  									},
  1030  									{
  1031  										Name: "workflow2",
  1032  									},
  1033  								},
  1034  							},
  1035  						},
  1036  						"ks2": {
  1037  							Response: &vtctldatapb.GetWorkflowsResponse{
  1038  								Workflows: []*vtctldatapb.Workflow{
  1039  									{
  1040  										Name: "workflow_a",
  1041  									},
  1042  								},
  1043  							},
  1044  						},
  1045  					},
  1046  				},
  1047  			},
  1048  			keyspaces: nil,
  1049  			opts: cluster.FindWorkflowsOptions{
  1050  				IgnoreKeyspaces: sets.New[string]("ks2"),
  1051  			},
  1052  			expected: &vtadminpb.ClusterWorkflows{
  1053  				Workflows: []*vtadminpb.Workflow{
  1054  					{
  1055  						Cluster: &vtadminpb.Cluster{
  1056  							Id:   "c1",
  1057  							Name: "cluster1",
  1058  						},
  1059  						Keyspace: "ks1",
  1060  						Workflow: &vtctldatapb.Workflow{
  1061  							Name: "workflow1",
  1062  						},
  1063  					},
  1064  					{
  1065  						Cluster: &vtadminpb.Cluster{
  1066  							Id:   "c1",
  1067  							Name: "cluster1",
  1068  						},
  1069  						Keyspace: "ks1",
  1070  						Workflow: &vtctldatapb.Workflow{
  1071  							Name: "workflow2",
  1072  						},
  1073  					},
  1074  				},
  1075  			},
  1076  			shouldErr: false,
  1077  		},
  1078  		{
  1079  			name: "error getting workflows is fatal if all keyspaces fail",
  1080  			cfg: testutil.TestClusterConfig{
  1081  				Cluster: &vtadminpb.Cluster{
  1082  					Id:   "c1",
  1083  					Name: "cluster1",
  1084  				},
  1085  				VtctldClient: &fakevtctldclient.VtctldClient{
  1086  					GetWorkflowsResults: map[string]struct {
  1087  						Response *vtctldatapb.GetWorkflowsResponse
  1088  						Error    error
  1089  					}{
  1090  						"ks1": {
  1091  							Error: assert.AnError,
  1092  						},
  1093  					},
  1094  				},
  1095  			},
  1096  			keyspaces: []string{"ks1"},
  1097  			expected:  nil,
  1098  			shouldErr: true,
  1099  		},
  1100  		{
  1101  			name: "error getting workflows is non-fatal if some keyspaces fail",
  1102  			cfg: testutil.TestClusterConfig{
  1103  				Cluster: &vtadminpb.Cluster{
  1104  					Id:   "c1",
  1105  					Name: "cluster1",
  1106  				},
  1107  				VtctldClient: &fakevtctldclient.VtctldClient{
  1108  					GetWorkflowsResults: map[string]struct {
  1109  						Response *vtctldatapb.GetWorkflowsResponse
  1110  						Error    error
  1111  					}{
  1112  						"ks1": {
  1113  							Error: assert.AnError,
  1114  						},
  1115  						"ks2": {
  1116  							Response: &vtctldatapb.GetWorkflowsResponse{
  1117  								Workflows: []*vtctldatapb.Workflow{
  1118  									{
  1119  										Name: "workflow1",
  1120  									},
  1121  								},
  1122  							},
  1123  						},
  1124  					},
  1125  				},
  1126  			},
  1127  			keyspaces: []string{"ks1", "ks2"},
  1128  			expected: &vtadminpb.ClusterWorkflows{
  1129  				Workflows: []*vtadminpb.Workflow{
  1130  					{
  1131  						Cluster: &vtadminpb.Cluster{
  1132  							Id:   "c1",
  1133  							Name: "cluster1",
  1134  						},
  1135  						Keyspace: "ks2",
  1136  						Workflow: &vtctldatapb.Workflow{
  1137  							Name: "workflow1",
  1138  						},
  1139  					},
  1140  				},
  1141  				Warnings: []string{"something about ks1"},
  1142  			},
  1143  			shouldErr: false,
  1144  		},
  1145  		{
  1146  			name: "filtered workflows",
  1147  			cfg: testutil.TestClusterConfig{
  1148  				Cluster: &vtadminpb.Cluster{
  1149  					Id:   "c1",
  1150  					Name: "cluster1",
  1151  				},
  1152  				VtctldClient: &fakevtctldclient.VtctldClient{
  1153  					GetWorkflowsResults: map[string]struct {
  1154  						Response *vtctldatapb.GetWorkflowsResponse
  1155  						Error    error
  1156  					}{
  1157  						"ks1": {
  1158  							Response: &vtctldatapb.GetWorkflowsResponse{
  1159  								Workflows: []*vtctldatapb.Workflow{
  1160  									{
  1161  										Name: "include_me",
  1162  									},
  1163  									{
  1164  										Name: "dont_include_me",
  1165  									},
  1166  								},
  1167  							},
  1168  						},
  1169  					},
  1170  				},
  1171  			},
  1172  			keyspaces: []string{"ks1"},
  1173  			opts: cluster.FindWorkflowsOptions{
  1174  				Filter: func(workflow *vtadminpb.Workflow) bool {
  1175  					return strings.HasPrefix(workflow.Workflow.Name, "include_me")
  1176  				},
  1177  			},
  1178  			expected: &vtadminpb.ClusterWorkflows{
  1179  				Workflows: []*vtadminpb.Workflow{
  1180  					{
  1181  						Cluster: &vtadminpb.Cluster{
  1182  							Id:   "c1",
  1183  							Name: "cluster1",
  1184  						},
  1185  						Keyspace: "ks1",
  1186  						Workflow: &vtctldatapb.Workflow{
  1187  							Name: "include_me",
  1188  						},
  1189  					},
  1190  				},
  1191  			},
  1192  			shouldErr: false,
  1193  		},
  1194  	}
  1195  
  1196  	ctx := context.Background()
  1197  
  1198  	for _, tt := range tests {
  1199  		tt := tt
  1200  
  1201  		t.Run(tt.name, func(t *testing.T) {
  1202  			t.Parallel()
  1203  
  1204  			c := testutil.BuildCluster(t, tt.cfg)
  1205  			workflows, err := c.FindWorkflows(ctx, tt.keyspaces, tt.opts)
  1206  			if tt.shouldErr {
  1207  				assert.Error(t, err)
  1208  
  1209  				return
  1210  			}
  1211  
  1212  			assert.NoError(t, err)
  1213  			testutil.AssertClusterWorkflowsEqual(t, tt.expected, workflows)
  1214  		})
  1215  	}
  1216  }
  1217  
  1218  func TestGetCellInfos(t *testing.T) {
  1219  	t.Parallel()
  1220  
  1221  	cpb := &vtadminpb.Cluster{
  1222  		Id:   "test",
  1223  		Name: "test",
  1224  	}
  1225  
  1226  	tests := []struct {
  1227  		name      string
  1228  		vtctld    *fakevtctldclient.VtctldClient
  1229  		req       *vtadminpb.GetCellInfosRequest
  1230  		expected  []*vtadminpb.ClusterCellInfo
  1231  		shouldErr bool
  1232  	}{
  1233  		{
  1234  			name: "all cells - ok",
  1235  			vtctld: &fakevtctldclient.VtctldClient{
  1236  				GetCellInfoNamesResults: &struct {
  1237  					Response *vtctldatapb.GetCellInfoNamesResponse
  1238  					Error    error
  1239  				}{
  1240  					Response: &vtctldatapb.GetCellInfoNamesResponse{
  1241  						Names: []string{"c1", "c2"},
  1242  					},
  1243  				},
  1244  				GetCellInfoResults: map[string]struct {
  1245  					Response *vtctldatapb.GetCellInfoResponse
  1246  					Error    error
  1247  				}{
  1248  					"c1": {
  1249  						Response: &vtctldatapb.GetCellInfoResponse{
  1250  							CellInfo: &topodatapb.CellInfo{
  1251  								ServerAddress: "http://cell1",
  1252  								Root:          "/vitess/cell1",
  1253  							},
  1254  						},
  1255  					},
  1256  					"c2": {
  1257  						Response: &vtctldatapb.GetCellInfoResponse{
  1258  							CellInfo: &topodatapb.CellInfo{
  1259  								ServerAddress: "http://cell2",
  1260  								Root:          "/vitess/cell2",
  1261  							},
  1262  						},
  1263  					},
  1264  				},
  1265  			},
  1266  			req: &vtadminpb.GetCellInfosRequest{},
  1267  			expected: []*vtadminpb.ClusterCellInfo{
  1268  				{
  1269  					Cluster: cpb,
  1270  					Name:    "c1",
  1271  					CellInfo: &topodatapb.CellInfo{
  1272  						ServerAddress: "http://cell1",
  1273  						Root:          "/vitess/cell1",
  1274  					},
  1275  				},
  1276  				{
  1277  					Cluster: cpb,
  1278  					Name:    "c2",
  1279  					CellInfo: &topodatapb.CellInfo{
  1280  						ServerAddress: "http://cell2",
  1281  						Root:          "/vitess/cell2",
  1282  					},
  1283  				},
  1284  			},
  1285  		},
  1286  		{
  1287  			name: "all cells - names only",
  1288  			vtctld: &fakevtctldclient.VtctldClient{
  1289  				GetCellInfoNamesResults: &struct {
  1290  					Response *vtctldatapb.GetCellInfoNamesResponse
  1291  					Error    error
  1292  				}{
  1293  					Response: &vtctldatapb.GetCellInfoNamesResponse{
  1294  						Names: []string{"c1", "c2"},
  1295  					},
  1296  				},
  1297  				GetCellInfoResults: map[string]struct {
  1298  					Response *vtctldatapb.GetCellInfoResponse
  1299  					Error    error
  1300  				}{
  1301  					"c1": {
  1302  						Response: &vtctldatapb.GetCellInfoResponse{
  1303  							CellInfo: &topodatapb.CellInfo{
  1304  								ServerAddress: "http://cell1",
  1305  								Root:          "/vitess/cell1",
  1306  							},
  1307  						},
  1308  					},
  1309  					"c2": {
  1310  						Response: &vtctldatapb.GetCellInfoResponse{
  1311  							CellInfo: &topodatapb.CellInfo{
  1312  								ServerAddress: "http://cell2",
  1313  								Root:          "/vitess/cell2",
  1314  							},
  1315  						},
  1316  					},
  1317  				},
  1318  			},
  1319  			req: &vtadminpb.GetCellInfosRequest{
  1320  				NamesOnly: true,
  1321  			},
  1322  			expected: []*vtadminpb.ClusterCellInfo{
  1323  				{
  1324  					Cluster: cpb,
  1325  					Name:    "c1",
  1326  				},
  1327  				{
  1328  					Cluster: cpb,
  1329  					Name:    "c2",
  1330  				},
  1331  			},
  1332  		},
  1333  		{
  1334  			name: "all cells - fail",
  1335  			vtctld: &fakevtctldclient.VtctldClient{
  1336  				GetCellInfoNamesResults: &struct {
  1337  					Response *vtctldatapb.GetCellInfoNamesResponse
  1338  					Error    error
  1339  				}{
  1340  					Error: fmt.Errorf("getcellinfonames failed"),
  1341  				},
  1342  			},
  1343  			req:       &vtadminpb.GetCellInfosRequest{},
  1344  			shouldErr: true,
  1345  		},
  1346  		{
  1347  			name: "cells - ok",
  1348  			vtctld: &fakevtctldclient.VtctldClient{
  1349  				GetCellInfoNamesResults: &struct {
  1350  					Response *vtctldatapb.GetCellInfoNamesResponse
  1351  					Error    error
  1352  				}{
  1353  					Response: &vtctldatapb.GetCellInfoNamesResponse{
  1354  						Names: []string{"c1", "c2"},
  1355  					},
  1356  				},
  1357  				GetCellInfoResults: map[string]struct {
  1358  					Response *vtctldatapb.GetCellInfoResponse
  1359  					Error    error
  1360  				}{
  1361  					"c1": {
  1362  						Response: &vtctldatapb.GetCellInfoResponse{
  1363  							CellInfo: &topodatapb.CellInfo{
  1364  								ServerAddress: "http://cell1",
  1365  								Root:          "/vitess/cell1",
  1366  							},
  1367  						},
  1368  					},
  1369  					"c2": {
  1370  						Response: &vtctldatapb.GetCellInfoResponse{
  1371  							CellInfo: &topodatapb.CellInfo{
  1372  								ServerAddress: "http://cell2",
  1373  								Root:          "/vitess/cell2",
  1374  							},
  1375  						},
  1376  					},
  1377  				},
  1378  			},
  1379  			req: &vtadminpb.GetCellInfosRequest{
  1380  				Cells: []string{"c1"},
  1381  			},
  1382  			expected: []*vtadminpb.ClusterCellInfo{
  1383  				{
  1384  					Cluster: cpb,
  1385  					Name:    "c1",
  1386  					CellInfo: &topodatapb.CellInfo{
  1387  						ServerAddress: "http://cell1",
  1388  						Root:          "/vitess/cell1",
  1389  					},
  1390  				},
  1391  			},
  1392  		},
  1393  		{
  1394  			name: "getcellinfo - fail",
  1395  			vtctld: &fakevtctldclient.VtctldClient{
  1396  				GetCellInfoNamesResults: &struct {
  1397  					Response *vtctldatapb.GetCellInfoNamesResponse
  1398  					Error    error
  1399  				}{
  1400  					Response: &vtctldatapb.GetCellInfoNamesResponse{
  1401  						Names: []string{"c1", "c2"},
  1402  					},
  1403  				},
  1404  				GetCellInfoResults: map[string]struct {
  1405  					Response *vtctldatapb.GetCellInfoResponse
  1406  					Error    error
  1407  				}{
  1408  					"c1": {
  1409  						Error: fmt.Errorf("GetCellInfo(c1) fail"),
  1410  					},
  1411  					"c2": {
  1412  						Response: &vtctldatapb.GetCellInfoResponse{
  1413  							CellInfo: &topodatapb.CellInfo{
  1414  								ServerAddress: "http://cell2",
  1415  								Root:          "/vitess/cell2",
  1416  							},
  1417  						},
  1418  					},
  1419  				},
  1420  			},
  1421  			req: &vtadminpb.GetCellInfosRequest{
  1422  				Cells: []string{"c1", "c2"},
  1423  			},
  1424  			shouldErr: true,
  1425  		},
  1426  		{
  1427  			name: "cells with names only still gives full info",
  1428  			vtctld: &fakevtctldclient.VtctldClient{
  1429  				GetCellInfoNamesResults: &struct {
  1430  					Response *vtctldatapb.GetCellInfoNamesResponse
  1431  					Error    error
  1432  				}{
  1433  					Response: &vtctldatapb.GetCellInfoNamesResponse{
  1434  						Names: []string{"c1", "c2"},
  1435  					},
  1436  				},
  1437  				GetCellInfoResults: map[string]struct {
  1438  					Response *vtctldatapb.GetCellInfoResponse
  1439  					Error    error
  1440  				}{
  1441  					"c1": {
  1442  						Response: &vtctldatapb.GetCellInfoResponse{
  1443  							CellInfo: &topodatapb.CellInfo{
  1444  								ServerAddress: "http://cell1",
  1445  								Root:          "/vitess/cell1",
  1446  							},
  1447  						},
  1448  					},
  1449  					"c2": {
  1450  						Response: &vtctldatapb.GetCellInfoResponse{
  1451  							CellInfo: &topodatapb.CellInfo{
  1452  								ServerAddress: "http://cell2",
  1453  								Root:          "/vitess/cell2",
  1454  							},
  1455  						},
  1456  					},
  1457  				},
  1458  			},
  1459  			req: &vtadminpb.GetCellInfosRequest{
  1460  				Cells:     []string{"c1"},
  1461  				NamesOnly: true,
  1462  			},
  1463  			expected: []*vtadminpb.ClusterCellInfo{
  1464  				{
  1465  					Cluster: cpb,
  1466  					Name:    "c1",
  1467  					CellInfo: &topodatapb.CellInfo{
  1468  						ServerAddress: "http://cell1",
  1469  						Root:          "/vitess/cell1",
  1470  					},
  1471  				},
  1472  			},
  1473  		},
  1474  	}
  1475  
  1476  	for _, tt := range tests {
  1477  		tt := tt
  1478  		t.Run(tt.name, func(t *testing.T) {
  1479  			t.Parallel()
  1480  
  1481  			c := testutil.BuildCluster(t, testutil.TestClusterConfig{
  1482  				Cluster:      cpb,
  1483  				VtctldClient: tt.vtctld,
  1484  			})
  1485  			cellInfos, err := c.GetCellInfos(context.Background(), tt.req)
  1486  			if tt.shouldErr {
  1487  				assert.Error(t, err)
  1488  				return
  1489  			}
  1490  
  1491  			require.NoError(t, err)
  1492  
  1493  			sort.Slice(tt.expected, func(i, j int) bool {
  1494  				return tt.expected[i].Name < tt.expected[j].Name
  1495  			})
  1496  			sort.Slice(cellInfos, func(i, j int) bool {
  1497  				return cellInfos[i].Name < cellInfos[j].Name
  1498  			})
  1499  
  1500  			utils.MustMatch(t, tt.expected, cellInfos)
  1501  		})
  1502  	}
  1503  }
  1504  
  1505  func TestGetCellsAliases(t *testing.T) {
  1506  	t.Parallel()
  1507  
  1508  	cpb := &vtadminpb.Cluster{
  1509  		Id:   "test",
  1510  		Name: "test",
  1511  	}
  1512  
  1513  	tests := []struct {
  1514  		name      string
  1515  		vtctld    *fakevtctldclient.VtctldClient
  1516  		expected  *vtadminpb.ClusterCellsAliases
  1517  		shouldErr bool
  1518  	}{
  1519  		{
  1520  			name: "ok",
  1521  			vtctld: &fakevtctldclient.VtctldClient{
  1522  				GetCellsAliasesResults: &struct {
  1523  					Response *vtctldatapb.GetCellsAliasesResponse
  1524  					Error    error
  1525  				}{
  1526  					Response: &vtctldatapb.GetCellsAliasesResponse{
  1527  						Aliases: map[string]*topodatapb.CellsAlias{
  1528  							"a1": {
  1529  								Cells: []string{"c1", "c2"},
  1530  							},
  1531  						},
  1532  					},
  1533  				},
  1534  			},
  1535  			expected: &vtadminpb.ClusterCellsAliases{
  1536  				Cluster: cpb,
  1537  				Aliases: map[string]*topodatapb.CellsAlias{
  1538  					"a1": {
  1539  						Cells: []string{"c1", "c2"},
  1540  					},
  1541  				},
  1542  			},
  1543  		},
  1544  		{
  1545  			name: "error",
  1546  			vtctld: &fakevtctldclient.VtctldClient{
  1547  				GetCellsAliasesResults: &struct {
  1548  					Response *vtctldatapb.GetCellsAliasesResponse
  1549  					Error    error
  1550  				}{
  1551  					Error: fmt.Errorf("this should fail"),
  1552  				},
  1553  			},
  1554  			shouldErr: true,
  1555  		},
  1556  	}
  1557  
  1558  	for _, tt := range tests {
  1559  		tt := tt
  1560  		t.Run(tt.name, func(t *testing.T) {
  1561  			t.Parallel()
  1562  
  1563  			c := testutil.BuildCluster(t, testutil.TestClusterConfig{
  1564  				Cluster:      cpb,
  1565  				VtctldClient: tt.vtctld,
  1566  			})
  1567  			cellsAliases, err := c.GetCellsAliases(context.Background())
  1568  			if tt.shouldErr {
  1569  				assert.Error(t, err)
  1570  				return
  1571  			}
  1572  
  1573  			require.NoError(t, err)
  1574  			utils.MustMatch(t, tt.expected, cellsAliases)
  1575  		})
  1576  	}
  1577  }
  1578  
  1579  func TestGetSchema(t *testing.T) {
  1580  	t.Parallel()
  1581  
  1582  	tests := []struct {
  1583  		name      string
  1584  		vtctld    vtctldclient.VtctldClient
  1585  		req       *vtctldatapb.GetSchemaRequest
  1586  		tablet    *vtadminpb.Tablet
  1587  		expected  *vtadminpb.Schema
  1588  		shouldErr bool
  1589  	}{
  1590  		{
  1591  			name: "success",
  1592  			vtctld: &fakevtctldclient.VtctldClient{
  1593  				GetSchemaResults: map[string]struct {
  1594  					Response *vtctldatapb.GetSchemaResponse
  1595  					Error    error
  1596  				}{
  1597  					"zone1-0000000100": {
  1598  						Response: &vtctldatapb.GetSchemaResponse{
  1599  							Schema: &tabletmanagerdatapb.SchemaDefinition{
  1600  								TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  1601  									{
  1602  										Name: "some_table",
  1603  									},
  1604  								},
  1605  							},
  1606  						},
  1607  					},
  1608  				},
  1609  			},
  1610  			req: &vtctldatapb.GetSchemaRequest{},
  1611  			tablet: &vtadminpb.Tablet{
  1612  				State: vtadminpb.Tablet_SERVING,
  1613  				Tablet: &topodatapb.Tablet{
  1614  					Alias: &topodatapb.TabletAlias{
  1615  						Cell: "zone1",
  1616  						Uid:  100,
  1617  					},
  1618  					Keyspace: "testkeyspace",
  1619  				},
  1620  			},
  1621  			expected: &vtadminpb.Schema{
  1622  				Cluster: &vtadminpb.Cluster{
  1623  					Name: "cluster0",
  1624  					Id:   "c0",
  1625  				},
  1626  				Keyspace: "testkeyspace",
  1627  				TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  1628  					{
  1629  						Name: "some_table",
  1630  					},
  1631  				},
  1632  				TableSizes: map[string]*vtadminpb.Schema_TableSize{},
  1633  			},
  1634  			shouldErr: false,
  1635  		},
  1636  		{
  1637  			name: "error getting schema",
  1638  			vtctld: &fakevtctldclient.VtctldClient{
  1639  				GetSchemaResults: map[string]struct {
  1640  					Response *vtctldatapb.GetSchemaResponse
  1641  					Error    error
  1642  				}{
  1643  					"zone1-0000000100": {
  1644  						Error: assert.AnError,
  1645  					},
  1646  				},
  1647  			},
  1648  			req: &vtctldatapb.GetSchemaRequest{},
  1649  			tablet: &vtadminpb.Tablet{
  1650  				State: vtadminpb.Tablet_SERVING,
  1651  				Tablet: &topodatapb.Tablet{
  1652  					Alias: &topodatapb.TabletAlias{
  1653  						Cell: "zone1",
  1654  						Uid:  100,
  1655  					},
  1656  					Keyspace: "testkeyspace",
  1657  				},
  1658  			},
  1659  			expected:  nil,
  1660  			shouldErr: true,
  1661  		},
  1662  		{
  1663  			name: "underlying schema is nil",
  1664  			vtctld: &fakevtctldclient.VtctldClient{
  1665  				GetSchemaResults: map[string]struct {
  1666  					Response *vtctldatapb.GetSchemaResponse
  1667  					Error    error
  1668  				}{
  1669  					"zone1-0000000100": {
  1670  						Response: &vtctldatapb.GetSchemaResponse{
  1671  							Schema: nil,
  1672  						},
  1673  						Error: nil,
  1674  					},
  1675  				},
  1676  			},
  1677  			req: &vtctldatapb.GetSchemaRequest{},
  1678  			tablet: &vtadminpb.Tablet{
  1679  				State: vtadminpb.Tablet_SERVING,
  1680  				Tablet: &topodatapb.Tablet{
  1681  					Alias: &topodatapb.TabletAlias{
  1682  						Cell: "zone1",
  1683  						Uid:  100,
  1684  					},
  1685  					Keyspace: "testkeyspace",
  1686  				},
  1687  			},
  1688  			expected: &vtadminpb.Schema{
  1689  				Cluster: &vtadminpb.Cluster{
  1690  					Id:   "c2",
  1691  					Name: "cluster2",
  1692  				},
  1693  				Keyspace:         "testkeyspace",
  1694  				TableDefinitions: nil,
  1695  				TableSizes:       map[string]*vtadminpb.Schema_TableSize{},
  1696  			},
  1697  			shouldErr: false,
  1698  		},
  1699  	}
  1700  
  1701  	ctx := context.Background()
  1702  
  1703  	for i, tt := range tests {
  1704  		i := i
  1705  		tt := tt
  1706  
  1707  		t.Run(tt.name, func(t *testing.T) {
  1708  			t.Parallel()
  1709  
  1710  			c := testutil.BuildCluster(t, testutil.TestClusterConfig{
  1711  				Cluster: &vtadminpb.Cluster{
  1712  					Id:   fmt.Sprintf("c%d", i),
  1713  					Name: fmt.Sprintf("cluster%d", i),
  1714  				},
  1715  				VtctldClient: tt.vtctld,
  1716  				Tablets:      []*vtadminpb.Tablet{tt.tablet},
  1717  				DBConfig:     testutil.Dbcfg{},
  1718  			})
  1719  
  1720  			schema, err := c.GetSchema(ctx, "testkeyspace", cluster.GetSchemaOptions{
  1721  				BaseRequest: tt.req,
  1722  			})
  1723  			if tt.shouldErr {
  1724  				assert.Error(t, err)
  1725  
  1726  				return
  1727  			}
  1728  
  1729  			assert.NoError(t, err)
  1730  			assert.Equal(t, tt.expected, schema)
  1731  		})
  1732  	}
  1733  
  1734  	t.Run("does not modify passed-in request", func(t *testing.T) {
  1735  		t.Parallel()
  1736  
  1737  		vtctld := &fakevtctldclient.VtctldClient{
  1738  			GetSchemaResults: map[string]struct {
  1739  				Response *vtctldatapb.GetSchemaResponse
  1740  				Error    error
  1741  			}{
  1742  				"zone1-0000000100": {
  1743  					Response: &vtctldatapb.GetSchemaResponse{},
  1744  				},
  1745  			},
  1746  		}
  1747  
  1748  		req := &vtctldatapb.GetSchemaRequest{
  1749  			TabletAlias: &topodatapb.TabletAlias{
  1750  				Cell: "otherzone",
  1751  				Uid:  500,
  1752  			},
  1753  		}
  1754  		tablet := &vtadminpb.Tablet{
  1755  			Tablet: &topodatapb.Tablet{
  1756  				Alias: &topodatapb.TabletAlias{
  1757  					Cell: "zone1",
  1758  					Uid:  100,
  1759  				},
  1760  			},
  1761  		}
  1762  
  1763  		c := testutil.BuildCluster(t, testutil.TestClusterConfig{
  1764  			Cluster: &vtadminpb.Cluster{
  1765  				Id:   "c0",
  1766  				Name: "cluster0",
  1767  			},
  1768  			VtctldClient: vtctld,
  1769  		})
  1770  
  1771  		_, _ = c.GetSchema(ctx, "testkeyspace", cluster.GetSchemaOptions{
  1772  			BaseRequest: req,
  1773  		})
  1774  
  1775  		assert.NotEqual(t, req.TabletAlias, tablet.Tablet.Alias, "expected GetSchema to not modify original request object")
  1776  	})
  1777  
  1778  	t.Run("size aggregation", func(t *testing.T) {
  1779  		t.Parallel()
  1780  
  1781  		tests := []struct {
  1782  			name      string
  1783  			cfg       testutil.TestClusterConfig
  1784  			keyspace  string
  1785  			opts      cluster.GetSchemaOptions
  1786  			expected  *vtadminpb.Schema
  1787  			shouldErr bool
  1788  		}{
  1789  			{
  1790  				name: "success",
  1791  				cfg: testutil.TestClusterConfig{
  1792  					Cluster: &vtadminpb.Cluster{
  1793  						Id:   "c0",
  1794  						Name: "cluster0",
  1795  					},
  1796  					Tablets: []*vtadminpb.Tablet{
  1797  						{
  1798  							Tablet: &topodatapb.Tablet{
  1799  								Alias: &topodatapb.TabletAlias{
  1800  									Cell: "zone1",
  1801  									Uid:  100,
  1802  								},
  1803  								Keyspace: "testkeyspace",
  1804  								Shard:    "-80",
  1805  							},
  1806  							State: vtadminpb.Tablet_SERVING,
  1807  						},
  1808  						{
  1809  							Tablet: &topodatapb.Tablet{
  1810  								Alias: &topodatapb.TabletAlias{
  1811  									Cell: "zone1",
  1812  									Uid:  200,
  1813  								},
  1814  								Keyspace: "testkeyspace",
  1815  								Shard:    "80-",
  1816  							},
  1817  							State: vtadminpb.Tablet_SERVING,
  1818  						},
  1819  					},
  1820  					VtctldClient: &fakevtctldclient.VtctldClient{
  1821  						FindAllShardsInKeyspaceResults: map[string]struct {
  1822  							Response *vtctldatapb.FindAllShardsInKeyspaceResponse
  1823  							Error    error
  1824  						}{
  1825  							"testkeyspace": {
  1826  								Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
  1827  									Shards: map[string]*vtctldatapb.Shard{
  1828  										"-80": {
  1829  											Name: "-80",
  1830  											Shard: &topodatapb.Shard{
  1831  												IsPrimaryServing: true,
  1832  												PrimaryAlias: &topodatapb.TabletAlias{
  1833  													Cell: "zone1",
  1834  													Uid:  100,
  1835  												},
  1836  											},
  1837  										},
  1838  										"80-": {
  1839  											Name: "80-",
  1840  											Shard: &topodatapb.Shard{
  1841  												IsPrimaryServing: true,
  1842  												PrimaryAlias: &topodatapb.TabletAlias{
  1843  													Cell: "zone1",
  1844  													Uid:  200,
  1845  												},
  1846  											},
  1847  										},
  1848  										"-": {
  1849  											Name: "-",
  1850  											Shard: &topodatapb.Shard{
  1851  												IsPrimaryServing: false,
  1852  											},
  1853  										},
  1854  									},
  1855  								},
  1856  							},
  1857  						},
  1858  						GetSchemaResults: map[string]struct {
  1859  							Response *vtctldatapb.GetSchemaResponse
  1860  							Error    error
  1861  						}{
  1862  							"zone1-0000000100": {
  1863  								Response: &vtctldatapb.GetSchemaResponse{
  1864  									Schema: &tabletmanagerdatapb.SchemaDefinition{
  1865  										DatabaseSchema: "CREATE DATABASE vt_testkeyspcae",
  1866  										TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  1867  											{
  1868  												Name:       "foo",
  1869  												Schema:     "CREATE TABLE foo (\n\tid INT(11) NOT NULL\n) ENGINE=InnoDB",
  1870  												DataLength: 100,
  1871  												RowCount:   5,
  1872  											},
  1873  										},
  1874  									},
  1875  								},
  1876  							},
  1877  							"zone1-0000000200": {
  1878  								Response: &vtctldatapb.GetSchemaResponse{
  1879  									Schema: &tabletmanagerdatapb.SchemaDefinition{
  1880  										DatabaseSchema: "CREATE DATABASE vt_testkeyspcae",
  1881  										TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  1882  											{
  1883  												Name:       "foo",
  1884  												Schema:     "CREATE TABLE foo (\n\tid INT(11) NOT NULL\n) ENGINE=InnoDB",
  1885  												DataLength: 200,
  1886  												RowCount:   420,
  1887  											},
  1888  										},
  1889  									},
  1890  								},
  1891  							},
  1892  						},
  1893  					},
  1894  				},
  1895  				keyspace: "testkeyspace",
  1896  				opts: cluster.GetSchemaOptions{
  1897  					TableSizeOptions: &vtadminpb.GetSchemaTableSizeOptions{
  1898  						AggregateSizes: true,
  1899  					},
  1900  				},
  1901  				expected: &vtadminpb.Schema{
  1902  					Cluster: &vtadminpb.Cluster{
  1903  						Id:   "c0",
  1904  						Name: "cluster0",
  1905  					},
  1906  					Keyspace: "testkeyspace",
  1907  					TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  1908  						{
  1909  							Name:       "foo",
  1910  							Schema:     "CREATE TABLE foo (\n\tid INT(11) NOT NULL\n) ENGINE=InnoDB",
  1911  							DataLength: 0,
  1912  							RowCount:   0,
  1913  						},
  1914  					},
  1915  					TableSizes: map[string]*vtadminpb.Schema_TableSize{
  1916  						"foo": {
  1917  							DataLength: 100 + 200,
  1918  							RowCount:   5 + 420,
  1919  							ByShard: map[string]*vtadminpb.Schema_ShardTableSize{
  1920  								"-80": {
  1921  									DataLength: 100,
  1922  									RowCount:   5,
  1923  								},
  1924  								"80-": {
  1925  									DataLength: 200,
  1926  									RowCount:   420,
  1927  								},
  1928  							},
  1929  						},
  1930  					},
  1931  				},
  1932  				shouldErr: false,
  1933  			},
  1934  			{
  1935  				name: "include nonserving shards",
  1936  				cfg: testutil.TestClusterConfig{
  1937  					Cluster: &vtadminpb.Cluster{
  1938  						Id:   "c0",
  1939  						Name: "cluster0",
  1940  					},
  1941  					Tablets: []*vtadminpb.Tablet{
  1942  						{
  1943  							Tablet: &topodatapb.Tablet{
  1944  								Alias: &topodatapb.TabletAlias{
  1945  									Cell: "zone1",
  1946  									Uid:  100,
  1947  								},
  1948  								Keyspace: "testkeyspace",
  1949  								Shard:    "-80",
  1950  							},
  1951  							State: vtadminpb.Tablet_SERVING,
  1952  						},
  1953  						{
  1954  							Tablet: &topodatapb.Tablet{
  1955  								Alias: &topodatapb.TabletAlias{
  1956  									Cell: "zone1",
  1957  									Uid:  200,
  1958  								},
  1959  								Keyspace: "testkeyspace",
  1960  								Shard:    "80-",
  1961  							},
  1962  							State: vtadminpb.Tablet_SERVING,
  1963  						},
  1964  						{
  1965  							Tablet: &topodatapb.Tablet{
  1966  								Alias: &topodatapb.TabletAlias{
  1967  									Cell: "zone1",
  1968  									Uid:  300,
  1969  								},
  1970  								Keyspace: "testkeyspace",
  1971  								Shard:    "-",
  1972  							},
  1973  							State: vtadminpb.Tablet_SERVING,
  1974  						},
  1975  					},
  1976  					VtctldClient: &fakevtctldclient.VtctldClient{
  1977  						FindAllShardsInKeyspaceResults: map[string]struct {
  1978  							Response *vtctldatapb.FindAllShardsInKeyspaceResponse
  1979  							Error    error
  1980  						}{
  1981  							"testkeyspace": {
  1982  								Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
  1983  									Shards: map[string]*vtctldatapb.Shard{
  1984  										"-80": {
  1985  											Name: "-80",
  1986  											Shard: &topodatapb.Shard{
  1987  												IsPrimaryServing: true,
  1988  											},
  1989  										},
  1990  										"80-": {
  1991  											Name: "80-",
  1992  											Shard: &topodatapb.Shard{
  1993  												IsPrimaryServing: true,
  1994  											},
  1995  										},
  1996  										"-": {
  1997  											Name: "-",
  1998  											Shard: &topodatapb.Shard{
  1999  												IsPrimaryServing: false,
  2000  											},
  2001  										},
  2002  									},
  2003  								},
  2004  							},
  2005  						},
  2006  						GetSchemaResults: map[string]struct {
  2007  							Response *vtctldatapb.GetSchemaResponse
  2008  							Error    error
  2009  						}{
  2010  							"zone1-0000000100": {
  2011  								Response: &vtctldatapb.GetSchemaResponse{
  2012  									Schema: &tabletmanagerdatapb.SchemaDefinition{
  2013  										DatabaseSchema: "CREATE DATABASE vt_testkeyspcae",
  2014  										TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2015  											{
  2016  												Name:       "foo",
  2017  												Schema:     "CREATE TABLE foo (\n\tid INT(11) NOT NULL\n) ENGINE=InnoDB",
  2018  												DataLength: 100,
  2019  												RowCount:   5,
  2020  											},
  2021  										},
  2022  									},
  2023  								},
  2024  							},
  2025  							"zone1-0000000200": {
  2026  								Response: &vtctldatapb.GetSchemaResponse{
  2027  									Schema: &tabletmanagerdatapb.SchemaDefinition{
  2028  										DatabaseSchema: "CREATE DATABASE vt_testkeyspcae",
  2029  										TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2030  											{
  2031  												Name:       "foo",
  2032  												Schema:     "CREATE TABLE foo (\n\tid INT(11) NOT NULL\n) ENGINE=InnoDB",
  2033  												DataLength: 200,
  2034  												RowCount:   420,
  2035  											},
  2036  										},
  2037  									},
  2038  								},
  2039  							},
  2040  							"zone1-0000000300": {
  2041  								Response: &vtctldatapb.GetSchemaResponse{
  2042  									Schema: &tabletmanagerdatapb.SchemaDefinition{
  2043  										DatabaseSchema: "CREATE DATABASE vt_testkeyspcae",
  2044  										TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2045  											{
  2046  												Name:       "foo",
  2047  												Schema:     "CREATE TABLE foo (\n\tid INT(11) NOT NULL\n) ENGINE=InnoDB",
  2048  												DataLength: 1000,
  2049  												RowCount:   200,
  2050  											},
  2051  										},
  2052  									},
  2053  								},
  2054  							},
  2055  						},
  2056  					},
  2057  				},
  2058  				keyspace: "testkeyspace",
  2059  				opts: cluster.GetSchemaOptions{
  2060  					TableSizeOptions: &vtadminpb.GetSchemaTableSizeOptions{
  2061  						AggregateSizes:          true,
  2062  						IncludeNonServingShards: true,
  2063  					},
  2064  				},
  2065  				expected: &vtadminpb.Schema{
  2066  					Cluster: &vtadminpb.Cluster{
  2067  						Id:   "c0",
  2068  						Name: "cluster0",
  2069  					},
  2070  					Keyspace: "testkeyspace",
  2071  					TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2072  						{
  2073  							Name:       "foo",
  2074  							Schema:     "CREATE TABLE foo (\n\tid INT(11) NOT NULL\n) ENGINE=InnoDB",
  2075  							DataLength: 0,
  2076  							RowCount:   0,
  2077  						},
  2078  					},
  2079  					TableSizes: map[string]*vtadminpb.Schema_TableSize{
  2080  						"foo": {
  2081  							DataLength: 100 + 200 + 1000,
  2082  							RowCount:   5 + 420 + 200,
  2083  							ByShard: map[string]*vtadminpb.Schema_ShardTableSize{
  2084  								"-80": {
  2085  									DataLength: 100,
  2086  									RowCount:   5,
  2087  								},
  2088  								"80-": {
  2089  									DataLength: 200,
  2090  									RowCount:   420,
  2091  								},
  2092  								"-": {
  2093  									DataLength: 1000,
  2094  									RowCount:   200,
  2095  								},
  2096  							},
  2097  						},
  2098  					},
  2099  				},
  2100  				shouldErr: false,
  2101  			},
  2102  			{
  2103  				name: "no serving tablets found for shard",
  2104  				cfg: testutil.TestClusterConfig{
  2105  					Cluster: &vtadminpb.Cluster{
  2106  						Id:   "c0",
  2107  						Name: "cluster0",
  2108  					},
  2109  					Tablets: []*vtadminpb.Tablet{
  2110  						{
  2111  							Tablet: &topodatapb.Tablet{
  2112  								Alias: &topodatapb.TabletAlias{
  2113  									Cell: "zone1",
  2114  									Uid:  100,
  2115  								},
  2116  								Keyspace: "testkeyspace",
  2117  								Shard:    "-80",
  2118  							},
  2119  							State: vtadminpb.Tablet_NOT_SERVING,
  2120  						},
  2121  						{
  2122  							Tablet: &topodatapb.Tablet{
  2123  								Alias: &topodatapb.TabletAlias{
  2124  									Cell: "zone1",
  2125  									Uid:  200,
  2126  								},
  2127  								Keyspace: "testkeyspace",
  2128  								Shard:    "80-",
  2129  							},
  2130  							State: vtadminpb.Tablet_SERVING,
  2131  						},
  2132  					},
  2133  					VtctldClient: &fakevtctldclient.VtctldClient{
  2134  						FindAllShardsInKeyspaceResults: map[string]struct {
  2135  							Response *vtctldatapb.FindAllShardsInKeyspaceResponse
  2136  							Error    error
  2137  						}{
  2138  							"testkeyspace": {
  2139  								Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
  2140  									Shards: map[string]*vtctldatapb.Shard{
  2141  										"-80": {
  2142  											Name: "-80",
  2143  											Shard: &topodatapb.Shard{
  2144  												IsPrimaryServing: true,
  2145  												PrimaryAlias: &topodatapb.TabletAlias{
  2146  													Cell: "zone1",
  2147  													Uid:  100,
  2148  												},
  2149  											},
  2150  										},
  2151  										"80-": {
  2152  											Name: "80-",
  2153  											Shard: &topodatapb.Shard{
  2154  												IsPrimaryServing: true,
  2155  												PrimaryAlias: &topodatapb.TabletAlias{
  2156  													Cell: "zone1",
  2157  													Uid:  200,
  2158  												},
  2159  											},
  2160  										},
  2161  										"-": {
  2162  											Name: "-",
  2163  											Shard: &topodatapb.Shard{
  2164  												IsPrimaryServing: false,
  2165  											},
  2166  										},
  2167  									},
  2168  								},
  2169  							},
  2170  						},
  2171  						GetSchemaResults: map[string]struct {
  2172  							Response *vtctldatapb.GetSchemaResponse
  2173  							Error    error
  2174  						}{
  2175  							"zone1-0000000100": {
  2176  								Response: &vtctldatapb.GetSchemaResponse{
  2177  									Schema: &tabletmanagerdatapb.SchemaDefinition{
  2178  										DatabaseSchema: "CREATE DATABASE vt_testkeyspcae",
  2179  										TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2180  											{
  2181  												Name:       "foo",
  2182  												Schema:     "CREATE TABLE foo (\n\tid INT(11) NOT NULL\n) ENGINE=InnoDB",
  2183  												DataLength: 100,
  2184  												RowCount:   5,
  2185  											},
  2186  										},
  2187  									},
  2188  								},
  2189  							},
  2190  							"zone1-0000000200": {
  2191  								Response: &vtctldatapb.GetSchemaResponse{
  2192  									Schema: &tabletmanagerdatapb.SchemaDefinition{
  2193  										DatabaseSchema: "CREATE DATABASE vt_testkeyspcae",
  2194  										TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2195  											{
  2196  												Name:       "foo",
  2197  												Schema:     "CREATE TABLE foo (\n\tid INT(11) NOT NULL\n) ENGINE=InnoDB",
  2198  												DataLength: 200,
  2199  												RowCount:   420,
  2200  											},
  2201  										},
  2202  									},
  2203  								},
  2204  							},
  2205  						},
  2206  					},
  2207  				},
  2208  				keyspace: "testkeyspace",
  2209  				opts: cluster.GetSchemaOptions{
  2210  					TableSizeOptions: &vtadminpb.GetSchemaTableSizeOptions{
  2211  						AggregateSizes: true,
  2212  					},
  2213  				},
  2214  				expected:  nil,
  2215  				shouldErr: true,
  2216  			},
  2217  			{
  2218  				name: "ignore TableNamesOnly",
  2219  				cfg: testutil.TestClusterConfig{
  2220  					Cluster: &vtadminpb.Cluster{
  2221  						Id:   "c0",
  2222  						Name: "cluster0",
  2223  					},
  2224  					Tablets: []*vtadminpb.Tablet{
  2225  						{
  2226  							Tablet: &topodatapb.Tablet{
  2227  								Alias: &topodatapb.TabletAlias{
  2228  									Cell: "zone1",
  2229  									Uid:  100,
  2230  								},
  2231  								Keyspace: "testkeyspace",
  2232  								Shard:    "-80",
  2233  							},
  2234  							State: vtadminpb.Tablet_SERVING,
  2235  						},
  2236  						{
  2237  							Tablet: &topodatapb.Tablet{
  2238  								Alias: &topodatapb.TabletAlias{
  2239  									Cell: "zone1",
  2240  									Uid:  200,
  2241  								},
  2242  								Keyspace: "testkeyspace",
  2243  								Shard:    "80-",
  2244  							},
  2245  							State: vtadminpb.Tablet_SERVING,
  2246  						},
  2247  					},
  2248  					VtctldClient: &fakevtctldclient.VtctldClient{
  2249  						FindAllShardsInKeyspaceResults: map[string]struct {
  2250  							Response *vtctldatapb.FindAllShardsInKeyspaceResponse
  2251  							Error    error
  2252  						}{
  2253  							"testkeyspace": {
  2254  								Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
  2255  									Shards: map[string]*vtctldatapb.Shard{
  2256  										"-80": {
  2257  											Name: "-80",
  2258  											Shard: &topodatapb.Shard{
  2259  												IsPrimaryServing: true,
  2260  												PrimaryAlias: &topodatapb.TabletAlias{
  2261  													Cell: "zone1",
  2262  													Uid:  100,
  2263  												},
  2264  											},
  2265  										},
  2266  										"80-": {
  2267  											Name: "80-",
  2268  											Shard: &topodatapb.Shard{
  2269  												IsPrimaryServing: true,
  2270  												PrimaryAlias: &topodatapb.TabletAlias{
  2271  													Cell: "zone1",
  2272  													Uid:  200,
  2273  												},
  2274  											},
  2275  										},
  2276  										"-": {
  2277  											Name: "-",
  2278  											Shard: &topodatapb.Shard{
  2279  												IsPrimaryServing: false,
  2280  											},
  2281  										},
  2282  									},
  2283  								},
  2284  							},
  2285  						},
  2286  						GetSchemaResults: map[string]struct {
  2287  							Response *vtctldatapb.GetSchemaResponse
  2288  							Error    error
  2289  						}{
  2290  							"zone1-0000000100": {
  2291  								Response: &vtctldatapb.GetSchemaResponse{
  2292  									Schema: &tabletmanagerdatapb.SchemaDefinition{
  2293  										DatabaseSchema: "CREATE DATABASE vt_testkeyspcae",
  2294  										TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2295  											{
  2296  												Name:       "foo",
  2297  												Schema:     "CREATE TABLE foo (\n\tid INT(11) NOT NULL\n) ENGINE=InnoDB",
  2298  												DataLength: 100,
  2299  												RowCount:   5,
  2300  											},
  2301  										},
  2302  									},
  2303  								},
  2304  							},
  2305  							"zone1-0000000200": {
  2306  								Response: &vtctldatapb.GetSchemaResponse{
  2307  									Schema: &tabletmanagerdatapb.SchemaDefinition{
  2308  										DatabaseSchema: "CREATE DATABASE vt_testkeyspcae",
  2309  										TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2310  											{
  2311  												Name:       "foo",
  2312  												Schema:     "CREATE TABLE foo (\n\tid INT(11) NOT NULL\n) ENGINE=InnoDB",
  2313  												DataLength: 200,
  2314  												RowCount:   420,
  2315  											},
  2316  										},
  2317  									},
  2318  								},
  2319  							},
  2320  						},
  2321  					},
  2322  				},
  2323  				keyspace: "testkeyspace",
  2324  				opts: cluster.GetSchemaOptions{
  2325  					BaseRequest: &vtctldatapb.GetSchemaRequest{
  2326  						TableNamesOnly: true, // Just checking things to blow up if this gets set.
  2327  					},
  2328  					TableSizeOptions: &vtadminpb.GetSchemaTableSizeOptions{
  2329  						AggregateSizes: true,
  2330  					},
  2331  				},
  2332  				expected: &vtadminpb.Schema{
  2333  					Cluster: &vtadminpb.Cluster{
  2334  						Id:   "c0",
  2335  						Name: "cluster0",
  2336  					},
  2337  					Keyspace: "testkeyspace",
  2338  					TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2339  						{
  2340  							Name:       "foo",
  2341  							Schema:     "CREATE TABLE foo (\n\tid INT(11) NOT NULL\n) ENGINE=InnoDB",
  2342  							DataLength: 0,
  2343  							RowCount:   0,
  2344  						},
  2345  					},
  2346  					TableSizes: map[string]*vtadminpb.Schema_TableSize{
  2347  						"foo": {
  2348  							DataLength: 100 + 200,
  2349  							RowCount:   5 + 420,
  2350  							ByShard: map[string]*vtadminpb.Schema_ShardTableSize{
  2351  								"-80": {
  2352  									DataLength: 100,
  2353  									RowCount:   5,
  2354  								},
  2355  								"80-": {
  2356  									DataLength: 200,
  2357  									RowCount:   420,
  2358  								},
  2359  							},
  2360  						},
  2361  					},
  2362  				},
  2363  				shouldErr: false,
  2364  			},
  2365  			{
  2366  				name: "single GetSchema error fails the request",
  2367  				cfg: testutil.TestClusterConfig{
  2368  					Cluster: &vtadminpb.Cluster{
  2369  						Id:   "c0",
  2370  						Name: "cluster0",
  2371  					},
  2372  					Tablets: []*vtadminpb.Tablet{
  2373  						{
  2374  							Tablet: &topodatapb.Tablet{
  2375  								Alias: &topodatapb.TabletAlias{
  2376  									Cell: "zone1",
  2377  									Uid:  100,
  2378  								},
  2379  								Keyspace: "testkeyspace",
  2380  								Shard:    "-80",
  2381  							},
  2382  							State: vtadminpb.Tablet_SERVING,
  2383  						},
  2384  						{
  2385  							Tablet: &topodatapb.Tablet{
  2386  								Alias: &topodatapb.TabletAlias{
  2387  									Cell: "zone1",
  2388  									Uid:  200,
  2389  								},
  2390  								Keyspace: "testkeyspace",
  2391  								Shard:    "80-",
  2392  							},
  2393  							State: vtadminpb.Tablet_SERVING,
  2394  						},
  2395  					},
  2396  					VtctldClient: &fakevtctldclient.VtctldClient{
  2397  						FindAllShardsInKeyspaceResults: map[string]struct {
  2398  							Response *vtctldatapb.FindAllShardsInKeyspaceResponse
  2399  							Error    error
  2400  						}{
  2401  							"testkeyspace": {
  2402  								Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
  2403  									Shards: map[string]*vtctldatapb.Shard{
  2404  										"-80": {
  2405  											Name: "-80",
  2406  											Shard: &topodatapb.Shard{
  2407  												IsPrimaryServing: true,
  2408  												PrimaryAlias: &topodatapb.TabletAlias{
  2409  													Cell: "zone1",
  2410  													Uid:  100,
  2411  												},
  2412  											},
  2413  										},
  2414  										"80-": {
  2415  											Name: "80-",
  2416  											Shard: &topodatapb.Shard{
  2417  												IsPrimaryServing: true,
  2418  												PrimaryAlias: &topodatapb.TabletAlias{
  2419  													Cell: "zone1",
  2420  													Uid:  200,
  2421  												},
  2422  											},
  2423  										},
  2424  										"-": {
  2425  											Name: "-",
  2426  											Shard: &topodatapb.Shard{
  2427  												IsPrimaryServing: false,
  2428  											},
  2429  										},
  2430  									},
  2431  								},
  2432  							},
  2433  						},
  2434  						GetSchemaResults: map[string]struct {
  2435  							Response *vtctldatapb.GetSchemaResponse
  2436  							Error    error
  2437  						}{
  2438  							"zone1-0000000100": {
  2439  								Response: &vtctldatapb.GetSchemaResponse{
  2440  									Schema: &tabletmanagerdatapb.SchemaDefinition{
  2441  										DatabaseSchema: "CREATE DATABASE vt_testkeyspcae",
  2442  										TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2443  											{
  2444  												Name:       "foo",
  2445  												Schema:     "CREATE TABLE foo (\n\tid INT(11) NOT NULL\n) ENGINE=InnoDB",
  2446  												DataLength: 100,
  2447  												RowCount:   5,
  2448  											},
  2449  										},
  2450  									},
  2451  								},
  2452  							},
  2453  							"zone1-0000000200": {
  2454  								Error: assert.AnError,
  2455  							},
  2456  						},
  2457  					},
  2458  				},
  2459  				keyspace: "testkeyspace",
  2460  				opts: cluster.GetSchemaOptions{
  2461  					TableSizeOptions: &vtadminpb.GetSchemaTableSizeOptions{
  2462  						AggregateSizes: true,
  2463  					},
  2464  				},
  2465  				expected:  nil,
  2466  				shouldErr: true,
  2467  			},
  2468  			{
  2469  				name: "FindAllShardsInKeyspace error",
  2470  				cfg: testutil.TestClusterConfig{
  2471  					Cluster: &vtadminpb.Cluster{
  2472  						Id:   "c0",
  2473  						Name: "cluster0",
  2474  					},
  2475  					Tablets: []*vtadminpb.Tablet{
  2476  						{
  2477  							Tablet: &topodatapb.Tablet{
  2478  								Alias: &topodatapb.TabletAlias{
  2479  									Cell: "zone1",
  2480  									Uid:  100,
  2481  								},
  2482  								Keyspace: "testkeyspace",
  2483  								Shard:    "-80",
  2484  							},
  2485  							State: vtadminpb.Tablet_SERVING,
  2486  						},
  2487  						{
  2488  							Tablet: &topodatapb.Tablet{
  2489  								Alias: &topodatapb.TabletAlias{
  2490  									Cell: "zone1",
  2491  									Uid:  200,
  2492  								},
  2493  								Keyspace: "testkeyspace",
  2494  								Shard:    "80-",
  2495  							},
  2496  							State: vtadminpb.Tablet_SERVING,
  2497  						},
  2498  					},
  2499  					VtctldClient: &fakevtctldclient.VtctldClient{
  2500  						FindAllShardsInKeyspaceResults: map[string]struct {
  2501  							Response *vtctldatapb.FindAllShardsInKeyspaceResponse
  2502  							Error    error
  2503  						}{
  2504  							"testkeyspace": {
  2505  								Error: assert.AnError,
  2506  							},
  2507  						},
  2508  						GetSchemaResults: map[string]struct {
  2509  							Response *vtctldatapb.GetSchemaResponse
  2510  							Error    error
  2511  						}{
  2512  							"zone1-0000000100": {
  2513  								Response: &vtctldatapb.GetSchemaResponse{
  2514  									Schema: &tabletmanagerdatapb.SchemaDefinition{
  2515  										DatabaseSchema: "CREATE DATABASE vt_testkeyspcae",
  2516  										TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2517  											{
  2518  												Name:       "foo",
  2519  												Schema:     "CREATE TABLE foo (\n\tid INT(11) NOT NULL\n) ENGINE=InnoDB",
  2520  												DataLength: 100,
  2521  												RowCount:   5,
  2522  											},
  2523  										},
  2524  									},
  2525  								},
  2526  							},
  2527  							"zone1-0000000200": {
  2528  								Response: &vtctldatapb.GetSchemaResponse{
  2529  									Schema: &tabletmanagerdatapb.SchemaDefinition{
  2530  										DatabaseSchema: "CREATE DATABASE vt_testkeyspcae",
  2531  										TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2532  											{
  2533  												Name:       "foo",
  2534  												Schema:     "CREATE TABLE foo (\n\tid INT(11) NOT NULL\n) ENGINE=InnoDB",
  2535  												DataLength: 200,
  2536  												RowCount:   420,
  2537  											},
  2538  										},
  2539  									},
  2540  								},
  2541  							},
  2542  						},
  2543  					},
  2544  				},
  2545  				keyspace: "testkeyspace",
  2546  				opts: cluster.GetSchemaOptions{
  2547  					TableSizeOptions: &vtadminpb.GetSchemaTableSizeOptions{
  2548  						AggregateSizes: true,
  2549  					},
  2550  				},
  2551  				expected:  nil,
  2552  				shouldErr: true,
  2553  			},
  2554  			{
  2555  				name: "tablet filtering checks keyspace field",
  2556  				cfg: testutil.TestClusterConfig{
  2557  					Cluster: &vtadminpb.Cluster{
  2558  						Id:   "c0",
  2559  						Name: "cluster0",
  2560  					},
  2561  					Tablets: []*vtadminpb.Tablet{
  2562  						{
  2563  							Tablet: &topodatapb.Tablet{
  2564  								Alias: &topodatapb.TabletAlias{
  2565  									Cell: "zone1",
  2566  									Uid:  100,
  2567  								},
  2568  								Keyspace: "testkeyspace",
  2569  								Shard:    "-80",
  2570  							},
  2571  							State: vtadminpb.Tablet_SERVING,
  2572  						},
  2573  						{
  2574  							Tablet: &topodatapb.Tablet{
  2575  								Alias: &topodatapb.TabletAlias{
  2576  									Cell: "zone1",
  2577  									Uid:  300,
  2578  								},
  2579  								// Note this is for another keyspace, so we fail to find a tablet for testkeyspace/-80.
  2580  								Keyspace: "otherkeyspace",
  2581  								Shard:    "80-",
  2582  							},
  2583  							State: vtadminpb.Tablet_SERVING,
  2584  						},
  2585  					},
  2586  					VtctldClient: &fakevtctldclient.VtctldClient{
  2587  						FindAllShardsInKeyspaceResults: map[string]struct {
  2588  							Response *vtctldatapb.FindAllShardsInKeyspaceResponse
  2589  							Error    error
  2590  						}{
  2591  							"testkeyspace": {
  2592  								Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
  2593  									Shards: map[string]*vtctldatapb.Shard{
  2594  										"-80": {
  2595  											Name: "-80",
  2596  											Shard: &topodatapb.Shard{
  2597  												IsPrimaryServing: true,
  2598  												PrimaryAlias: &topodatapb.TabletAlias{
  2599  													Cell: "zone1",
  2600  													Uid:  100,
  2601  												},
  2602  											},
  2603  										},
  2604  										"80-": {
  2605  											Name: "80-",
  2606  											Shard: &topodatapb.Shard{
  2607  												IsPrimaryServing: true,
  2608  												PrimaryAlias: &topodatapb.TabletAlias{
  2609  													Cell: "zone1",
  2610  													Uid:  200,
  2611  												},
  2612  											},
  2613  										},
  2614  									},
  2615  								},
  2616  							},
  2617  						},
  2618  						GetSchemaResults: map[string]struct {
  2619  							Response *vtctldatapb.GetSchemaResponse
  2620  							Error    error
  2621  						}{
  2622  							"zone1-0000000100": {
  2623  								Response: &vtctldatapb.GetSchemaResponse{
  2624  									Schema: &tabletmanagerdatapb.SchemaDefinition{
  2625  										DatabaseSchema: "CREATE DATABASE vt_testkeyspcae",
  2626  										TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2627  											{
  2628  												Name:       "foo",
  2629  												Schema:     "CREATE TABLE foo (\n\tid INT(11) NOT NULL\n) ENGINE=InnoDB",
  2630  												DataLength: 100,
  2631  												RowCount:   5,
  2632  											},
  2633  										},
  2634  									},
  2635  								},
  2636  							},
  2637  							"zone1-0000000200": {
  2638  								Response: &vtctldatapb.GetSchemaResponse{
  2639  									Schema: &tabletmanagerdatapb.SchemaDefinition{
  2640  										DatabaseSchema: "CREATE DATABASE vt_testkeyspcae",
  2641  										TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2642  											{
  2643  												Name:       "foo",
  2644  												Schema:     "CREATE TABLE foo (\n\tid INT(11) NOT NULL\n) ENGINE=InnoDB",
  2645  												DataLength: 200,
  2646  												RowCount:   420,
  2647  											},
  2648  										},
  2649  									},
  2650  								},
  2651  							},
  2652  							"zone1-0000000300": {
  2653  								Response: &vtctldatapb.GetSchemaResponse{
  2654  									Schema: &tabletmanagerdatapb.SchemaDefinition{
  2655  										DatabaseSchema: "CREATE DATABASE vt_otherkeyspacekeyspcae",
  2656  										TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  2657  											{
  2658  												Name:       "bar",
  2659  												Schema:     "CREATE TABLE bar (\n\tid INT(11) NOT NULL\n) ENGINE=InnoDB",
  2660  												DataLength: 101,
  2661  												RowCount:   202,
  2662  											},
  2663  										},
  2664  									},
  2665  								},
  2666  							},
  2667  						},
  2668  					},
  2669  				},
  2670  				keyspace: "testkeyspace",
  2671  				opts: cluster.GetSchemaOptions{
  2672  					TableSizeOptions: &vtadminpb.GetSchemaTableSizeOptions{
  2673  						AggregateSizes: true,
  2674  					},
  2675  				},
  2676  				expected:  nil,
  2677  				shouldErr: true,
  2678  			},
  2679  		}
  2680  
  2681  		for _, tt := range tests {
  2682  			tt := tt
  2683  
  2684  			t.Run(tt.name, func(t *testing.T) {
  2685  				t.Parallel()
  2686  
  2687  				if tt.keyspace == "" {
  2688  					t.SkipNow()
  2689  				}
  2690  
  2691  				c := testutil.BuildCluster(t, tt.cfg)
  2692  				schema, err := c.GetSchema(ctx, tt.keyspace, tt.opts)
  2693  				if tt.shouldErr {
  2694  					assert.Error(t, err)
  2695  
  2696  					return
  2697  				}
  2698  
  2699  				// Clone so our mutation below doesn't trip the race detector.
  2700  				schema = proto.Clone(schema).(*vtadminpb.Schema)
  2701  
  2702  				if schema.TableDefinitions != nil {
  2703  					// For simplicity, we're going to assert only on the state
  2704  					// of the aggregated sizes (in schema.TableSizes), since the
  2705  					// TableDefinitions size values depends on tablet iteration
  2706  					// order, and that's not something we're interested in
  2707  					// coupling the implementation to.
  2708  					for _, td := range schema.TableDefinitions {
  2709  						td.DataLength = 0
  2710  						td.RowCount = 0
  2711  					}
  2712  				}
  2713  
  2714  				assert.NoError(t, err)
  2715  				testutil.AssertSchemaSlicesEqual(t, []*vtadminpb.Schema{tt.expected}, []*vtadminpb.Schema{schema})
  2716  			})
  2717  		}
  2718  	})
  2719  }
  2720  
  2721  func TestGetShardReplicationPositions(t *testing.T) {
  2722  	t.Parallel()
  2723  
  2724  	ctx := context.Background()
  2725  	tests := []struct {
  2726  		name      string
  2727  		cfg       testutil.TestClusterConfig
  2728  		req       *vtadminpb.GetShardReplicationPositionsRequest
  2729  		expected  []*vtadminpb.ClusterShardReplicationPosition
  2730  		shouldErr bool
  2731  	}{
  2732  		{
  2733  			cfg: testutil.TestClusterConfig{
  2734  				Cluster: &vtadminpb.Cluster{
  2735  					Id:   "c1",
  2736  					Name: "cluster1",
  2737  				},
  2738  				VtctldClient: &fakevtctldclient.VtctldClient{
  2739  					GetKeyspaceResults: map[string]struct {
  2740  						Response *vtctldatapb.GetKeyspaceResponse
  2741  						Error    error
  2742  					}{
  2743  						"ks": {
  2744  							Response: &vtctldatapb.GetKeyspaceResponse{
  2745  								Keyspace: &vtctldatapb.Keyspace{
  2746  									Keyspace: &topodatapb.Keyspace{},
  2747  								},
  2748  							},
  2749  						},
  2750  					},
  2751  					FindAllShardsInKeyspaceResults: map[string]struct {
  2752  						Response *vtctldatapb.FindAllShardsInKeyspaceResponse
  2753  						Error    error
  2754  					}{
  2755  						"ks": {
  2756  							Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
  2757  								Shards: map[string]*vtctldatapb.Shard{
  2758  									"-": {
  2759  										Keyspace: "ks",
  2760  										Name:     "-",
  2761  										Shard:    &topodatapb.Shard{},
  2762  									},
  2763  								},
  2764  							},
  2765  						},
  2766  					},
  2767  					ShardReplicationPositionsResults: map[string]struct {
  2768  						Response *vtctldatapb.ShardReplicationPositionsResponse
  2769  						Error    error
  2770  					}{
  2771  						"ks/-": {
  2772  							Response: &vtctldatapb.ShardReplicationPositionsResponse{
  2773  								ReplicationStatuses: map[string]*replicationdatapb.Status{
  2774  									"zone1-001": {
  2775  										IoState:  int32(mysql.ReplicationStateStopped),
  2776  										SqlState: int32(mysql.ReplicationStateStopped),
  2777  										Position: "MySQL56/08d0dbbb-be29-11eb-9fea-0aafb9701138:1-109848265",
  2778  									},
  2779  									"zone1-002": { // Note: in reality other fields will be set on replicating hosts as well, but this is sufficient to illustrate in the testing.
  2780  										IoState:  int32(mysql.ReplicationStateRunning),
  2781  										SqlState: int32(mysql.ReplicationStateRunning),
  2782  										Position: "MySQL56/08d0dbbb-be29-11eb-9fea-0aafb9701138:1-109848265",
  2783  									},
  2784  									"zone1-003": {
  2785  										IoState:  int32(mysql.ReplicationStateRunning),
  2786  										SqlState: int32(mysql.ReplicationStateRunning),
  2787  										Position: "MySQL56/08d0dbbb-be29-11eb-9fea-0aafb9701138:1-109848265",
  2788  									},
  2789  								},
  2790  								TabletMap: map[string]*topodatapb.Tablet{
  2791  									"zone1-001": {
  2792  										Keyspace: "ks",
  2793  										Shard:    "-",
  2794  										Type:     topodatapb.TabletType_PRIMARY,
  2795  										Alias: &topodatapb.TabletAlias{
  2796  											Cell: "zone1",
  2797  											Uid:  1,
  2798  										},
  2799  									},
  2800  									"zone1-002": {
  2801  										Keyspace: "ks",
  2802  										Shard:    "-",
  2803  										Type:     topodatapb.TabletType_REPLICA,
  2804  										Alias: &topodatapb.TabletAlias{
  2805  											Cell: "zone1",
  2806  											Uid:  2,
  2807  										},
  2808  									},
  2809  									"zone1-003": {
  2810  										Keyspace: "ks",
  2811  										Shard:    "-",
  2812  										Type:     topodatapb.TabletType_RDONLY,
  2813  										Alias: &topodatapb.TabletAlias{
  2814  											Cell: "zone1",
  2815  											Uid:  3,
  2816  										},
  2817  									},
  2818  								},
  2819  							},
  2820  						},
  2821  					},
  2822  				},
  2823  			},
  2824  			req: &vtadminpb.GetShardReplicationPositionsRequest{
  2825  				KeyspaceShards: []string{"ks/-"},
  2826  			},
  2827  			expected: []*vtadminpb.ClusterShardReplicationPosition{
  2828  				{
  2829  					Cluster: &vtadminpb.Cluster{
  2830  						Id:   "c1",
  2831  						Name: "cluster1",
  2832  					},
  2833  					Keyspace: "ks",
  2834  					Shard:    "-",
  2835  					PositionInfo: &vtctldatapb.ShardReplicationPositionsResponse{
  2836  						ReplicationStatuses: map[string]*replicationdatapb.Status{
  2837  							"zone1-001": {
  2838  								IoState:  int32(mysql.ReplicationStateStopped),
  2839  								SqlState: int32(mysql.ReplicationStateStopped),
  2840  								Position: "MySQL56/08d0dbbb-be29-11eb-9fea-0aafb9701138:1-109848265",
  2841  							},
  2842  							"zone1-002": {
  2843  								IoState:  int32(mysql.ReplicationStateRunning),
  2844  								SqlState: int32(mysql.ReplicationStateRunning),
  2845  								Position: "MySQL56/08d0dbbb-be29-11eb-9fea-0aafb9701138:1-109848265",
  2846  							},
  2847  							"zone1-003": {
  2848  								IoState:  int32(mysql.ReplicationStateRunning),
  2849  								SqlState: int32(mysql.ReplicationStateRunning),
  2850  								Position: "MySQL56/08d0dbbb-be29-11eb-9fea-0aafb9701138:1-109848265",
  2851  							},
  2852  						},
  2853  						TabletMap: map[string]*topodatapb.Tablet{
  2854  							"zone1-001": {
  2855  								Keyspace: "ks",
  2856  								Shard:    "-",
  2857  								Type:     topodatapb.TabletType_PRIMARY,
  2858  								Alias: &topodatapb.TabletAlias{
  2859  									Cell: "zone1",
  2860  									Uid:  1,
  2861  								},
  2862  							},
  2863  							"zone1-002": {
  2864  								Keyspace: "ks",
  2865  								Shard:    "-",
  2866  								Type:     topodatapb.TabletType_REPLICA,
  2867  								Alias: &topodatapb.TabletAlias{
  2868  									Cell: "zone1",
  2869  									Uid:  2,
  2870  								},
  2871  							},
  2872  							"zone1-003": {
  2873  								Keyspace: "ks",
  2874  								Shard:    "-",
  2875  								Type:     topodatapb.TabletType_RDONLY,
  2876  								Alias: &topodatapb.TabletAlias{
  2877  									Cell: "zone1",
  2878  									Uid:  3,
  2879  								},
  2880  							},
  2881  						},
  2882  					},
  2883  				},
  2884  			},
  2885  		},
  2886  		{
  2887  			name: "error",
  2888  			cfg: testutil.TestClusterConfig{
  2889  				Cluster: &vtadminpb.Cluster{
  2890  					Id:   "c1",
  2891  					Name: "cluster1",
  2892  				},
  2893  				VtctldClient: &fakevtctldclient.VtctldClient{
  2894  					GetKeyspaceResults: map[string]struct {
  2895  						Response *vtctldatapb.GetKeyspaceResponse
  2896  						Error    error
  2897  					}{
  2898  						"ks": {
  2899  							Response: &vtctldatapb.GetKeyspaceResponse{
  2900  								Keyspace: &vtctldatapb.Keyspace{
  2901  									Keyspace: &topodatapb.Keyspace{},
  2902  								},
  2903  							},
  2904  						},
  2905  					},
  2906  					FindAllShardsInKeyspaceResults: map[string]struct {
  2907  						Response *vtctldatapb.FindAllShardsInKeyspaceResponse
  2908  						Error    error
  2909  					}{
  2910  						"ks": {
  2911  							Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
  2912  								Shards: map[string]*vtctldatapb.Shard{
  2913  									"-": {
  2914  										Keyspace: "ks",
  2915  										Name:     "-",
  2916  										Shard:    &topodatapb.Shard{},
  2917  									},
  2918  								},
  2919  							},
  2920  						},
  2921  					},
  2922  					ShardReplicationPositionsResults: map[string]struct {
  2923  						Response *vtctldatapb.ShardReplicationPositionsResponse
  2924  						Error    error
  2925  					}{
  2926  						"ks/-": {
  2927  							Error: assert.AnError,
  2928  						},
  2929  					},
  2930  				},
  2931  			},
  2932  			req: &vtadminpb.GetShardReplicationPositionsRequest{
  2933  				KeyspaceShards: []string{"ks/-"},
  2934  			},
  2935  			expected:  nil,
  2936  			shouldErr: true,
  2937  		},
  2938  	}
  2939  
  2940  	for _, tt := range tests {
  2941  		tt := tt
  2942  
  2943  		t.Run(tt.name, func(t *testing.T) {
  2944  			t.Parallel()
  2945  
  2946  			c := testutil.BuildCluster(t, tt.cfg)
  2947  
  2948  			resp, err := c.GetShardReplicationPositions(ctx, tt.req)
  2949  			if tt.shouldErr {
  2950  				assert.Error(t, err)
  2951  				return
  2952  			}
  2953  
  2954  			require.NoError(t, err)
  2955  			assert.Equal(t, tt.expected, resp)
  2956  		})
  2957  	}
  2958  }
  2959  
  2960  func TestGetVSchema(t *testing.T) {
  2961  	t.Parallel()
  2962  
  2963  	tests := []struct {
  2964  		name      string
  2965  		cfg       testutil.TestClusterConfig
  2966  		keyspace  string
  2967  		expected  *vtadminpb.VSchema
  2968  		shouldErr bool
  2969  	}{
  2970  		{
  2971  			name: "success",
  2972  			cfg: testutil.TestClusterConfig{
  2973  				Cluster: &vtadminpb.Cluster{
  2974  					Id:   "c0",
  2975  					Name: "cluster0",
  2976  				},
  2977  				VtctldClient: &fakevtctldclient.VtctldClient{
  2978  					GetVSchemaResults: map[string]struct {
  2979  						Response *vtctldatapb.GetVSchemaResponse
  2980  						Error    error
  2981  					}{
  2982  						"testkeyspace": {
  2983  							Response: &vtctldatapb.GetVSchemaResponse{
  2984  								VSchema: &vschemapb.Keyspace{Sharded: true},
  2985  							},
  2986  						},
  2987  					},
  2988  				},
  2989  			},
  2990  			keyspace: "testkeyspace",
  2991  			expected: &vtadminpb.VSchema{
  2992  				Cluster: &vtadminpb.Cluster{
  2993  					Id:   "c0",
  2994  					Name: "cluster0",
  2995  				},
  2996  				Name:    "testkeyspace",
  2997  				VSchema: &vschemapb.Keyspace{Sharded: true},
  2998  			},
  2999  			shouldErr: false,
  3000  		},
  3001  		{
  3002  			name: "error",
  3003  			cfg: testutil.TestClusterConfig{
  3004  				Cluster: &vtadminpb.Cluster{
  3005  					Id:   "c0",
  3006  					Name: "cluster0",
  3007  				},
  3008  				VtctldClient: &fakevtctldclient.VtctldClient{
  3009  					GetVSchemaResults: map[string]struct {
  3010  						Response *vtctldatapb.GetVSchemaResponse
  3011  						Error    error
  3012  					}{
  3013  						"testkeyspace": {
  3014  							Response: &vtctldatapb.GetVSchemaResponse{
  3015  								VSchema: &vschemapb.Keyspace{Sharded: true},
  3016  							},
  3017  						},
  3018  					},
  3019  				},
  3020  			},
  3021  			keyspace:  "notfound",
  3022  			expected:  nil,
  3023  			shouldErr: true,
  3024  		},
  3025  	}
  3026  
  3027  	ctx := context.Background()
  3028  
  3029  	for _, tt := range tests {
  3030  		tt := tt
  3031  
  3032  		t.Run(tt.name, func(t *testing.T) {
  3033  			t.Parallel()
  3034  
  3035  			cluster := testutil.BuildCluster(t, tt.cfg)
  3036  
  3037  			vschema, err := cluster.GetVSchema(ctx, tt.keyspace)
  3038  			if tt.shouldErr {
  3039  				assert.Error(t, err)
  3040  
  3041  				return
  3042  			}
  3043  
  3044  			assert.NoError(t, err)
  3045  			assert.Equal(t, tt.expected, vschema)
  3046  		})
  3047  	}
  3048  }
  3049  
  3050  func TestGetWorkflow(t *testing.T) {
  3051  	t.Parallel()
  3052  
  3053  	tests := []struct {
  3054  		name      string
  3055  		cfg       testutil.TestClusterConfig
  3056  		keyspace  string
  3057  		workflow  string
  3058  		opts      cluster.GetWorkflowOptions
  3059  		expected  *vtadminpb.Workflow
  3060  		shouldErr bool
  3061  	}{
  3062  		{
  3063  			name: "found",
  3064  			cfg: testutil.TestClusterConfig{
  3065  				Cluster: &vtadminpb.Cluster{
  3066  					Id:   "c1",
  3067  					Name: "cluster1",
  3068  				},
  3069  				VtctldClient: &fakevtctldclient.VtctldClient{
  3070  					GetWorkflowsResults: map[string]struct {
  3071  						Response *vtctldatapb.GetWorkflowsResponse
  3072  						Error    error
  3073  					}{
  3074  						"ks1": {
  3075  							Response: &vtctldatapb.GetWorkflowsResponse{
  3076  								Workflows: []*vtctldatapb.Workflow{
  3077  									{
  3078  										Name: "workflow1",
  3079  									},
  3080  									{
  3081  										Name: "workflow2",
  3082  									},
  3083  								},
  3084  							},
  3085  						},
  3086  					},
  3087  				},
  3088  			},
  3089  			keyspace: "ks1",
  3090  			workflow: "workflow2",
  3091  			expected: &vtadminpb.Workflow{
  3092  				Cluster: &vtadminpb.Cluster{
  3093  					Id:   "c1",
  3094  					Name: "cluster1",
  3095  				},
  3096  				Keyspace: "ks1",
  3097  				Workflow: &vtctldatapb.Workflow{
  3098  					Name: "workflow2",
  3099  				},
  3100  			},
  3101  			shouldErr: false,
  3102  		},
  3103  		{
  3104  			name: "error getting workflows",
  3105  			cfg: testutil.TestClusterConfig{
  3106  				Cluster: &vtadminpb.Cluster{
  3107  					Id:   "c1",
  3108  					Name: "cluster1",
  3109  				},
  3110  				VtctldClient: &fakevtctldclient.VtctldClient{
  3111  					GetWorkflowsResults: map[string]struct {
  3112  						Response *vtctldatapb.GetWorkflowsResponse
  3113  						Error    error
  3114  					}{
  3115  						"ks1": {
  3116  							Error: assert.AnError,
  3117  						},
  3118  					},
  3119  				},
  3120  			},
  3121  			keyspace:  "ks1",
  3122  			workflow:  "workflow2",
  3123  			expected:  nil,
  3124  			shouldErr: true,
  3125  		},
  3126  		{
  3127  			name: "no workflows found",
  3128  			cfg: testutil.TestClusterConfig{
  3129  				Cluster: &vtadminpb.Cluster{
  3130  					Id:   "c1",
  3131  					Name: "cluster1",
  3132  				},
  3133  				VtctldClient: &fakevtctldclient.VtctldClient{
  3134  					GetWorkflowsResults: map[string]struct {
  3135  						Response *vtctldatapb.GetWorkflowsResponse
  3136  						Error    error
  3137  					}{
  3138  						"ks1": {
  3139  							Response: &vtctldatapb.GetWorkflowsResponse{
  3140  								Workflows: []*vtctldatapb.Workflow{},
  3141  							},
  3142  						},
  3143  					},
  3144  				},
  3145  			},
  3146  			keyspace:  "ks1",
  3147  			workflow:  "workflow2",
  3148  			expected:  nil,
  3149  			shouldErr: true,
  3150  		},
  3151  		{
  3152  			name: "multiple workflows found",
  3153  			cfg: testutil.TestClusterConfig{
  3154  				Cluster: &vtadminpb.Cluster{
  3155  					Id:   "c1",
  3156  					Name: "cluster1",
  3157  				},
  3158  				VtctldClient: &fakevtctldclient.VtctldClient{
  3159  					GetWorkflowsResults: map[string]struct {
  3160  						Response *vtctldatapb.GetWorkflowsResponse
  3161  						Error    error
  3162  					}{
  3163  						"ks1": {
  3164  							Response: &vtctldatapb.GetWorkflowsResponse{
  3165  								Workflows: []*vtctldatapb.Workflow{
  3166  									{
  3167  										Name: "duplicate",
  3168  									},
  3169  									{
  3170  										Name: "duplicate",
  3171  									},
  3172  								},
  3173  							},
  3174  						},
  3175  					},
  3176  				},
  3177  			},
  3178  			keyspace:  "ks1",
  3179  			workflow:  "duplicate",
  3180  			expected:  nil,
  3181  			shouldErr: true,
  3182  		},
  3183  	}
  3184  
  3185  	ctx := context.Background()
  3186  
  3187  	for _, tt := range tests {
  3188  		tt := tt
  3189  
  3190  		t.Run(tt.name, func(t *testing.T) {
  3191  			t.Parallel()
  3192  
  3193  			c := testutil.BuildCluster(t, tt.cfg)
  3194  			workflow, err := c.GetWorkflow(ctx, tt.keyspace, tt.workflow, tt.opts)
  3195  			if tt.shouldErr {
  3196  				assert.Error(t, err)
  3197  
  3198  				return
  3199  			}
  3200  
  3201  			assert.NoError(t, err)
  3202  			assert.Equal(t, tt.expected, workflow)
  3203  		})
  3204  	}
  3205  }
  3206  
  3207  func TestGetWorkflows(t *testing.T) {
  3208  	t.Parallel()
  3209  
  3210  	// Note: GetWorkflows is almost entirely a passthrough to FindWorkflows, so
  3211  	// these test cases mostly just verify we're calling that function more or
  3212  	// less correctly.
  3213  
  3214  	tests := []struct {
  3215  		name      string
  3216  		cfg       testutil.TestClusterConfig
  3217  		keyspaces []string
  3218  		opts      cluster.GetWorkflowsOptions
  3219  		expected  *vtadminpb.ClusterWorkflows
  3220  		shouldErr bool
  3221  	}{
  3222  		{
  3223  			name: "success",
  3224  			cfg: testutil.TestClusterConfig{
  3225  				Cluster: &vtadminpb.Cluster{
  3226  					Id:   "c1",
  3227  					Name: "cluster1",
  3228  				},
  3229  				VtctldClient: &fakevtctldclient.VtctldClient{
  3230  					GetWorkflowsResults: map[string]struct {
  3231  						Response *vtctldatapb.GetWorkflowsResponse
  3232  						Error    error
  3233  					}{
  3234  						"ks1": {
  3235  							Response: &vtctldatapb.GetWorkflowsResponse{
  3236  								Workflows: []*vtctldatapb.Workflow{
  3237  									{
  3238  										Name: "ks1-workflow1",
  3239  									},
  3240  								},
  3241  							},
  3242  						},
  3243  						"ks2": {
  3244  							Response: &vtctldatapb.GetWorkflowsResponse{
  3245  								Workflows: []*vtctldatapb.Workflow{
  3246  									{
  3247  										Name: "ks2-workflow1",
  3248  									},
  3249  								},
  3250  							},
  3251  						},
  3252  					},
  3253  				},
  3254  			},
  3255  			keyspaces: []string{"ks1", "ks2"},
  3256  			expected: &vtadminpb.ClusterWorkflows{
  3257  				Workflows: []*vtadminpb.Workflow{
  3258  					{
  3259  						Cluster: &vtadminpb.Cluster{
  3260  							Id:   "c1",
  3261  							Name: "cluster1",
  3262  						},
  3263  						Keyspace: "ks1",
  3264  						Workflow: &vtctldatapb.Workflow{
  3265  							Name: "ks1-workflow1",
  3266  						},
  3267  					},
  3268  					{
  3269  						Cluster: &vtadminpb.Cluster{
  3270  							Id:   "c1",
  3271  							Name: "cluster1",
  3272  						},
  3273  						Keyspace: "ks2",
  3274  						Workflow: &vtctldatapb.Workflow{
  3275  							Name: "ks2-workflow1",
  3276  						},
  3277  					},
  3278  				},
  3279  			},
  3280  			shouldErr: false,
  3281  		},
  3282  		{
  3283  			name: "partial error",
  3284  			cfg: testutil.TestClusterConfig{
  3285  				Cluster: &vtadminpb.Cluster{
  3286  					Id:   "c1",
  3287  					Name: "cluster1",
  3288  				},
  3289  				VtctldClient: &fakevtctldclient.VtctldClient{
  3290  					GetWorkflowsResults: map[string]struct {
  3291  						Response *vtctldatapb.GetWorkflowsResponse
  3292  						Error    error
  3293  					}{
  3294  						"ks1": {
  3295  							Response: &vtctldatapb.GetWorkflowsResponse{
  3296  								Workflows: []*vtctldatapb.Workflow{
  3297  									{
  3298  										Name: "ks1-workflow1",
  3299  									},
  3300  								},
  3301  							},
  3302  						},
  3303  						"ks2": {
  3304  							Error: assert.AnError,
  3305  						},
  3306  					},
  3307  				},
  3308  			},
  3309  			keyspaces: []string{"ks1", "ks2"},
  3310  			expected: &vtadminpb.ClusterWorkflows{
  3311  				Workflows: []*vtadminpb.Workflow{
  3312  					{
  3313  						Cluster: &vtadminpb.Cluster{
  3314  							Id:   "c1",
  3315  							Name: "cluster1",
  3316  						},
  3317  						Keyspace: "ks1",
  3318  						Workflow: &vtctldatapb.Workflow{
  3319  							Name: "ks1-workflow1",
  3320  						},
  3321  					},
  3322  				},
  3323  				Warnings: []string{"something about ks2"},
  3324  			},
  3325  			shouldErr: false,
  3326  		},
  3327  		{
  3328  			name: "error",
  3329  			cfg: testutil.TestClusterConfig{
  3330  				Cluster: &vtadminpb.Cluster{
  3331  					Id:   "c1",
  3332  					Name: "cluster1",
  3333  				},
  3334  				VtctldClient: &fakevtctldclient.VtctldClient{
  3335  					GetWorkflowsResults: map[string]struct {
  3336  						Response *vtctldatapb.GetWorkflowsResponse
  3337  						Error    error
  3338  					}{
  3339  						"ks1": {
  3340  							Error: assert.AnError,
  3341  						},
  3342  					},
  3343  				},
  3344  			},
  3345  			keyspaces: []string{"ks1"},
  3346  			expected:  nil,
  3347  			shouldErr: true,
  3348  		},
  3349  	}
  3350  
  3351  	ctx := context.Background()
  3352  
  3353  	for _, tt := range tests {
  3354  		tt := tt
  3355  
  3356  		t.Run(tt.name, func(t *testing.T) {
  3357  			t.Parallel()
  3358  
  3359  			c := testutil.BuildCluster(t, tt.cfg)
  3360  			workflows, err := c.GetWorkflows(ctx, tt.keyspaces, tt.opts)
  3361  			if tt.shouldErr {
  3362  				assert.Error(t, err)
  3363  
  3364  				return
  3365  			}
  3366  
  3367  			assert.NoError(t, err)
  3368  			testutil.AssertClusterWorkflowsEqual(t, tt.expected, workflows)
  3369  		})
  3370  	}
  3371  }
  3372  
  3373  func TestSetWritable(t *testing.T) {
  3374  	t.Parallel()
  3375  
  3376  	ctx := context.Background()
  3377  	tests := []struct {
  3378  		name              string
  3379  		cfg               testutil.TestClusterConfig
  3380  		req               *vtctldatapb.SetWritableRequest
  3381  		assertion         func(t assert.TestingT, err error, msgAndArgs ...any) bool
  3382  		assertionMsgExtra []any
  3383  	}{
  3384  		{
  3385  			name: "ok",
  3386  			cfg: testutil.TestClusterConfig{
  3387  				Cluster: &vtadminpb.Cluster{
  3388  					Id:   "test",
  3389  					Name: "test",
  3390  				},
  3391  				VtctldClient: &fakevtctldclient.VtctldClient{
  3392  					SetWritableResults: map[string]error{
  3393  						"zone1-0000000100": nil,
  3394  					},
  3395  				},
  3396  			},
  3397  			req: &vtctldatapb.SetWritableRequest{
  3398  				TabletAlias: &topodatapb.TabletAlias{
  3399  					Cell: "zone1",
  3400  					Uid:  100,
  3401  				},
  3402  				Writable: true,
  3403  			},
  3404  			assertion: assert.NoError,
  3405  		},
  3406  		{
  3407  			name: "error",
  3408  			cfg: testutil.TestClusterConfig{
  3409  				Cluster: &vtadminpb.Cluster{
  3410  					Id:   "test",
  3411  					Name: "test",
  3412  				},
  3413  				VtctldClient: &fakevtctldclient.VtctldClient{
  3414  					SetWritableResults: map[string]error{
  3415  						"zone1-0000000100": fmt.Errorf("some error"),
  3416  					},
  3417  				},
  3418  			},
  3419  			req: &vtctldatapb.SetWritableRequest{
  3420  				TabletAlias: &topodatapb.TabletAlias{
  3421  					Cell: "zone1",
  3422  					Uid:  100,
  3423  				},
  3424  				Writable: true,
  3425  			},
  3426  			assertion: assert.Error,
  3427  		},
  3428  	}
  3429  
  3430  	for _, tt := range tests {
  3431  		tt := tt
  3432  
  3433  		t.Run(tt.name, func(t *testing.T) {
  3434  			t.Parallel()
  3435  
  3436  			c := testutil.BuildCluster(t, tt.cfg)
  3437  			err := c.SetWritable(ctx, tt.req)
  3438  			tt.assertion(t, err, tt.assertionMsgExtra...)
  3439  		})
  3440  	}
  3441  }
  3442  
  3443  func TestToggleTabletReplication(t *testing.T) {
  3444  	t.Parallel()
  3445  
  3446  	type ReplicationState bool
  3447  	const (
  3448  		Start ReplicationState = true
  3449  		Stop  ReplicationState = false
  3450  	)
  3451  
  3452  	testClusterProto := &vtadminpb.Cluster{
  3453  		Id:   "test",
  3454  		Name: "test",
  3455  	}
  3456  
  3457  	ctx := context.Background()
  3458  	tests := []struct {
  3459  		name              string
  3460  		cfg               testutil.TestClusterConfig
  3461  		tablet            *vtadminpb.Tablet
  3462  		state             ReplicationState
  3463  		assertion         func(t assert.TestingT, err error, msgAndArgs ...any) bool
  3464  		assertionMsgExtra []any
  3465  	}{
  3466  		{
  3467  			name: "start/ok",
  3468  			cfg: testutil.TestClusterConfig{
  3469  				Cluster: testClusterProto,
  3470  				VtctldClient: &fakevtctldclient.VtctldClient{
  3471  					StartReplicationResults: map[string]error{
  3472  						"zone1-0000000100": nil,
  3473  						"zone1-0000000101": fmt.Errorf("some error"),
  3474  					},
  3475  					StopReplicationResults: map[string]error{
  3476  						"zone1-0000000100": fmt.Errorf("some error"),
  3477  						"zone1-0000000101": nil,
  3478  					},
  3479  				},
  3480  			},
  3481  			tablet: &vtadminpb.Tablet{
  3482  				Cluster: testClusterProto,
  3483  				Tablet: &topodatapb.Tablet{
  3484  					Alias: &topodatapb.TabletAlias{
  3485  						Cell: "zone1",
  3486  						Uid:  100,
  3487  					},
  3488  				},
  3489  			},
  3490  			state:     Start,
  3491  			assertion: assert.NoError,
  3492  		},
  3493  		{
  3494  			name: "start/error",
  3495  			cfg: testutil.TestClusterConfig{
  3496  				Cluster: testClusterProto,
  3497  				VtctldClient: &fakevtctldclient.VtctldClient{
  3498  					StartReplicationResults: map[string]error{
  3499  						"zone1-0000000100": fmt.Errorf("some error"),
  3500  						"zone1-0000000101": nil,
  3501  					},
  3502  					StopReplicationResults: map[string]error{
  3503  						"zone1-0000000100": fmt.Errorf("some error"),
  3504  						"zone1-0000000101": nil,
  3505  					},
  3506  				},
  3507  			},
  3508  			tablet: &vtadminpb.Tablet{
  3509  				Cluster: testClusterProto,
  3510  				Tablet: &topodatapb.Tablet{
  3511  					Alias: &topodatapb.TabletAlias{
  3512  						Cell: "zone1",
  3513  						Uid:  100,
  3514  					},
  3515  				},
  3516  			},
  3517  			state:     Start,
  3518  			assertion: assert.Error,
  3519  		},
  3520  		{
  3521  			name: "stop/ok",
  3522  			cfg: testutil.TestClusterConfig{
  3523  				Cluster: testClusterProto,
  3524  				VtctldClient: &fakevtctldclient.VtctldClient{
  3525  					StartReplicationResults: map[string]error{
  3526  						"zone1-0000000100": fmt.Errorf("some error"),
  3527  						"zone1-0000000101": nil,
  3528  					},
  3529  					StopReplicationResults: map[string]error{
  3530  						"zone1-0000000100": nil,
  3531  						"zone1-0000000101": fmt.Errorf("some error"),
  3532  					},
  3533  				},
  3534  			},
  3535  			tablet: &vtadminpb.Tablet{
  3536  				Cluster: testClusterProto,
  3537  				Tablet: &topodatapb.Tablet{
  3538  					Alias: &topodatapb.TabletAlias{
  3539  						Cell: "zone1",
  3540  						Uid:  100,
  3541  					},
  3542  				},
  3543  			},
  3544  			state:     Stop,
  3545  			assertion: assert.NoError,
  3546  		},
  3547  		{
  3548  			name: "stop/error",
  3549  			cfg: testutil.TestClusterConfig{
  3550  				Cluster: testClusterProto,
  3551  				VtctldClient: &fakevtctldclient.VtctldClient{
  3552  					StartReplicationResults: map[string]error{
  3553  						"zone1-0000000100": fmt.Errorf("some error"),
  3554  						"zone1-0000000101": nil,
  3555  					},
  3556  					StopReplicationResults: map[string]error{
  3557  						"zone1-0000000100": fmt.Errorf("some error"),
  3558  						"zone1-0000000101": nil,
  3559  					},
  3560  				},
  3561  			},
  3562  			tablet: &vtadminpb.Tablet{
  3563  				Cluster: testClusterProto,
  3564  				Tablet: &topodatapb.Tablet{
  3565  					Alias: &topodatapb.TabletAlias{
  3566  						Cell: "zone1",
  3567  						Uid:  100,
  3568  					},
  3569  				},
  3570  			},
  3571  			state:     Stop,
  3572  			assertion: assert.Error,
  3573  		},
  3574  	}
  3575  
  3576  	for _, tt := range tests {
  3577  		tt := tt
  3578  
  3579  		t.Run(tt.name, func(t *testing.T) {
  3580  			t.Parallel()
  3581  
  3582  			c := testutil.BuildCluster(t, tt.cfg)
  3583  			err := c.ToggleTabletReplication(ctx, tt.tablet, bool(tt.state))
  3584  			tt.assertion(t, err, tt.assertionMsgExtra...)
  3585  		})
  3586  	}
  3587  }