vitess.io/vitess@v0.16.2/go/vt/vtctl/grpcvtctldserver/server_test.go (about)

     1  /*
     2  Copyright 2020 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package grpcvtctldserver
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"os"
    25  	"sort"
    26  	"testing"
    27  	"time"
    28  
    29  	_flag "vitess.io/vitess/go/internal/flag"
    30  
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  	"google.golang.org/protobuf/proto"
    34  
    35  	"vitess.io/vitess/go/mysql"
    36  	"vitess.io/vitess/go/protoutil"
    37  	"vitess.io/vitess/go/sqltypes"
    38  	"vitess.io/vitess/go/test/utils"
    39  	hk "vitess.io/vitess/go/vt/hook"
    40  	"vitess.io/vitess/go/vt/logutil"
    41  	"vitess.io/vitess/go/vt/mysqlctl/backupstorage"
    42  	"vitess.io/vitess/go/vt/topo"
    43  	"vitess.io/vitess/go/vt/topo/memorytopo"
    44  	"vitess.io/vitess/go/vt/topo/topoproto"
    45  	"vitess.io/vitess/go/vt/vtctl/grpcvtctldserver/testutil"
    46  	"vitess.io/vitess/go/vt/vtctl/localvtctldclient"
    47  	"vitess.io/vitess/go/vt/vttablet/tmclient"
    48  	"vitess.io/vitess/go/vt/vttablet/tmclienttest"
    49  
    50  	logutilpb "vitess.io/vitess/go/vt/proto/logutil"
    51  	mysqlctlpb "vitess.io/vitess/go/vt/proto/mysqlctl"
    52  	querypb "vitess.io/vitess/go/vt/proto/query"
    53  	replicationdatapb "vitess.io/vitess/go/vt/proto/replicationdata"
    54  	tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata"
    55  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    56  	vschemapb "vitess.io/vitess/go/vt/proto/vschema"
    57  	vtctldatapb "vitess.io/vitess/go/vt/proto/vtctldata"
    58  	vtctlservicepb "vitess.io/vitess/go/vt/proto/vtctlservice"
    59  	"vitess.io/vitess/go/vt/proto/vttime"
    60  )
    61  
    62  func init() {
    63  	backupstorage.BackupStorageImplementation = testutil.BackupStorageImplementation
    64  
    65  	// For tests that don't actually care about mocking the tmclient (i.e. they
    66  	// call NewVtctldServer to initialize the unit under test), this needs to be
    67  	// set.
    68  	//
    69  	// Tests that do care about the tmclient should use
    70  	// testutil.NewVtctldServerWithTabletManagerClient to initialize their
    71  	// VtctldServer.
    72  	tmclienttest.SetProtocol("go.vt.vtctl.grpcvtctldserver", "grpcvtctldserver.test")
    73  	tmclient.RegisterTabletManagerClientFactory("grpcvtctldserver.test", func() tmclient.TabletManagerClient {
    74  		return nil
    75  	})
    76  }
    77  
    78  func TestPanicHandler(t *testing.T) {
    79  	t.Parallel()
    80  
    81  	defer func() {
    82  		err := recover()
    83  		assert.Nil(t, err, "bad request should catch panic")
    84  	}()
    85  
    86  	vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, nil, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
    87  		return NewVtctldServer(ts)
    88  	})
    89  
    90  	_, err := vtctld.AddCellInfo(context.Background(), nil)
    91  	assert.Error(t, err)
    92  }
    93  
    94  func TestAddCellInfo(t *testing.T) {
    95  	t.Parallel()
    96  
    97  	ctx := context.Background()
    98  	tests := []struct {
    99  		name      string
   100  		ts        *topo.Server
   101  		req       *vtctldatapb.AddCellInfoRequest
   102  		shouldErr bool
   103  	}{
   104  		{
   105  			ts: memorytopo.NewServer("zone1"),
   106  			req: &vtctldatapb.AddCellInfoRequest{
   107  				Name: "zone2",
   108  				CellInfo: &topodatapb.CellInfo{
   109  					ServerAddress: ":2222",
   110  					Root:          "/zone2",
   111  				},
   112  			},
   113  		},
   114  		{
   115  			name: "cell already exists",
   116  			ts:   memorytopo.NewServer("zone1"),
   117  			req: &vtctldatapb.AddCellInfoRequest{
   118  				Name: "zone1",
   119  				CellInfo: &topodatapb.CellInfo{
   120  					ServerAddress: ":1111",
   121  					Root:          "/zone1",
   122  				},
   123  			},
   124  			shouldErr: true,
   125  		},
   126  		{
   127  			name: "no cell root",
   128  			ts:   memorytopo.NewServer("zone1"),
   129  			req: &vtctldatapb.AddCellInfoRequest{
   130  				Name: "zone2",
   131  				CellInfo: &topodatapb.CellInfo{
   132  					ServerAddress: ":2222",
   133  				},
   134  			},
   135  			shouldErr: true,
   136  		},
   137  	}
   138  
   139  	for _, tt := range tests {
   140  		tt := tt
   141  		t.Run(tt.name, func(t *testing.T) {
   142  			t.Parallel()
   143  
   144  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, tt.ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
   145  				return NewVtctldServer(ts)
   146  			})
   147  			_, err := vtctld.AddCellInfo(ctx, tt.req)
   148  			if tt.shouldErr {
   149  				assert.Error(t, err)
   150  				return
   151  			}
   152  
   153  			require.NoError(t, err)
   154  			ci, err := tt.ts.GetCellInfo(ctx, tt.req.Name, true)
   155  			require.NoError(t, err, "failed to read new cell %s from topo", tt.req.Name)
   156  			utils.MustMatch(t, tt.req.CellInfo, ci)
   157  		})
   158  	}
   159  }
   160  
   161  func TestAddCellsAlias(t *testing.T) {
   162  	t.Parallel()
   163  
   164  	ctx := context.Background()
   165  	tests := []struct {
   166  		name      string
   167  		ts        *topo.Server
   168  		setup     func(ts *topo.Server) error
   169  		req       *vtctldatapb.AddCellsAliasRequest
   170  		shouldErr bool
   171  	}{
   172  		{
   173  			ts: memorytopo.NewServer("zone1", "zone2", "zone3"),
   174  			req: &vtctldatapb.AddCellsAliasRequest{
   175  				Name:  "zone",
   176  				Cells: []string{"zone1", "zone2", "zone3"},
   177  			},
   178  		},
   179  		{
   180  			name: "alias exists",
   181  			ts:   memorytopo.NewServer("zone1", "zone2", "zone3"),
   182  			setup: func(ts *topo.Server) error {
   183  				return ts.CreateCellsAlias(ctx, "zone", &topodatapb.CellsAlias{
   184  					Cells: []string{"zone1", "zone2"},
   185  				})
   186  			},
   187  			req: &vtctldatapb.AddCellsAliasRequest{
   188  				Name:  "zone",
   189  				Cells: []string{"zone1", "zone2", "zone3"},
   190  			},
   191  			shouldErr: true,
   192  		},
   193  		{
   194  			name: "alias overlaps",
   195  			ts:   memorytopo.NewServer("zone1", "zone2", "zone3"),
   196  			setup: func(ts *topo.Server) error {
   197  				return ts.CreateCellsAlias(context.Background(), "zone_a", &topodatapb.CellsAlias{
   198  					Cells: []string{"zone1", "zone3"},
   199  				})
   200  			},
   201  			req: &vtctldatapb.AddCellsAliasRequest{
   202  				Name:  "zone_b",
   203  				Cells: []string{"zone1", "zone2", "zone3"},
   204  			},
   205  			shouldErr: true,
   206  		},
   207  	}
   208  
   209  	for _, tt := range tests {
   210  		tt := tt
   211  		t.Run(tt.name, func(t *testing.T) {
   212  			t.Parallel()
   213  
   214  			if tt.setup != nil {
   215  				err := tt.setup(tt.ts)
   216  				require.NoError(t, err, "test setup failed")
   217  			}
   218  
   219  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, tt.ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
   220  				return NewVtctldServer(ts)
   221  			})
   222  			_, err := vtctld.AddCellsAlias(ctx, tt.req)
   223  			if tt.shouldErr {
   224  				assert.Error(t, err)
   225  				return
   226  			}
   227  
   228  			require.NoError(t, err)
   229  			ca, err := tt.ts.GetCellsAlias(ctx, tt.req.Name, true)
   230  			require.NoError(t, err, "failed to read new cells alias %s from topo", tt.req.Name)
   231  			utils.MustMatch(t, &topodatapb.CellsAlias{Cells: tt.req.Cells}, ca)
   232  		})
   233  	}
   234  }
   235  
   236  func TestApplyRoutingRules(t *testing.T) {
   237  	t.Parallel()
   238  
   239  	ctx := context.Background()
   240  	tests := []struct {
   241  		name          string
   242  		cells         []string
   243  		req           *vtctldatapb.ApplyRoutingRulesRequest
   244  		expectedRules *vschemapb.RoutingRules
   245  		topoDown      bool
   246  		shouldErr     bool
   247  	}{
   248  		{
   249  			name:  "success",
   250  			cells: []string{"zone1"},
   251  			req: &vtctldatapb.ApplyRoutingRulesRequest{
   252  				RoutingRules: &vschemapb.RoutingRules{
   253  					Rules: []*vschemapb.RoutingRule{
   254  						{
   255  							FromTable: "t1",
   256  							ToTables:  []string{"t1", "t2"},
   257  						},
   258  					},
   259  				},
   260  			},
   261  			expectedRules: &vschemapb.RoutingRules{
   262  				Rules: []*vschemapb.RoutingRule{
   263  					{
   264  						FromTable: "t1",
   265  						ToTables:  []string{"t1", "t2"},
   266  					},
   267  				},
   268  			},
   269  		},
   270  		{
   271  			name:  "rebuild failed (bad cell)",
   272  			cells: []string{"zone1"},
   273  			req: &vtctldatapb.ApplyRoutingRulesRequest{
   274  				RoutingRules: &vschemapb.RoutingRules{
   275  					Rules: []*vschemapb.RoutingRule{
   276  						{
   277  							FromTable: "t1",
   278  							ToTables:  []string{"t1", "t2"},
   279  						},
   280  					},
   281  				},
   282  				RebuildCells: []string{"zone1", "zone2"},
   283  			},
   284  			shouldErr: true,
   285  		},
   286  		{
   287  			// this test case is exactly like the previous, but we don't fail
   288  			// because we don't rebuild the vschema graph (which would fail the
   289  			// way we've set up the test case with the bogus cell).
   290  			name:  "rebuild skipped",
   291  			cells: []string{"zone1"},
   292  			req: &vtctldatapb.ApplyRoutingRulesRequest{
   293  				RoutingRules: &vschemapb.RoutingRules{
   294  					Rules: []*vschemapb.RoutingRule{
   295  						{
   296  							FromTable: "t1",
   297  							ToTables:  []string{"t1", "t2"},
   298  						},
   299  					},
   300  				},
   301  				SkipRebuild:  true,
   302  				RebuildCells: []string{"zone1", "zone2"},
   303  			},
   304  			expectedRules: &vschemapb.RoutingRules{
   305  				Rules: []*vschemapb.RoutingRule{
   306  					{
   307  						FromTable: "t1",
   308  						ToTables:  []string{"t1", "t2"},
   309  					},
   310  				},
   311  			},
   312  			shouldErr: false,
   313  		},
   314  		{
   315  			name:      "topo down",
   316  			cells:     []string{"zone1"},
   317  			req:       &vtctldatapb.ApplyRoutingRulesRequest{},
   318  			topoDown:  true,
   319  			shouldErr: true,
   320  		},
   321  	}
   322  
   323  	for _, tt := range tests {
   324  		tt := tt
   325  		t.Run(tt.name, func(t *testing.T) {
   326  			t.Parallel()
   327  
   328  			ts, factory := memorytopo.NewServerAndFactory(tt.cells...)
   329  			if tt.topoDown {
   330  				factory.SetError(errors.New("topo down for testing"))
   331  			}
   332  
   333  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
   334  				return NewVtctldServer(ts)
   335  			})
   336  			_, err := vtctld.ApplyRoutingRules(ctx, tt.req)
   337  			if tt.shouldErr {
   338  				assert.Error(t, err)
   339  				return
   340  			}
   341  
   342  			require.NoError(t, err, "ApplyRoutingRules(%+v) failed", tt.req)
   343  
   344  			rr, err := ts.GetRoutingRules(ctx)
   345  			require.NoError(t, err, "failed to get routing rules from topo to compare")
   346  			utils.MustMatch(t, tt.expectedRules, rr)
   347  		})
   348  	}
   349  }
   350  
   351  func TestApplyVSchema(t *testing.T) {
   352  	t.Parallel()
   353  
   354  	tests := []struct {
   355  		name      string
   356  		req       *vtctldatapb.ApplyVSchemaRequest
   357  		exp       *vtctldatapb.ApplyVSchemaResponse
   358  		shouldErr bool
   359  	}{
   360  		{
   361  			name: "normal",
   362  			req: &vtctldatapb.ApplyVSchemaRequest{
   363  				Keyspace: "testkeyspace",
   364  				VSchema: &vschemapb.Keyspace{
   365  					Sharded: false,
   366  				},
   367  			},
   368  			exp: &vtctldatapb.ApplyVSchemaResponse{
   369  				VSchema: &vschemapb.Keyspace{
   370  					Sharded: false,
   371  				},
   372  			},
   373  			shouldErr: false,
   374  		}, {
   375  			name: "skip rebuild",
   376  			req: &vtctldatapb.ApplyVSchemaRequest{
   377  				Keyspace: "testkeyspace",
   378  				VSchema: &vschemapb.Keyspace{
   379  					Sharded: false,
   380  				},
   381  				SkipRebuild: true,
   382  			},
   383  			exp: &vtctldatapb.ApplyVSchemaResponse{
   384  				VSchema: &vschemapb.Keyspace{
   385  					Sharded: false,
   386  				},
   387  			},
   388  			shouldErr: false,
   389  		}, {
   390  			name: "both",
   391  			req: &vtctldatapb.ApplyVSchemaRequest{
   392  				Keyspace: "testkeyspace",
   393  				VSchema: &vschemapb.Keyspace{
   394  					Sharded: false,
   395  				},
   396  				Sql: "some vschema ddl here",
   397  			},
   398  			shouldErr: true,
   399  		}, {
   400  			name: "neither",
   401  			req: &vtctldatapb.ApplyVSchemaRequest{
   402  				Keyspace: "testkeyspace",
   403  			},
   404  			shouldErr: true,
   405  		}, {
   406  			name: "dry run",
   407  			req: &vtctldatapb.ApplyVSchemaRequest{
   408  				Keyspace: "testkeyspace",
   409  				VSchema: &vschemapb.Keyspace{
   410  					Sharded: false,
   411  				},
   412  				DryRun: true,
   413  			},
   414  			exp: &vtctldatapb.ApplyVSchemaResponse{
   415  				VSchema: &vschemapb.Keyspace{
   416  					Sharded: false,
   417  				},
   418  			},
   419  			shouldErr: false,
   420  		},
   421  	}
   422  
   423  	for _, tt := range tests {
   424  		tt := tt
   425  		t.Run(tt.name, func(t *testing.T) {
   426  			t.Parallel()
   427  
   428  			ctx := context.Background()
   429  			ts := memorytopo.NewServer("zone1")
   430  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
   431  				return NewVtctldServer(ts)
   432  			})
   433  
   434  			testutil.AddKeyspace(ctx, t, ts, &vtctldatapb.Keyspace{
   435  				Name: tt.req.Keyspace,
   436  				Keyspace: &topodatapb.Keyspace{
   437  					KeyspaceType: topodatapb.KeyspaceType_NORMAL,
   438  				},
   439  			})
   440  
   441  			origVSchema := &vschemapb.Keyspace{
   442  				Sharded: true,
   443  				Vindexes: map[string]*vschemapb.Vindex{
   444  					"v1": {
   445  						Type: "hash",
   446  					},
   447  				},
   448  			}
   449  			err := ts.SaveVSchema(ctx, tt.req.Keyspace, origVSchema)
   450  			require.NoError(t, err)
   451  
   452  			origSrvVSchema := &vschemapb.SrvVSchema{
   453  				Keyspaces: map[string]*vschemapb.Keyspace{
   454  					"testkeyspace": {
   455  						Sharded: true,
   456  						Vindexes: map[string]*vschemapb.Vindex{
   457  							"v1": {
   458  								Type: "hash",
   459  							},
   460  						},
   461  					},
   462  				},
   463  				RoutingRules: &vschemapb.RoutingRules{
   464  					Rules: []*vschemapb.RoutingRule{},
   465  				},
   466  			}
   467  			err = ts.UpdateSrvVSchema(ctx, "zone1", origSrvVSchema)
   468  			require.NoError(t, err)
   469  
   470  			res, err := vtctld.ApplyVSchema(ctx, tt.req)
   471  			if tt.shouldErr {
   472  				assert.Error(t, err)
   473  				return
   474  			}
   475  
   476  			assert.NoError(t, err)
   477  			utils.MustMatch(t, tt.exp, res)
   478  
   479  			if tt.req.DryRun {
   480  				actual, err := ts.GetVSchema(ctx, tt.req.Keyspace)
   481  				require.NoError(t, err)
   482  				utils.MustMatch(t, origVSchema, actual)
   483  			}
   484  
   485  			finalSrvVSchema, err := ts.GetSrvVSchema(ctx, "zone1")
   486  			require.NoError(t, err)
   487  
   488  			if tt.req.SkipRebuild || tt.req.DryRun {
   489  				utils.MustMatch(t, origSrvVSchema, finalSrvVSchema)
   490  			} else {
   491  				changedSrvVSchema := &vschemapb.SrvVSchema{
   492  					Keyspaces: map[string]*vschemapb.Keyspace{
   493  						"testkeyspace": {
   494  							Sharded: false,
   495  						},
   496  					},
   497  					RoutingRules: &vschemapb.RoutingRules{
   498  						Rules: []*vschemapb.RoutingRule{},
   499  					},
   500  					ShardRoutingRules: &vschemapb.ShardRoutingRules{
   501  						Rules: []*vschemapb.ShardRoutingRule{},
   502  					},
   503  				}
   504  				utils.MustMatch(t, changedSrvVSchema, finalSrvVSchema)
   505  			}
   506  		})
   507  	}
   508  }
   509  
   510  func TestBackup(t *testing.T) {
   511  	ctx := context.Background()
   512  	tests := []struct {
   513  		name      string
   514  		ts        *topo.Server
   515  		tmc       tmclient.TabletManagerClient
   516  		tablet    *topodatapb.Tablet
   517  		req       *vtctldatapb.BackupRequest
   518  		shouldErr bool
   519  		assertion func(t *testing.T, responses []*vtctldatapb.BackupResponse, err error)
   520  	}{
   521  		{
   522  			name: "ok",
   523  			ts:   memorytopo.NewServer("zone1"),
   524  			tmc: &testutil.TabletManagerClient{
   525  				Backups: map[string]struct {
   526  					Events        []*logutilpb.Event
   527  					EventInterval time.Duration
   528  					EventJitter   time.Duration
   529  					ErrorAfter    time.Duration
   530  				}{
   531  					"zone1-0000000100": {
   532  						Events: []*logutilpb.Event{{}, {}, {}},
   533  					},
   534  				},
   535  				SetReplicationSourceResults: map[string]error{
   536  					"zone1-0000000100": nil,
   537  				},
   538  			},
   539  			tablet: &topodatapb.Tablet{
   540  				Alias: &topodatapb.TabletAlias{
   541  					Cell: "zone1",
   542  					Uid:  100,
   543  				},
   544  				Type:     topodatapb.TabletType_REPLICA,
   545  				Keyspace: "ks",
   546  				Shard:    "-",
   547  			},
   548  			req: &vtctldatapb.BackupRequest{
   549  				TabletAlias: &topodatapb.TabletAlias{
   550  					Cell: "zone1",
   551  					Uid:  100,
   552  				},
   553  			},
   554  			assertion: func(t *testing.T, responses []*vtctldatapb.BackupResponse, err error) {
   555  				assert.ErrorIs(t, err, io.EOF, "expected Recv loop to end with io.EOF")
   556  				assert.Equal(t, 3, len(responses), "expected 3 messages from backupclient stream")
   557  			},
   558  		},
   559  		{
   560  			name: "cannot backup primary",
   561  			ts:   memorytopo.NewServer("zone1"),
   562  			tmc: &testutil.TabletManagerClient{
   563  				Backups: map[string]struct {
   564  					Events        []*logutilpb.Event
   565  					EventInterval time.Duration
   566  					EventJitter   time.Duration
   567  					ErrorAfter    time.Duration
   568  				}{
   569  					"zone1-0000000100": {
   570  						Events: []*logutilpb.Event{{}, {}, {}},
   571  					},
   572  				},
   573  			},
   574  			tablet: &topodatapb.Tablet{
   575  				Alias: &topodatapb.TabletAlias{
   576  					Cell: "zone1",
   577  					Uid:  100,
   578  				},
   579  				Type:     topodatapb.TabletType_PRIMARY,
   580  				Keyspace: "ks",
   581  				Shard:    "-",
   582  			},
   583  			req: &vtctldatapb.BackupRequest{
   584  				TabletAlias: &topodatapb.TabletAlias{
   585  					Cell: "zone1",
   586  					Uid:  100,
   587  				},
   588  			},
   589  			assertion: func(t *testing.T, responses []*vtctldatapb.BackupResponse, err error) {
   590  				assert.NotErrorIs(t, err, io.EOF, "expected backupclient stream to close with non-EOF")
   591  				assert.Zero(t, len(responses), "expected no backupclient messages")
   592  			},
   593  		},
   594  		{
   595  			name: "allow-primary",
   596  			ts:   memorytopo.NewServer("zone1"),
   597  			tmc: &testutil.TabletManagerClient{
   598  				Backups: map[string]struct {
   599  					Events        []*logutilpb.Event
   600  					EventInterval time.Duration
   601  					EventJitter   time.Duration
   602  					ErrorAfter    time.Duration
   603  				}{
   604  					"zone1-0000000100": {
   605  						Events: []*logutilpb.Event{{}, {}, {}},
   606  					},
   607  				},
   608  			},
   609  			tablet: &topodatapb.Tablet{
   610  				Alias: &topodatapb.TabletAlias{
   611  					Cell: "zone1",
   612  					Uid:  100,
   613  				},
   614  				Type:     topodatapb.TabletType_PRIMARY,
   615  				Keyspace: "ks",
   616  				Shard:    "-",
   617  			},
   618  			req: &vtctldatapb.BackupRequest{
   619  				TabletAlias: &topodatapb.TabletAlias{
   620  					Cell: "zone1",
   621  					Uid:  100,
   622  				},
   623  				AllowPrimary: true,
   624  			},
   625  			assertion: func(t *testing.T, responses []*vtctldatapb.BackupResponse, err error) {
   626  				assert.ErrorIs(t, err, io.EOF, "expected Recv loop to end with io.EOF")
   627  				assert.Equal(t, 3, len(responses), "expected 3 messages from backupclient stream")
   628  			},
   629  		},
   630  		{
   631  			name: "no tablet",
   632  			ts:   memorytopo.NewServer("zone1"),
   633  			tmc: &testutil.TabletManagerClient{
   634  				Backups: map[string]struct {
   635  					Events        []*logutilpb.Event
   636  					EventInterval time.Duration
   637  					EventJitter   time.Duration
   638  					ErrorAfter    time.Duration
   639  				}{
   640  					"zone1-0000000100": {
   641  						Events: []*logutilpb.Event{{}, {}, {}},
   642  					},
   643  				},
   644  			},
   645  			tablet: &topodatapb.Tablet{
   646  				Alias: &topodatapb.TabletAlias{
   647  					Cell: "zone1",
   648  					Uid:  100,
   649  				},
   650  				Type:     topodatapb.TabletType_REPLICA,
   651  				Keyspace: "ks",
   652  				Shard:    "-",
   653  			},
   654  			req: &vtctldatapb.BackupRequest{
   655  				TabletAlias: &topodatapb.TabletAlias{
   656  					Cell: "zone1",
   657  					Uid:  404,
   658  				},
   659  			},
   660  			assertion: func(t *testing.T, responses []*vtctldatapb.BackupResponse, err error) {
   661  				assert.NotErrorIs(t, err, io.EOF, "expected backupclient stream to close with non-EOF")
   662  				assert.Zero(t, len(responses), "expected no backupclient messages")
   663  			},
   664  		},
   665  		{
   666  			name: "midstream error",
   667  			ts:   memorytopo.NewServer("zone1"),
   668  			tmc: &testutil.TabletManagerClient{
   669  				Backups: map[string]struct {
   670  					Events        []*logutilpb.Event
   671  					EventInterval time.Duration
   672  					EventJitter   time.Duration
   673  					ErrorAfter    time.Duration
   674  				}{
   675  					"zone1-0000000100": {
   676  						Events:        []*logutilpb.Event{{}, {}, {}},
   677  						EventInterval: 100 * time.Millisecond,
   678  						ErrorAfter:    20 * time.Millisecond,
   679  					},
   680  				},
   681  			},
   682  			tablet: &topodatapb.Tablet{
   683  				Alias: &topodatapb.TabletAlias{
   684  					Cell: "zone1",
   685  					Uid:  100,
   686  				},
   687  				Type:     topodatapb.TabletType_REPLICA,
   688  				Keyspace: "ks",
   689  				Shard:    "-",
   690  			},
   691  			req: &vtctldatapb.BackupRequest{
   692  				TabletAlias: &topodatapb.TabletAlias{
   693  					Cell: "zone1",
   694  					Uid:  100,
   695  				},
   696  			},
   697  			assertion: func(t *testing.T, responses []*vtctldatapb.BackupResponse, err error) {
   698  				assert.NotErrorIs(t, err, io.EOF, "expected Recv loop to end with error other than io.EOF")
   699  				assert.Less(t, len(responses), 3, "expected fewer than 3 messages from backupclient stream")
   700  			},
   701  		},
   702  	}
   703  
   704  	for _, tt := range tests {
   705  		tt := tt
   706  		t.Run(tt.name, func(t *testing.T) {
   707  			if tt.tablet != nil {
   708  				testutil.AddTablet(ctx, t, tt.ts, tt.tablet, nil)
   709  			}
   710  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, tt.ts, tt.tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
   711  				return NewVtctldServer(ts)
   712  			})
   713  			client := localvtctldclient.New(vtctld)
   714  			stream, err := client.Backup(ctx, tt.req)
   715  			if tt.shouldErr {
   716  				assert.Error(t, err)
   717  			} else {
   718  				require.NoError(t, err)
   719  			}
   720  
   721  			responses, err := func() (responses []*vtctldatapb.BackupResponse, err error) {
   722  				for {
   723  					resp, err := stream.Recv()
   724  					if err != nil {
   725  						return responses, err
   726  					}
   727  
   728  					responses = append(responses, resp)
   729  				}
   730  			}()
   731  
   732  			if tt.assertion != nil {
   733  				func() {
   734  					t.Helper()
   735  					tt.assertion(t, responses, err)
   736  				}()
   737  			}
   738  		})
   739  	}
   740  }
   741  
   742  func TestBackupShard(t *testing.T) {
   743  	ctx := context.Background()
   744  	tests := []struct {
   745  		name      string
   746  		ts        *topo.Server
   747  		tmc       tmclient.TabletManagerClient
   748  		tablets   []*topodatapb.Tablet
   749  		req       *vtctldatapb.BackupShardRequest
   750  		shouldErr bool
   751  		assertion func(t *testing.T, responses []*vtctldatapb.BackupResponse, err error)
   752  	}{
   753  		{
   754  			name: "ok",
   755  			ts:   memorytopo.NewServer("zone1"),
   756  			tmc: &testutil.TabletManagerClient{
   757  				Backups: map[string]struct {
   758  					Events        []*logutilpb.Event
   759  					EventInterval time.Duration
   760  					EventJitter   time.Duration
   761  					ErrorAfter    time.Duration
   762  				}{
   763  					"zone1-0000000100": {
   764  						Events: []*logutilpb.Event{{}, {}, {}},
   765  					},
   766  				},
   767  				PrimaryPositionResults: map[string]struct {
   768  					Position string
   769  					Error    error
   770  				}{
   771  					"zone1-0000000200": {
   772  						Position: "some-position",
   773  					},
   774  				},
   775  				ReplicationStatusResults: map[string]struct {
   776  					Position *replicationdatapb.Status
   777  					Error    error
   778  				}{
   779  					"zone1-0000000100": {
   780  						Position: &replicationdatapb.Status{
   781  							ReplicationLagSeconds: 0,
   782  						},
   783  					},
   784  				},
   785  				SetReplicationSourceResults: map[string]error{
   786  					"zone1-0000000100": nil,
   787  				},
   788  			},
   789  			tablets: []*topodatapb.Tablet{
   790  				{
   791  					Alias: &topodatapb.TabletAlias{
   792  						Cell: "zone1",
   793  						Uid:  100,
   794  					},
   795  					Keyspace: "ks",
   796  					Shard:    "-",
   797  					Type:     topodatapb.TabletType_REPLICA,
   798  				},
   799  				{
   800  					Alias: &topodatapb.TabletAlias{
   801  						Cell: "zone1",
   802  						Uid:  200,
   803  					},
   804  					Keyspace: "ks",
   805  					Shard:    "-",
   806  					Type:     topodatapb.TabletType_PRIMARY,
   807  				},
   808  			},
   809  			req: &vtctldatapb.BackupShardRequest{
   810  				Keyspace: "ks",
   811  				Shard:    "-",
   812  			},
   813  			assertion: func(t *testing.T, responses []*vtctldatapb.BackupResponse, err error) {
   814  				assert.ErrorIs(t, err, io.EOF, "expected Recv loop to end with io.EOF")
   815  				assert.Equal(t, 3, len(responses), "expected 3 messages from backupclient stream")
   816  			},
   817  		},
   818  		{
   819  			name: "cannot backup primary",
   820  			ts:   memorytopo.NewServer("zone1"),
   821  			tmc: &testutil.TabletManagerClient{
   822  				Backups: map[string]struct {
   823  					Events        []*logutilpb.Event
   824  					EventInterval time.Duration
   825  					EventJitter   time.Duration
   826  					ErrorAfter    time.Duration
   827  				}{
   828  					"zone1-0000000100": {
   829  						Events: []*logutilpb.Event{{}, {}, {}},
   830  					},
   831  				},
   832  				PrimaryPositionResults: map[string]struct {
   833  					Position string
   834  					Error    error
   835  				}{
   836  					"zone1-0000000100": {
   837  						Position: "some-position",
   838  					},
   839  				},
   840  			},
   841  			tablets: []*topodatapb.Tablet{
   842  				{
   843  					Alias: &topodatapb.TabletAlias{
   844  						Cell: "zone1",
   845  						Uid:  100,
   846  					},
   847  					Type:     topodatapb.TabletType_PRIMARY,
   848  					Keyspace: "ks",
   849  					Shard:    "-",
   850  				},
   851  			},
   852  			req: &vtctldatapb.BackupShardRequest{
   853  				Keyspace: "ks",
   854  				Shard:    "-",
   855  			},
   856  			assertion: func(t *testing.T, responses []*vtctldatapb.BackupResponse, err error) {
   857  				assert.NotErrorIs(t, err, io.EOF, "expected backupclient stream to close with non-EOF")
   858  				assert.Zero(t, len(responses), "expected no backupclient messages")
   859  			},
   860  		},
   861  		{
   862  			name: "allow-primary",
   863  			ts:   memorytopo.NewServer("zone1"),
   864  			tmc: &testutil.TabletManagerClient{
   865  				Backups: map[string]struct {
   866  					Events        []*logutilpb.Event
   867  					EventInterval time.Duration
   868  					EventJitter   time.Duration
   869  					ErrorAfter    time.Duration
   870  				}{
   871  					"zone1-0000000100": {
   872  						Events: []*logutilpb.Event{{}, {}, {}},
   873  					},
   874  				},
   875  				PrimaryPositionResults: map[string]struct {
   876  					Position string
   877  					Error    error
   878  				}{
   879  					"zone1-0000000100": {
   880  						Position: "some-position",
   881  					},
   882  				},
   883  				ReplicationStatusResults: map[string]struct {
   884  					Position *replicationdatapb.Status
   885  					Error    error
   886  				}{
   887  					"zone1-0000000101": {
   888  						Position: &replicationdatapb.Status{},
   889  					},
   890  				},
   891  			},
   892  			tablets: []*topodatapb.Tablet{
   893  				{
   894  					Alias: &topodatapb.TabletAlias{
   895  						Cell: "zone1",
   896  						Uid:  100,
   897  					},
   898  					Type:     topodatapb.TabletType_PRIMARY,
   899  					Keyspace: "ks",
   900  					Shard:    "-",
   901  				},
   902  				{
   903  					Alias: &topodatapb.TabletAlias{
   904  						Cell: "zone1",
   905  						Uid:  101,
   906  					},
   907  					Type:     topodatapb.TabletType_BACKUP,
   908  					Keyspace: "ks",
   909  					Shard:    "-",
   910  				},
   911  			},
   912  			req: &vtctldatapb.BackupShardRequest{
   913  				Keyspace:     "ks",
   914  				Shard:        "-",
   915  				AllowPrimary: true,
   916  			},
   917  			assertion: func(t *testing.T, responses []*vtctldatapb.BackupResponse, err error) {
   918  				assert.ErrorIs(t, err, io.EOF, "expected Recv loop to end with io.EOF")
   919  				assert.Equal(t, 3, len(responses), "expected 3 messages from backupclient stream")
   920  			},
   921  		},
   922  		{
   923  			name: "no available tablet",
   924  			ts:   memorytopo.NewServer("zone1"),
   925  			tmc: &testutil.TabletManagerClient{
   926  				Backups: map[string]struct {
   927  					Events        []*logutilpb.Event
   928  					EventInterval time.Duration
   929  					EventJitter   time.Duration
   930  					ErrorAfter    time.Duration
   931  				}{
   932  					"zone1-0000000100": {
   933  						Events: []*logutilpb.Event{{}, {}, {}},
   934  					},
   935  				},
   936  				ReplicationStatusResults: map[string]struct {
   937  					Position *replicationdatapb.Status
   938  					Error    error
   939  				}{
   940  					"zone1-0000000100": {
   941  						Error: assert.AnError,
   942  					},
   943  					"zone1-0000000101": {
   944  						Error: assert.AnError,
   945  					},
   946  				},
   947  			},
   948  			tablets: []*topodatapb.Tablet{
   949  				{
   950  					Alias: &topodatapb.TabletAlias{
   951  						Cell: "zone1",
   952  						Uid:  100,
   953  					},
   954  					Type:     topodatapb.TabletType_REPLICA,
   955  					Keyspace: "ks",
   956  					Shard:    "-",
   957  				},
   958  				{
   959  					Alias: &topodatapb.TabletAlias{
   960  						Cell: "zone1",
   961  						Uid:  101,
   962  					},
   963  					Type:     topodatapb.TabletType_REPLICA,
   964  					Keyspace: "ks",
   965  					Shard:    "-",
   966  				},
   967  			},
   968  			req: &vtctldatapb.BackupShardRequest{},
   969  			assertion: func(t *testing.T, responses []*vtctldatapb.BackupResponse, err error) {
   970  				assert.NotErrorIs(t, err, io.EOF, "expected backupclient stream to close with non-EOF")
   971  				assert.Zero(t, len(responses), "expected no backupclient messages")
   972  			},
   973  		},
   974  	}
   975  
   976  	for _, tt := range tests {
   977  		tt := tt
   978  		t.Run(tt.name, func(t *testing.T) {
   979  			testutil.AddTablets(ctx, t, tt.ts,
   980  				&testutil.AddTabletOptions{
   981  					AlsoSetShardPrimary: true,
   982  				}, tt.tablets...,
   983  			)
   984  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, tt.ts, tt.tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
   985  				return NewVtctldServer(ts)
   986  			})
   987  			client := localvtctldclient.New(vtctld)
   988  			stream, err := client.BackupShard(ctx, tt.req)
   989  			if tt.shouldErr {
   990  				assert.Error(t, err)
   991  			} else {
   992  				require.NoError(t, err)
   993  			}
   994  
   995  			responses, err := func() (responses []*vtctldatapb.BackupResponse, err error) {
   996  				for {
   997  					resp, err := stream.Recv()
   998  					if err != nil {
   999  						return responses, err
  1000  					}
  1001  
  1002  					responses = append(responses, resp)
  1003  				}
  1004  			}()
  1005  
  1006  			if tt.assertion != nil {
  1007  				func() {
  1008  					t.Helper()
  1009  					tt.assertion(t, responses, err)
  1010  				}()
  1011  			}
  1012  		})
  1013  	}
  1014  }
  1015  
  1016  func TestChangeTabletType(t *testing.T) {
  1017  	t.Parallel()
  1018  
  1019  	tests := []struct {
  1020  		name      string
  1021  		cells     []string
  1022  		tablets   []*topodatapb.Tablet
  1023  		req       *vtctldatapb.ChangeTabletTypeRequest
  1024  		expected  *vtctldatapb.ChangeTabletTypeResponse
  1025  		shouldErr bool
  1026  	}{
  1027  		{
  1028  			name:  "success",
  1029  			cells: []string{"zone1"},
  1030  			tablets: []*topodatapb.Tablet{
  1031  				{
  1032  					Alias: &topodatapb.TabletAlias{
  1033  						Cell: "zone1",
  1034  						Uid:  100,
  1035  					},
  1036  					Keyspace: "ks",
  1037  					Shard:    "0",
  1038  					Type:     topodatapb.TabletType_REPLICA,
  1039  				}, {
  1040  					Alias: &topodatapb.TabletAlias{
  1041  						Cell: "zone1",
  1042  						Uid:  101,
  1043  					},
  1044  					Keyspace: "ks",
  1045  					Shard:    "0",
  1046  					Type:     topodatapb.TabletType_PRIMARY,
  1047  				},
  1048  			},
  1049  			req: &vtctldatapb.ChangeTabletTypeRequest{
  1050  				TabletAlias: &topodatapb.TabletAlias{
  1051  					Cell: "zone1",
  1052  					Uid:  100,
  1053  				},
  1054  				DbType: topodatapb.TabletType_RDONLY,
  1055  			},
  1056  			expected: &vtctldatapb.ChangeTabletTypeResponse{
  1057  				BeforeTablet: &topodatapb.Tablet{
  1058  					Alias: &topodatapb.TabletAlias{
  1059  						Cell: "zone1",
  1060  						Uid:  100,
  1061  					},
  1062  					Keyspace: "ks",
  1063  					Shard:    "0",
  1064  					Type:     topodatapb.TabletType_REPLICA,
  1065  				},
  1066  				AfterTablet: &topodatapb.Tablet{
  1067  					Alias: &topodatapb.TabletAlias{
  1068  						Cell: "zone1",
  1069  						Uid:  100,
  1070  					},
  1071  					Keyspace: "ks",
  1072  					Shard:    "0",
  1073  					Type:     topodatapb.TabletType_RDONLY,
  1074  				},
  1075  				WasDryRun: false,
  1076  			},
  1077  			shouldErr: false,
  1078  		},
  1079  		{
  1080  			name:  "dry run",
  1081  			cells: []string{"zone1"},
  1082  			tablets: []*topodatapb.Tablet{
  1083  				{
  1084  					Alias: &topodatapb.TabletAlias{
  1085  						Cell: "zone1",
  1086  						Uid:  100,
  1087  					},
  1088  					Keyspace: "ks",
  1089  					Shard:    "0",
  1090  					Type:     topodatapb.TabletType_REPLICA,
  1091  				}, {
  1092  					Alias: &topodatapb.TabletAlias{
  1093  						Cell: "zone1",
  1094  						Uid:  101,
  1095  					},
  1096  					Keyspace: "ks",
  1097  					Shard:    "0",
  1098  					Type:     topodatapb.TabletType_PRIMARY,
  1099  				},
  1100  			},
  1101  			req: &vtctldatapb.ChangeTabletTypeRequest{
  1102  				TabletAlias: &topodatapb.TabletAlias{
  1103  					Cell: "zone1",
  1104  					Uid:  100,
  1105  				},
  1106  				DbType: topodatapb.TabletType_RDONLY,
  1107  				DryRun: true,
  1108  			},
  1109  			expected: &vtctldatapb.ChangeTabletTypeResponse{
  1110  				BeforeTablet: &topodatapb.Tablet{
  1111  					Alias: &topodatapb.TabletAlias{
  1112  						Cell: "zone1",
  1113  						Uid:  100,
  1114  					},
  1115  					Keyspace: "ks",
  1116  					Shard:    "0",
  1117  					Type:     topodatapb.TabletType_REPLICA,
  1118  				},
  1119  				AfterTablet: &topodatapb.Tablet{
  1120  					Alias: &topodatapb.TabletAlias{
  1121  						Cell: "zone1",
  1122  						Uid:  100,
  1123  					},
  1124  					Keyspace: "ks",
  1125  					Shard:    "0",
  1126  					Type:     topodatapb.TabletType_RDONLY,
  1127  				},
  1128  				WasDryRun: true,
  1129  			},
  1130  			shouldErr: false,
  1131  		},
  1132  		{
  1133  			name:  "tablet not found",
  1134  			cells: []string{"zone1"},
  1135  			tablets: []*topodatapb.Tablet{
  1136  				{
  1137  					Alias: &topodatapb.TabletAlias{
  1138  						Cell: "zone1",
  1139  						Uid:  200,
  1140  					},
  1141  					Keyspace: "ks",
  1142  					Shard:    "0",
  1143  					Type:     topodatapb.TabletType_REPLICA,
  1144  				}, {
  1145  					Alias: &topodatapb.TabletAlias{
  1146  						Cell: "zone1",
  1147  						Uid:  101,
  1148  					},
  1149  					Keyspace: "ks",
  1150  					Shard:    "0",
  1151  					Type:     topodatapb.TabletType_PRIMARY,
  1152  				},
  1153  			},
  1154  			req: &vtctldatapb.ChangeTabletTypeRequest{
  1155  				TabletAlias: &topodatapb.TabletAlias{
  1156  					Cell: "zone1",
  1157  					Uid:  100,
  1158  				},
  1159  				DbType: topodatapb.TabletType_RDONLY,
  1160  			},
  1161  			expected:  nil,
  1162  			shouldErr: true,
  1163  		},
  1164  		{
  1165  			name:  "primary promotions not allowed",
  1166  			cells: []string{"zone1"},
  1167  			tablets: []*topodatapb.Tablet{
  1168  				{
  1169  					Alias: &topodatapb.TabletAlias{
  1170  						Cell: "zone1",
  1171  						Uid:  100,
  1172  					},
  1173  					Keyspace: "ks",
  1174  					Shard:    "0",
  1175  					Type:     topodatapb.TabletType_REPLICA,
  1176  				}, {
  1177  					Alias: &topodatapb.TabletAlias{
  1178  						Cell: "zone1",
  1179  						Uid:  101,
  1180  					},
  1181  					Keyspace: "ks",
  1182  					Shard:    "0",
  1183  					Type:     topodatapb.TabletType_PRIMARY,
  1184  				},
  1185  			},
  1186  			req: &vtctldatapb.ChangeTabletTypeRequest{
  1187  				TabletAlias: &topodatapb.TabletAlias{
  1188  					Cell: "zone1",
  1189  					Uid:  100,
  1190  				},
  1191  				DbType: topodatapb.TabletType_PRIMARY,
  1192  			},
  1193  			expected:  nil,
  1194  			shouldErr: true,
  1195  		},
  1196  		{
  1197  			name:  "primary demotions not allowed",
  1198  			cells: []string{"zone1"},
  1199  			tablets: []*topodatapb.Tablet{
  1200  				{
  1201  					Alias: &topodatapb.TabletAlias{
  1202  						Cell: "zone1",
  1203  						Uid:  100,
  1204  					},
  1205  					Keyspace: "ks",
  1206  					Shard:    "0",
  1207  					Type:     topodatapb.TabletType_PRIMARY,
  1208  				},
  1209  			},
  1210  			req: &vtctldatapb.ChangeTabletTypeRequest{
  1211  				TabletAlias: &topodatapb.TabletAlias{
  1212  					Cell: "zone1",
  1213  					Uid:  100,
  1214  				},
  1215  				DbType: topodatapb.TabletType_REPLICA,
  1216  			},
  1217  			expected:  nil,
  1218  			shouldErr: true,
  1219  		},
  1220  	}
  1221  
  1222  	for _, tt := range tests {
  1223  		tt := tt
  1224  
  1225  		t.Run(tt.name, func(t *testing.T) {
  1226  			t.Parallel()
  1227  
  1228  			ctx := context.Background()
  1229  			ts := memorytopo.NewServer(tt.cells...)
  1230  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, &testutil.TabletManagerClient{
  1231  				TopoServer: ts,
  1232  			}, func(ts *topo.Server) vtctlservicepb.VtctldServer { return NewVtctldServer(ts) })
  1233  
  1234  			testutil.AddTablets(ctx, t, ts, &testutil.AddTabletOptions{
  1235  				AlsoSetShardPrimary: true,
  1236  			}, tt.tablets...)
  1237  
  1238  			resp, err := vtctld.ChangeTabletType(ctx, tt.req)
  1239  			if tt.shouldErr {
  1240  				assert.Error(t, err)
  1241  				return
  1242  			}
  1243  
  1244  			assert.NoError(t, err)
  1245  			utils.MustMatch(t, tt.expected, resp)
  1246  
  1247  			// If we are testing a dry-run, then the tablet in the actual
  1248  			// topo should match the BeforeTablet in the response. Otherwise,
  1249  			// the tablet in the actual topo should match the AfterTablet in
  1250  			// the response.
  1251  			expectedRealType := resp.AfterTablet.Type
  1252  			msg := "ChangeTabletType did not cause topo update"
  1253  			if tt.req.DryRun {
  1254  				expectedRealType = resp.BeforeTablet.Type
  1255  				msg = "dryrun type change resulted in real type change"
  1256  			}
  1257  
  1258  			tablet, err := ts.GetTablet(ctx, tt.req.TabletAlias)
  1259  			assert.NoError(t, err,
  1260  				"could not load tablet %s from topo after type change %v -> %v [dryrun=%t]",
  1261  				topoproto.TabletAliasString(tt.req.TabletAlias),
  1262  				resp.BeforeTablet.Type,
  1263  				resp.AfterTablet.Type,
  1264  				resp.WasDryRun,
  1265  			)
  1266  			utils.MustMatch(t, expectedRealType, tablet.Type, msg)
  1267  		})
  1268  	}
  1269  
  1270  	t.Run("tabletmanager failure", func(t *testing.T) {
  1271  		t.Parallel()
  1272  
  1273  		ctx := context.Background()
  1274  		ts := memorytopo.NewServer("zone1")
  1275  		vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, &testutil.TabletManagerClient{
  1276  			TopoServer: nil,
  1277  		}, func(ts *topo.Server) vtctlservicepb.VtctldServer { return NewVtctldServer(ts) })
  1278  
  1279  		testutil.AddTablet(ctx, t, ts, &topodatapb.Tablet{
  1280  			Alias: &topodatapb.TabletAlias{
  1281  				Cell: "zone1",
  1282  				Uid:  100,
  1283  			},
  1284  			Keyspace: "ks",
  1285  			Shard:    "0",
  1286  			Type:     topodatapb.TabletType_REPLICA,
  1287  		}, nil)
  1288  		testutil.AddTablet(ctx, t, ts, &topodatapb.Tablet{
  1289  			Alias: &topodatapb.TabletAlias{
  1290  				Cell: "zone1",
  1291  				Uid:  101,
  1292  			},
  1293  			Keyspace: "ks",
  1294  			Shard:    "0",
  1295  			Type:     topodatapb.TabletType_PRIMARY,
  1296  		}, &testutil.AddTabletOptions{
  1297  			AlsoSetShardPrimary: true,
  1298  		})
  1299  
  1300  		_, err := vtctld.ChangeTabletType(ctx, &vtctldatapb.ChangeTabletTypeRequest{
  1301  			TabletAlias: &topodatapb.TabletAlias{
  1302  				Cell: "zone1",
  1303  				Uid:  100,
  1304  			},
  1305  			DbType: topodatapb.TabletType_RDONLY,
  1306  		})
  1307  		assert.Error(t, err)
  1308  	})
  1309  }
  1310  
  1311  func TestCreateKeyspace(t *testing.T) {
  1312  	t.Parallel()
  1313  
  1314  	cells := []string{"zone1", "zone2", "zone3"}
  1315  	tests := []struct {
  1316  		name               string
  1317  		topo               map[string]*topodatapb.Keyspace
  1318  		vschemas           map[string]*vschemapb.Keyspace
  1319  		req                *vtctldatapb.CreateKeyspaceRequest
  1320  		expected           *vtctldatapb.CreateKeyspaceResponse
  1321  		shouldErr          bool
  1322  		vschemaShouldExist bool
  1323  		expectedVSchema    *vschemapb.Keyspace
  1324  	}{
  1325  		{
  1326  			name: "normal keyspace",
  1327  			topo: nil,
  1328  			req: &vtctldatapb.CreateKeyspaceRequest{
  1329  				Name: "testkeyspace",
  1330  				Type: topodatapb.KeyspaceType_NORMAL,
  1331  			},
  1332  			expected: &vtctldatapb.CreateKeyspaceResponse{
  1333  				Keyspace: &vtctldatapb.Keyspace{
  1334  					Name: "testkeyspace",
  1335  					Keyspace: &topodatapb.Keyspace{
  1336  						KeyspaceType: topodatapb.KeyspaceType_NORMAL,
  1337  					},
  1338  				},
  1339  			},
  1340  			vschemaShouldExist: true,
  1341  			expectedVSchema: &vschemapb.Keyspace{
  1342  				Sharded: false,
  1343  			},
  1344  			shouldErr: false,
  1345  		},
  1346  		{
  1347  			name: "snapshot keyspace",
  1348  			topo: map[string]*topodatapb.Keyspace{
  1349  				"testkeyspace": {
  1350  					KeyspaceType: topodatapb.KeyspaceType_NORMAL,
  1351  				},
  1352  			},
  1353  			vschemas: map[string]*vschemapb.Keyspace{
  1354  				"testkeyspace": {
  1355  					Sharded: true,
  1356  					Vindexes: map[string]*vschemapb.Vindex{
  1357  						"h1": {
  1358  							Type: "hash",
  1359  						},
  1360  					},
  1361  				},
  1362  			},
  1363  			req: &vtctldatapb.CreateKeyspaceRequest{
  1364  				Name:         "testsnapshot",
  1365  				Type:         topodatapb.KeyspaceType_SNAPSHOT,
  1366  				BaseKeyspace: "testkeyspace",
  1367  				SnapshotTime: &vttime.Time{
  1368  					Seconds: 1,
  1369  				},
  1370  			},
  1371  			expected: &vtctldatapb.CreateKeyspaceResponse{
  1372  				Keyspace: &vtctldatapb.Keyspace{
  1373  					Name: "testsnapshot",
  1374  					Keyspace: &topodatapb.Keyspace{
  1375  						KeyspaceType: topodatapb.KeyspaceType_SNAPSHOT,
  1376  						BaseKeyspace: "testkeyspace",
  1377  						SnapshotTime: &vttime.Time{
  1378  							Seconds: 1,
  1379  						},
  1380  					},
  1381  				},
  1382  			},
  1383  			vschemaShouldExist: true,
  1384  			expectedVSchema: &vschemapb.Keyspace{
  1385  				Sharded: true,
  1386  				Vindexes: map[string]*vschemapb.Vindex{
  1387  					"h1": {
  1388  						Type: "hash",
  1389  					},
  1390  				},
  1391  				RequireExplicitRouting: true,
  1392  			},
  1393  			shouldErr: false,
  1394  		},
  1395  		{
  1396  			name: "snapshot keyspace with no base keyspace specified",
  1397  			topo: nil,
  1398  			req: &vtctldatapb.CreateKeyspaceRequest{
  1399  				Name:         "testsnapshot",
  1400  				Type:         topodatapb.KeyspaceType_SNAPSHOT,
  1401  				SnapshotTime: &vttime.Time{},
  1402  			},
  1403  			expected:  nil,
  1404  			shouldErr: true,
  1405  		},
  1406  		{
  1407  			name: "snapshot keyspace with no snapshot time",
  1408  			topo: nil,
  1409  			req: &vtctldatapb.CreateKeyspaceRequest{
  1410  				Name:         "testsnapshot",
  1411  				Type:         topodatapb.KeyspaceType_SNAPSHOT,
  1412  				BaseKeyspace: "testkeyspace",
  1413  			},
  1414  			expected:  nil,
  1415  			shouldErr: true,
  1416  		},
  1417  		{
  1418  			name: "snapshot keyspace with nonexistent base keyspace",
  1419  			topo: nil,
  1420  			req: &vtctldatapb.CreateKeyspaceRequest{
  1421  				Name:         "testsnapshot",
  1422  				Type:         topodatapb.KeyspaceType_SNAPSHOT,
  1423  				BaseKeyspace: "testkeyspace",
  1424  				SnapshotTime: &vttime.Time{Seconds: 100},
  1425  			},
  1426  			expected: &vtctldatapb.CreateKeyspaceResponse{
  1427  				Keyspace: &vtctldatapb.Keyspace{
  1428  					Name: "testsnapshot",
  1429  					Keyspace: &topodatapb.Keyspace{
  1430  						KeyspaceType: topodatapb.KeyspaceType_SNAPSHOT,
  1431  						BaseKeyspace: "testkeyspace",
  1432  						SnapshotTime: &vttime.Time{Seconds: 100},
  1433  					},
  1434  				},
  1435  			},
  1436  			vschemaShouldExist: true,
  1437  			expectedVSchema: &vschemapb.Keyspace{
  1438  				Sharded:                false,
  1439  				RequireExplicitRouting: true,
  1440  			},
  1441  			shouldErr: false,
  1442  		},
  1443  		{
  1444  			name: "invalid keyspace type",
  1445  			topo: nil,
  1446  			req: &vtctldatapb.CreateKeyspaceRequest{
  1447  				Name: "badkeyspacetype",
  1448  				Type: 10000000,
  1449  			},
  1450  			expected:  nil,
  1451  			shouldErr: true,
  1452  		},
  1453  		{
  1454  			name: "keyspace exists/no force",
  1455  			topo: map[string]*topodatapb.Keyspace{
  1456  				"testkeyspace": {
  1457  					KeyspaceType: topodatapb.KeyspaceType_NORMAL,
  1458  				},
  1459  			},
  1460  			req: &vtctldatapb.CreateKeyspaceRequest{
  1461  				Name:  "testkeyspace",
  1462  				Type:  topodatapb.KeyspaceType_NORMAL,
  1463  				Force: false,
  1464  			},
  1465  			expected:  nil,
  1466  			shouldErr: true,
  1467  		},
  1468  		{
  1469  			name: "keyspace exists/force",
  1470  			topo: map[string]*topodatapb.Keyspace{
  1471  				"testkeyspace": {
  1472  					KeyspaceType: topodatapb.KeyspaceType_NORMAL,
  1473  				},
  1474  			},
  1475  			req: &vtctldatapb.CreateKeyspaceRequest{
  1476  				Name:  "testkeyspace",
  1477  				Type:  topodatapb.KeyspaceType_NORMAL,
  1478  				Force: true,
  1479  			},
  1480  			expected: &vtctldatapb.CreateKeyspaceResponse{
  1481  				Keyspace: &vtctldatapb.Keyspace{
  1482  					Name: "testkeyspace",
  1483  					Keyspace: &topodatapb.Keyspace{
  1484  						KeyspaceType: topodatapb.KeyspaceType_NORMAL,
  1485  					},
  1486  				},
  1487  			},
  1488  			vschemaShouldExist: true,
  1489  			expectedVSchema: &vschemapb.Keyspace{
  1490  				Sharded: false,
  1491  			},
  1492  			shouldErr: false,
  1493  		},
  1494  		{
  1495  			name: "allow empty vschema",
  1496  			topo: nil,
  1497  			req: &vtctldatapb.CreateKeyspaceRequest{
  1498  				Name:              "testkeyspace",
  1499  				Type:              topodatapb.KeyspaceType_NORMAL,
  1500  				AllowEmptyVSchema: true,
  1501  			},
  1502  			expected: &vtctldatapb.CreateKeyspaceResponse{
  1503  				Keyspace: &vtctldatapb.Keyspace{
  1504  					Name: "testkeyspace",
  1505  					Keyspace: &topodatapb.Keyspace{
  1506  						KeyspaceType: topodatapb.KeyspaceType_NORMAL,
  1507  					},
  1508  				},
  1509  			},
  1510  			vschemaShouldExist: false,
  1511  			expectedVSchema:    nil,
  1512  			shouldErr:          false,
  1513  		}, {
  1514  			name: "keyspace with durability policy specified",
  1515  			topo: nil,
  1516  			req: &vtctldatapb.CreateKeyspaceRequest{
  1517  				Name:             "testkeyspace",
  1518  				Type:             topodatapb.KeyspaceType_NORMAL,
  1519  				DurabilityPolicy: "semi_sync",
  1520  			},
  1521  			expected: &vtctldatapb.CreateKeyspaceResponse{
  1522  				Keyspace: &vtctldatapb.Keyspace{
  1523  					Name: "testkeyspace",
  1524  					Keyspace: &topodatapb.Keyspace{
  1525  						KeyspaceType:     topodatapb.KeyspaceType_NORMAL,
  1526  						DurabilityPolicy: "semi_sync",
  1527  					},
  1528  				},
  1529  			},
  1530  			vschemaShouldExist: true,
  1531  			expectedVSchema: &vschemapb.Keyspace{
  1532  				Sharded: false,
  1533  			},
  1534  			shouldErr: false,
  1535  		},
  1536  	}
  1537  
  1538  	for _, tt := range tests {
  1539  		tt := tt
  1540  
  1541  		t.Run(tt.name, func(t *testing.T) {
  1542  			t.Parallel()
  1543  
  1544  			if tt.req == nil {
  1545  				t.Skip("test not yet implemented")
  1546  			}
  1547  
  1548  			ctx := context.Background()
  1549  			ts := memorytopo.NewServer(cells...)
  1550  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  1551  				return NewVtctldServer(ts)
  1552  			})
  1553  
  1554  			for name, ks := range tt.topo {
  1555  				testutil.AddKeyspace(ctx, t, ts, &vtctldatapb.Keyspace{
  1556  					Name:     name,
  1557  					Keyspace: ks,
  1558  				})
  1559  			}
  1560  
  1561  			for name, vs := range tt.vschemas {
  1562  				require.NoError(t, ts.SaveVSchema(ctx, name, vs), "error in SaveVSchema(%v, %+v)", name, vs)
  1563  			}
  1564  
  1565  			// Create the keyspace and make some assertions
  1566  			resp, err := vtctld.CreateKeyspace(ctx, tt.req)
  1567  			if tt.shouldErr {
  1568  				assert.Error(t, err)
  1569  				return
  1570  			}
  1571  
  1572  			assert.NoError(t, err)
  1573  			testutil.AssertKeyspacesEqual(t, tt.expected.Keyspace, resp.Keyspace, "%+v\n%+v\n", tt.expected.Keyspace, resp.Keyspace)
  1574  
  1575  			// Fetch the newly-created keyspace out of the topo and assert on it
  1576  			ks, err := ts.GetKeyspace(ctx, tt.req.Name)
  1577  			assert.NoError(t, err, "cannot get keyspace %v after creating", tt.req.Name)
  1578  			require.NotNil(t, ks.Keyspace)
  1579  
  1580  			actualKs := &vtctldatapb.Keyspace{
  1581  				Name:     tt.req.Name,
  1582  				Keyspace: ks.Keyspace,
  1583  			}
  1584  			testutil.AssertKeyspacesEqual(
  1585  				t,
  1586  				tt.expected.Keyspace,
  1587  				actualKs,
  1588  				"created keyspace %v does not match requested keyspace (name = %v) %v",
  1589  				actualKs,
  1590  				tt.expected.Keyspace,
  1591  			)
  1592  
  1593  			// Finally, check the VSchema
  1594  			vs, err := ts.GetVSchema(ctx, tt.req.Name)
  1595  			if !tt.vschemaShouldExist {
  1596  				assert.True(t, topo.IsErrType(err, topo.NoNode), "vschema should not exist, but got other error = %v", err)
  1597  				return
  1598  			}
  1599  			assert.NoError(t, err)
  1600  			utils.MustMatch(t, tt.expectedVSchema, vs)
  1601  		})
  1602  	}
  1603  }
  1604  
  1605  func TestCreateShard(t *testing.T) {
  1606  	t.Parallel()
  1607  
  1608  	tests := []struct {
  1609  		name      string
  1610  		keyspaces []*vtctldatapb.Keyspace
  1611  		shards    []*vtctldatapb.Shard
  1612  		topoErr   error
  1613  		req       *vtctldatapb.CreateShardRequest
  1614  		expected  *vtctldatapb.CreateShardResponse
  1615  		shouldErr bool
  1616  	}{
  1617  		{
  1618  			name: "success",
  1619  			keyspaces: []*vtctldatapb.Keyspace{
  1620  				{
  1621  					Name:     "testkeyspace",
  1622  					Keyspace: &topodatapb.Keyspace{},
  1623  				},
  1624  			},
  1625  			shards:  nil,
  1626  			topoErr: nil,
  1627  			req: &vtctldatapb.CreateShardRequest{
  1628  				Keyspace:  "testkeyspace",
  1629  				ShardName: "-",
  1630  			},
  1631  			expected: &vtctldatapb.CreateShardResponse{
  1632  				Keyspace: &vtctldatapb.Keyspace{
  1633  					Name:     "testkeyspace",
  1634  					Keyspace: &topodatapb.Keyspace{},
  1635  				},
  1636  				Shard: &vtctldatapb.Shard{
  1637  					Keyspace: "testkeyspace",
  1638  					Name:     "-",
  1639  					Shard: &topodatapb.Shard{
  1640  						KeyRange:         &topodatapb.KeyRange{},
  1641  						IsPrimaryServing: true,
  1642  					},
  1643  				},
  1644  				ShardAlreadyExists: false,
  1645  			},
  1646  			shouldErr: false,
  1647  		},
  1648  		{
  1649  			name:      "include parent",
  1650  			keyspaces: nil,
  1651  			shards:    nil,
  1652  			topoErr:   nil,
  1653  			req: &vtctldatapb.CreateShardRequest{
  1654  				Keyspace:      "testkeyspace",
  1655  				ShardName:     "-",
  1656  				IncludeParent: true,
  1657  			},
  1658  			expected: &vtctldatapb.CreateShardResponse{
  1659  				Keyspace: &vtctldatapb.Keyspace{
  1660  					Name:     "testkeyspace",
  1661  					Keyspace: &topodatapb.Keyspace{},
  1662  				},
  1663  				Shard: &vtctldatapb.Shard{
  1664  					Keyspace: "testkeyspace",
  1665  					Name:     "-",
  1666  					Shard: &topodatapb.Shard{
  1667  						KeyRange:         &topodatapb.KeyRange{},
  1668  						IsPrimaryServing: true,
  1669  					},
  1670  				},
  1671  				ShardAlreadyExists: false,
  1672  			},
  1673  			shouldErr: false,
  1674  		},
  1675  		{
  1676  			name:      "keyspace does not exist",
  1677  			keyspaces: nil,
  1678  			shards:    nil,
  1679  			topoErr:   nil,
  1680  			req: &vtctldatapb.CreateShardRequest{
  1681  				Keyspace:  "testkeyspace",
  1682  				ShardName: "-",
  1683  			},
  1684  			expected:  nil,
  1685  			shouldErr: true,
  1686  		},
  1687  		{
  1688  			name: "include parent/keyspace exists/no force",
  1689  			keyspaces: []*vtctldatapb.Keyspace{
  1690  				{
  1691  					Name:     "testkeyspace",
  1692  					Keyspace: &topodatapb.Keyspace{},
  1693  				},
  1694  			},
  1695  			shards:  nil,
  1696  			topoErr: nil,
  1697  			req: &vtctldatapb.CreateShardRequest{
  1698  				Keyspace:      "testkeyspace",
  1699  				ShardName:     "-",
  1700  				IncludeParent: true,
  1701  			},
  1702  			expected:  nil,
  1703  			shouldErr: true,
  1704  		},
  1705  		{
  1706  			name: "include parent/keyspace exists/force",
  1707  			keyspaces: []*vtctldatapb.Keyspace{
  1708  				{
  1709  					Name:     "testkeyspace",
  1710  					Keyspace: &topodatapb.Keyspace{},
  1711  				},
  1712  			},
  1713  			shards:  nil,
  1714  			topoErr: nil,
  1715  			req: &vtctldatapb.CreateShardRequest{
  1716  				Keyspace:      "testkeyspace",
  1717  				ShardName:     "-",
  1718  				IncludeParent: true,
  1719  				Force:         true,
  1720  			},
  1721  			expected: &vtctldatapb.CreateShardResponse{
  1722  				Keyspace: &vtctldatapb.Keyspace{
  1723  					Name:     "testkeyspace",
  1724  					Keyspace: &topodatapb.Keyspace{},
  1725  				},
  1726  				Shard: &vtctldatapb.Shard{
  1727  					Keyspace: "testkeyspace",
  1728  					Name:     "-",
  1729  					Shard: &topodatapb.Shard{
  1730  						KeyRange:         &topodatapb.KeyRange{},
  1731  						IsPrimaryServing: true,
  1732  					},
  1733  				},
  1734  				ShardAlreadyExists: false,
  1735  			},
  1736  			shouldErr: false,
  1737  		},
  1738  		{
  1739  			name: "shard exists/no force",
  1740  			keyspaces: []*vtctldatapb.Keyspace{
  1741  				{
  1742  					Name:     "testkeyspace",
  1743  					Keyspace: &topodatapb.Keyspace{},
  1744  				},
  1745  			},
  1746  			shards: []*vtctldatapb.Shard{
  1747  				{
  1748  					Keyspace: "testkeyspace",
  1749  					Name:     "-",
  1750  				},
  1751  			},
  1752  			topoErr: nil,
  1753  			req: &vtctldatapb.CreateShardRequest{
  1754  				Keyspace:  "testkeyspace",
  1755  				ShardName: "-",
  1756  			},
  1757  			expected:  nil,
  1758  			shouldErr: true,
  1759  		},
  1760  		{
  1761  			name: "shard exists/force",
  1762  			keyspaces: []*vtctldatapb.Keyspace{
  1763  				{
  1764  					Name:     "testkeyspace",
  1765  					Keyspace: &topodatapb.Keyspace{},
  1766  				},
  1767  			},
  1768  			shards: []*vtctldatapb.Shard{
  1769  				{
  1770  					Keyspace: "testkeyspace",
  1771  					Name:     "-",
  1772  				},
  1773  			},
  1774  			topoErr: nil,
  1775  			req: &vtctldatapb.CreateShardRequest{
  1776  				Keyspace:  "testkeyspace",
  1777  				ShardName: "-",
  1778  				Force:     true,
  1779  			},
  1780  			expected: &vtctldatapb.CreateShardResponse{
  1781  				Keyspace: &vtctldatapb.Keyspace{
  1782  					Name:     "testkeyspace",
  1783  					Keyspace: &topodatapb.Keyspace{},
  1784  				},
  1785  				Shard: &vtctldatapb.Shard{
  1786  					Keyspace: "testkeyspace",
  1787  					Name:     "-",
  1788  					Shard: &topodatapb.Shard{
  1789  						KeyRange:         &topodatapb.KeyRange{},
  1790  						IsPrimaryServing: true,
  1791  					},
  1792  				},
  1793  				ShardAlreadyExists: true,
  1794  			},
  1795  			shouldErr: false,
  1796  		},
  1797  		{
  1798  			name: "topo is down",
  1799  			keyspaces: []*vtctldatapb.Keyspace{
  1800  				{
  1801  					Name:     "testkeyspace",
  1802  					Keyspace: &topodatapb.Keyspace{},
  1803  				},
  1804  			},
  1805  			shards:  nil,
  1806  			topoErr: assert.AnError,
  1807  			req: &vtctldatapb.CreateShardRequest{
  1808  				Keyspace:  "testkeyspace",
  1809  				ShardName: "-",
  1810  			},
  1811  			expected:  nil,
  1812  			shouldErr: true,
  1813  		},
  1814  	}
  1815  
  1816  	for _, tt := range tests {
  1817  		tt := tt
  1818  
  1819  		t.Run(tt.name, func(t *testing.T) {
  1820  			t.Parallel()
  1821  			if tt.req == nil {
  1822  				t.Skip("focusing on other tests")
  1823  			}
  1824  
  1825  			ctx := context.Background()
  1826  			ts, topofactory := memorytopo.NewServerAndFactory("zone1")
  1827  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  1828  				return NewVtctldServer(ts)
  1829  			})
  1830  
  1831  			for _, ks := range tt.keyspaces {
  1832  				testutil.AddKeyspace(ctx, t, ts, ks)
  1833  			}
  1834  
  1835  			testutil.AddShards(ctx, t, ts, tt.shards...)
  1836  
  1837  			if tt.topoErr != nil {
  1838  				topofactory.SetError(tt.topoErr)
  1839  			}
  1840  
  1841  			resp, err := vtctld.CreateShard(ctx, tt.req)
  1842  			if tt.shouldErr {
  1843  				assert.Error(t, err)
  1844  				return
  1845  			}
  1846  
  1847  			assert.NoError(t, err)
  1848  			utils.MustMatch(t, tt.expected, resp)
  1849  		})
  1850  	}
  1851  }
  1852  
  1853  func TestDeleteCellInfo(t *testing.T) {
  1854  	t.Parallel()
  1855  
  1856  	ctx := context.Background()
  1857  	tests := []struct {
  1858  		name      string
  1859  		ts        *topo.Server
  1860  		req       *vtctldatapb.DeleteCellInfoRequest
  1861  		shouldErr bool
  1862  	}{
  1863  		{
  1864  			ts: memorytopo.NewServer("zone1", "zone2"),
  1865  			req: &vtctldatapb.DeleteCellInfoRequest{
  1866  				Name: "zone2",
  1867  			},
  1868  		},
  1869  		{
  1870  			name: "cell does not exist",
  1871  			ts:   memorytopo.NewServer("zone1"),
  1872  			req: &vtctldatapb.DeleteCellInfoRequest{
  1873  				Name: "zone2",
  1874  			},
  1875  			shouldErr: true,
  1876  		},
  1877  	}
  1878  
  1879  	for _, tt := range tests {
  1880  		tt := tt
  1881  		t.Run(tt.name, func(t *testing.T) {
  1882  			t.Parallel()
  1883  
  1884  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, tt.ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  1885  				return NewVtctldServer(ts)
  1886  			})
  1887  			_, err := vtctld.DeleteCellInfo(ctx, tt.req)
  1888  			if tt.shouldErr {
  1889  				assert.Error(t, err)
  1890  				return
  1891  			}
  1892  
  1893  			require.NoError(t, err)
  1894  			ci, err := tt.ts.GetCellInfo(ctx, tt.req.Name, true)
  1895  			assert.True(t, topo.IsErrType(err, topo.NoNode), "expected cell %s to no longer exist; found %+v", tt.req.Name, ci)
  1896  		})
  1897  	}
  1898  }
  1899  
  1900  func TestDeleteCellsAlias(t *testing.T) {
  1901  	t.Parallel()
  1902  
  1903  	ctx := context.Background()
  1904  	tests := []struct {
  1905  		name      string
  1906  		ts        *topo.Server
  1907  		setup     func(ts *topo.Server) error
  1908  		req       *vtctldatapb.DeleteCellsAliasRequest
  1909  		shouldErr bool
  1910  	}{
  1911  		{
  1912  			ts: memorytopo.NewServer("zone1", "zone2"),
  1913  			setup: func(ts *topo.Server) error {
  1914  				return ts.CreateCellsAlias(ctx, "zone", &topodatapb.CellsAlias{
  1915  					Cells: []string{"zone1", "zone2"},
  1916  				})
  1917  			},
  1918  			req: &vtctldatapb.DeleteCellsAliasRequest{
  1919  				Name: "zone",
  1920  			},
  1921  		},
  1922  		{
  1923  			name: "alias does not exist",
  1924  			ts:   memorytopo.NewServer("zone1", "zone2"),
  1925  			setup: func(ts *topo.Server) error {
  1926  				return ts.CreateCellsAlias(ctx, "zone_a", &topodatapb.CellsAlias{
  1927  					Cells: []string{"zone1", "zone2"},
  1928  				})
  1929  			},
  1930  			req: &vtctldatapb.DeleteCellsAliasRequest{
  1931  				Name: "zone_b",
  1932  			},
  1933  			shouldErr: true,
  1934  		},
  1935  	}
  1936  
  1937  	for _, tt := range tests {
  1938  		tt := tt
  1939  		t.Run(tt.name, func(t *testing.T) {
  1940  			t.Parallel()
  1941  
  1942  			if tt.setup != nil {
  1943  				err := tt.setup(tt.ts)
  1944  				require.NoError(t, err, "test setup failed")
  1945  			}
  1946  
  1947  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, tt.ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  1948  				return NewVtctldServer(ts)
  1949  			})
  1950  			_, err := vtctld.DeleteCellsAlias(ctx, tt.req)
  1951  			if tt.shouldErr {
  1952  				assert.Error(t, err)
  1953  				return
  1954  			}
  1955  
  1956  			require.NoError(t, err)
  1957  			ca, err := tt.ts.GetCellsAlias(ctx, tt.req.Name, true)
  1958  			assert.True(t, topo.IsErrType(err, topo.NoNode), "expected cell alias %s to no longer exist; found %+v", tt.req.Name, ca)
  1959  		})
  1960  	}
  1961  }
  1962  
  1963  func TestDeleteKeyspace(t *testing.T) {
  1964  	t.Parallel()
  1965  
  1966  	type testcase struct {
  1967  		name                       string
  1968  		keyspaces                  []*vtctldatapb.Keyspace
  1969  		shards                     []*vtctldatapb.Shard
  1970  		srvKeyspaces               map[string]map[string]*topodatapb.SrvKeyspace
  1971  		before                     func(t *testing.T, ts *topo.Server, tt testcase) func()
  1972  		topoErr                    error
  1973  		req                        *vtctldatapb.DeleteKeyspaceRequest
  1974  		expected                   *vtctldatapb.DeleteKeyspaceResponse
  1975  		expectedRemainingKeyspaces []string
  1976  		expectedRemainingShards    map[string][]string
  1977  		shouldErr                  bool
  1978  	}
  1979  	tests := []testcase{
  1980  		{
  1981  			name: "success",
  1982  			keyspaces: []*vtctldatapb.Keyspace{
  1983  				{
  1984  					Name:     "testkeyspace",
  1985  					Keyspace: &topodatapb.Keyspace{},
  1986  				},
  1987  			},
  1988  			shards:       nil,
  1989  			srvKeyspaces: nil,
  1990  			topoErr:      nil,
  1991  			req: &vtctldatapb.DeleteKeyspaceRequest{
  1992  				Keyspace: "testkeyspace",
  1993  			},
  1994  			expected:                   &vtctldatapb.DeleteKeyspaceResponse{},
  1995  			expectedRemainingKeyspaces: []string{},
  1996  			expectedRemainingShards:    map[string][]string{},
  1997  			shouldErr:                  false,
  1998  		},
  1999  		{
  2000  			name: "keyspace does not exist",
  2001  			keyspaces: []*vtctldatapb.Keyspace{
  2002  				{
  2003  					Name:     "otherkeyspace",
  2004  					Keyspace: &topodatapb.Keyspace{},
  2005  				},
  2006  			},
  2007  			shards:       nil,
  2008  			srvKeyspaces: nil,
  2009  			topoErr:      nil,
  2010  			req: &vtctldatapb.DeleteKeyspaceRequest{
  2011  				Keyspace: "testkeyspace",
  2012  			},
  2013  			expected:                   nil,
  2014  			expectedRemainingKeyspaces: []string{"otherkeyspace"},
  2015  			expectedRemainingShards: map[string][]string{
  2016  				"otherkeyspace": nil,
  2017  			},
  2018  			shouldErr: true,
  2019  		},
  2020  		{
  2021  			name: "keyspace has shards/Recursive=false",
  2022  			keyspaces: []*vtctldatapb.Keyspace{
  2023  				{
  2024  					Name:     "testkeyspace",
  2025  					Keyspace: &topodatapb.Keyspace{},
  2026  				},
  2027  			},
  2028  			shards: []*vtctldatapb.Shard{
  2029  				{
  2030  					Keyspace: "testkeyspace",
  2031  					Name:     "-80",
  2032  				},
  2033  				{
  2034  					Keyspace: "testkeyspace",
  2035  					Name:     "80-",
  2036  				},
  2037  			},
  2038  			srvKeyspaces: nil,
  2039  			topoErr:      nil,
  2040  			req: &vtctldatapb.DeleteKeyspaceRequest{
  2041  				Keyspace: "testkeyspace",
  2042  			},
  2043  			expected:                   nil,
  2044  			expectedRemainingKeyspaces: []string{"testkeyspace"},
  2045  			expectedRemainingShards: map[string][]string{
  2046  				"testkeyspace": {"-80", "80-"},
  2047  			},
  2048  			shouldErr: true,
  2049  		},
  2050  		{
  2051  			name: "keyspace has shards/Recursive=true",
  2052  			keyspaces: []*vtctldatapb.Keyspace{
  2053  				{
  2054  					Name:     "testkeyspace",
  2055  					Keyspace: &topodatapb.Keyspace{},
  2056  				},
  2057  			},
  2058  			shards: []*vtctldatapb.Shard{
  2059  				{
  2060  					Keyspace: "testkeyspace",
  2061  					Name:     "-80",
  2062  				},
  2063  				{
  2064  					Keyspace: "testkeyspace",
  2065  					Name:     "80-",
  2066  				},
  2067  			},
  2068  			srvKeyspaces: nil,
  2069  			topoErr:      nil,
  2070  			req: &vtctldatapb.DeleteKeyspaceRequest{
  2071  				Keyspace:  "testkeyspace",
  2072  				Recursive: true,
  2073  			},
  2074  			expected:                   &vtctldatapb.DeleteKeyspaceResponse{},
  2075  			expectedRemainingKeyspaces: []string{},
  2076  			expectedRemainingShards:    map[string][]string{},
  2077  			shouldErr:                  false,
  2078  		},
  2079  		// Not sure how to force this case because we always pass
  2080  		// (Recursive=true, EvenIfServing=true) so anything short of "topo
  2081  		// server is down" won't fail, and "topo server is down" will cause us
  2082  		// to error before we even reach this point in the code, so, ¯\_(ツ)_/¯.
  2083  		// {
  2084  		// 	name: "recursive/cannot delete shard",
  2085  		// },
  2086  		{
  2087  			name: "topo error",
  2088  			keyspaces: []*vtctldatapb.Keyspace{
  2089  				{
  2090  					Name:     "testkeyspace",
  2091  					Keyspace: &topodatapb.Keyspace{},
  2092  				},
  2093  			},
  2094  			shards:       nil,
  2095  			srvKeyspaces: nil,
  2096  			topoErr:      assert.AnError,
  2097  			req: &vtctldatapb.DeleteKeyspaceRequest{
  2098  				Keyspace: "testkeyspace",
  2099  			},
  2100  			expected:                   nil,
  2101  			expectedRemainingKeyspaces: []string{"testkeyspace"},
  2102  			expectedRemainingShards: map[string][]string{
  2103  				"testkeyspace": nil,
  2104  			},
  2105  			shouldErr: true,
  2106  		},
  2107  		{
  2108  			name: "keyspace is locked",
  2109  			keyspaces: []*vtctldatapb.Keyspace{
  2110  				{
  2111  					Name:     "testkeyspace",
  2112  					Keyspace: &topodatapb.Keyspace{},
  2113  				},
  2114  			},
  2115  			shards:       nil,
  2116  			srvKeyspaces: nil,
  2117  			topoErr:      nil,
  2118  			before: func(t *testing.T, ts *topo.Server, tt testcase) func() {
  2119  				_, unlock, err := ts.LockKeyspace(context.Background(), tt.req.Keyspace, "test.DeleteKeyspace")
  2120  				require.NoError(t, err, "failed to lock keyspace %s before test", tt.req.Keyspace)
  2121  				return func() {
  2122  					unlock(&err)
  2123  					if !topo.IsErrType(err, topo.NoNode) {
  2124  						assert.NoError(t, err, "error while unlocking keyspace %s after test", tt.req.Keyspace)
  2125  					}
  2126  				}
  2127  			},
  2128  			req: &vtctldatapb.DeleteKeyspaceRequest{
  2129  				Keyspace: "testkeyspace",
  2130  			},
  2131  			expected:                   nil,
  2132  			expectedRemainingKeyspaces: []string{"testkeyspace"},
  2133  			expectedRemainingShards: map[string][]string{
  2134  				"testkeyspace": nil,
  2135  			},
  2136  			shouldErr: true,
  2137  		},
  2138  		{
  2139  			name: "keyspace is locked with force",
  2140  			keyspaces: []*vtctldatapb.Keyspace{
  2141  				{
  2142  					Name:     "testkeyspace",
  2143  					Keyspace: &topodatapb.Keyspace{},
  2144  				},
  2145  			},
  2146  			shards:       nil,
  2147  			srvKeyspaces: nil,
  2148  			topoErr:      nil,
  2149  			before: func(t *testing.T, ts *topo.Server, tt testcase) func() {
  2150  				_, unlock, err := ts.LockKeyspace(context.Background(), tt.req.Keyspace, "test.DeleteKeyspace")
  2151  				require.NoError(t, err, "failed to lock keyspace %s before test", tt.req.Keyspace)
  2152  				return func() {
  2153  					unlock(&err)
  2154  					if !topo.IsErrType(err, topo.NoNode) {
  2155  						assert.NoError(t, err, "error while unlocking keyspace %s after test", tt.req.Keyspace)
  2156  					}
  2157  				}
  2158  			},
  2159  			req: &vtctldatapb.DeleteKeyspaceRequest{
  2160  				Keyspace: "testkeyspace",
  2161  				Force:    true,
  2162  			},
  2163  			expected:                   &vtctldatapb.DeleteKeyspaceResponse{},
  2164  			expectedRemainingKeyspaces: nil,
  2165  			expectedRemainingShards:    nil,
  2166  			shouldErr:                  false,
  2167  		},
  2168  	}
  2169  
  2170  	for _, tt := range tests {
  2171  		tt := tt
  2172  
  2173  		t.Run(tt.name, func(t *testing.T) {
  2174  			t.Parallel()
  2175  
  2176  			cells := []string{"zone1", "zone2", "zone3"}
  2177  
  2178  			ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
  2179  			defer cancel()
  2180  
  2181  			ts, topofactory := memorytopo.NewServerAndFactory(cells...)
  2182  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  2183  				return NewVtctldServer(ts)
  2184  			})
  2185  
  2186  			testutil.AddKeyspaces(ctx, t, ts, tt.keyspaces...)
  2187  			testutil.AddShards(ctx, t, ts, tt.shards...)
  2188  			testutil.UpdateSrvKeyspaces(ctx, t, ts, tt.srvKeyspaces)
  2189  
  2190  			if tt.topoErr != nil {
  2191  				topofactory.SetError(tt.topoErr)
  2192  			}
  2193  
  2194  			defer func() {
  2195  				if tt.expectedRemainingKeyspaces == nil {
  2196  					return
  2197  				}
  2198  
  2199  				topofactory.SetError(nil)
  2200  
  2201  				keyspaces, err := ts.GetKeyspaces(ctx)
  2202  				require.NoError(t, err, "cannot get keyspaces names after DeleteKeyspace call")
  2203  				assert.ElementsMatch(t, tt.expectedRemainingKeyspaces, keyspaces)
  2204  
  2205  				if tt.expectedRemainingShards == nil {
  2206  					return
  2207  				}
  2208  
  2209  				remainingShards := make(map[string][]string, len(keyspaces))
  2210  
  2211  				for _, ks := range keyspaces {
  2212  					shards, err := ts.GetShardNames(ctx, ks)
  2213  					require.NoError(t, err, "cannot get shard names for keyspace %s", ks)
  2214  
  2215  					remainingShards[ks] = shards
  2216  				}
  2217  
  2218  				utils.MustMatch(t, tt.expectedRemainingShards, remainingShards)
  2219  			}()
  2220  
  2221  			if tt.before != nil {
  2222  				if after := tt.before(t, ts, tt); after != nil {
  2223  					defer after()
  2224  				}
  2225  			}
  2226  
  2227  			resp, err := vtctld.DeleteKeyspace(ctx, tt.req)
  2228  			if tt.shouldErr {
  2229  				assert.Error(t, err)
  2230  
  2231  				return
  2232  			}
  2233  
  2234  			assert.NoError(t, err)
  2235  			utils.MustMatch(t, tt.expected, resp)
  2236  		})
  2237  	}
  2238  }
  2239  
  2240  func TestDeleteShards(t *testing.T) {
  2241  	t.Parallel()
  2242  
  2243  	type testcase struct {
  2244  		name                    string
  2245  		shards                  []*vtctldatapb.Shard
  2246  		tablets                 []*topodatapb.Tablet
  2247  		replicationGraphs       []*topo.ShardReplicationInfo
  2248  		srvKeyspaces            map[string]map[string]*topodatapb.SrvKeyspace
  2249  		topoErr                 error
  2250  		before                  func(t *testing.T, ts *topo.Server, tt testcase) func()
  2251  		req                     *vtctldatapb.DeleteShardsRequest
  2252  		expected                *vtctldatapb.DeleteShardsResponse
  2253  		expectedRemainingShards []*vtctldatapb.Shard
  2254  		shouldErr               bool
  2255  	}
  2256  	tests := []testcase{
  2257  		{
  2258  			name: "success",
  2259  			shards: []*vtctldatapb.Shard{
  2260  				{
  2261  					Keyspace: "testkeyspace",
  2262  					Name:     "-",
  2263  				},
  2264  			},
  2265  			tablets: nil,
  2266  			topoErr: nil,
  2267  			req: &vtctldatapb.DeleteShardsRequest{
  2268  				Shards: []*vtctldatapb.Shard{
  2269  					{
  2270  						Keyspace: "testkeyspace",
  2271  						Name:     "-",
  2272  					},
  2273  				},
  2274  			},
  2275  			expected:                &vtctldatapb.DeleteShardsResponse{},
  2276  			expectedRemainingShards: []*vtctldatapb.Shard{},
  2277  			shouldErr:               false,
  2278  		},
  2279  		{
  2280  			name:    "shard not found",
  2281  			shards:  nil,
  2282  			tablets: nil,
  2283  			topoErr: nil,
  2284  			req: &vtctldatapb.DeleteShardsRequest{
  2285  				Shards: []*vtctldatapb.Shard{
  2286  					{
  2287  						Keyspace: "testkeyspace",
  2288  						Name:     "-",
  2289  					},
  2290  				},
  2291  			},
  2292  			expected:  nil,
  2293  			shouldErr: true,
  2294  		},
  2295  		{
  2296  			name: "multiple shards",
  2297  			shards: []*vtctldatapb.Shard{
  2298  				{
  2299  					Keyspace: "testkeyspace",
  2300  					Name:     "-",
  2301  				},
  2302  				{
  2303  					Keyspace: "otherkeyspace",
  2304  					Name:     "-80",
  2305  				},
  2306  				{
  2307  					Keyspace: "otherkeyspace",
  2308  					Name:     "80-",
  2309  				},
  2310  			},
  2311  			tablets: nil,
  2312  			topoErr: nil,
  2313  			req: &vtctldatapb.DeleteShardsRequest{
  2314  				Shards: []*vtctldatapb.Shard{
  2315  					{
  2316  						Keyspace: "testkeyspace",
  2317  						Name:     "-",
  2318  					},
  2319  					{
  2320  						Keyspace: "otherkeyspace",
  2321  						Name:     "-80",
  2322  					},
  2323  				},
  2324  			},
  2325  			expected: &vtctldatapb.DeleteShardsResponse{},
  2326  			expectedRemainingShards: []*vtctldatapb.Shard{
  2327  				{
  2328  					Keyspace: "otherkeyspace",
  2329  					Name:     "80-",
  2330  				},
  2331  			},
  2332  			shouldErr: false,
  2333  		},
  2334  		{
  2335  			name: "topo is down",
  2336  			shards: []*vtctldatapb.Shard{
  2337  				{
  2338  					Keyspace: "testkeyspace",
  2339  					Name:     "-",
  2340  				},
  2341  			},
  2342  			tablets: nil,
  2343  			topoErr: assert.AnError,
  2344  			req: &vtctldatapb.DeleteShardsRequest{
  2345  				Shards: []*vtctldatapb.Shard{
  2346  					{
  2347  						Keyspace: "testkeyspace",
  2348  						Name:     "-",
  2349  					},
  2350  				},
  2351  			},
  2352  			expected: nil,
  2353  			expectedRemainingShards: []*vtctldatapb.Shard{
  2354  				{
  2355  					Keyspace: "testkeyspace",
  2356  					Name:     "-",
  2357  				},
  2358  			},
  2359  			shouldErr: true,
  2360  		},
  2361  		{
  2362  			name: "shard is serving/EvenIfServing=false",
  2363  			shards: []*vtctldatapb.Shard{
  2364  				{
  2365  					Keyspace: "testkeyspace",
  2366  					Name:     "-",
  2367  				},
  2368  			},
  2369  			tablets: nil,
  2370  			srvKeyspaces: map[string]map[string]*topodatapb.SrvKeyspace{
  2371  				"zone1": {
  2372  					"testkeyspace": &topodatapb.SrvKeyspace{
  2373  						Partitions: []*topodatapb.SrvKeyspace_KeyspacePartition{
  2374  							{
  2375  								ServedType: topodatapb.TabletType_PRIMARY,
  2376  								ShardReferences: []*topodatapb.ShardReference{
  2377  									{
  2378  										Name:     "-",
  2379  										KeyRange: &topodatapb.KeyRange{},
  2380  									},
  2381  								},
  2382  							},
  2383  						},
  2384  					},
  2385  				},
  2386  			},
  2387  			topoErr: nil,
  2388  			req: &vtctldatapb.DeleteShardsRequest{
  2389  				Shards: []*vtctldatapb.Shard{
  2390  					{
  2391  						Keyspace: "testkeyspace",
  2392  						Name:     "-",
  2393  					},
  2394  				},
  2395  			},
  2396  			expected: nil,
  2397  			expectedRemainingShards: []*vtctldatapb.Shard{
  2398  				{
  2399  					Keyspace: "testkeyspace",
  2400  					Name:     "-",
  2401  				},
  2402  			},
  2403  			shouldErr: true,
  2404  		},
  2405  		{
  2406  			name: "shard is serving/EvenIfServing=true",
  2407  			shards: []*vtctldatapb.Shard{
  2408  				{
  2409  					Keyspace: "testkeyspace",
  2410  					Name:     "-",
  2411  				},
  2412  			},
  2413  			tablets: nil,
  2414  			srvKeyspaces: map[string]map[string]*topodatapb.SrvKeyspace{
  2415  				"zone1": {
  2416  					"testkeyspace": &topodatapb.SrvKeyspace{
  2417  						Partitions: []*topodatapb.SrvKeyspace_KeyspacePartition{
  2418  							{
  2419  								ServedType: topodatapb.TabletType_PRIMARY,
  2420  								ShardReferences: []*topodatapb.ShardReference{
  2421  									{
  2422  										Name:     "-",
  2423  										KeyRange: &topodatapb.KeyRange{},
  2424  									},
  2425  								},
  2426  							},
  2427  						},
  2428  					},
  2429  				},
  2430  			},
  2431  			topoErr: nil,
  2432  			req: &vtctldatapb.DeleteShardsRequest{
  2433  				Shards: []*vtctldatapb.Shard{
  2434  					{
  2435  						Keyspace: "testkeyspace",
  2436  						Name:     "-",
  2437  					},
  2438  				},
  2439  				EvenIfServing: true,
  2440  			},
  2441  			expected:                &vtctldatapb.DeleteShardsResponse{},
  2442  			expectedRemainingShards: []*vtctldatapb.Shard{},
  2443  			shouldErr:               false,
  2444  		},
  2445  		{
  2446  			name: "ShardReplication in topo",
  2447  			shards: []*vtctldatapb.Shard{
  2448  				{
  2449  					Keyspace: "testkeyspace",
  2450  					Name:     "-",
  2451  				},
  2452  			},
  2453  			tablets: nil,
  2454  			replicationGraphs: []*topo.ShardReplicationInfo{
  2455  				topo.NewShardReplicationInfo(&topodatapb.ShardReplication{
  2456  					Nodes: []*topodatapb.ShardReplication_Node{
  2457  						{
  2458  							TabletAlias: &topodatapb.TabletAlias{
  2459  								Cell: "zone1",
  2460  								Uid:  100,
  2461  							},
  2462  						},
  2463  					},
  2464  				}, "zone1", "testkeyspace", "-"),
  2465  				topo.NewShardReplicationInfo(&topodatapb.ShardReplication{
  2466  					Nodes: []*topodatapb.ShardReplication_Node{
  2467  						{
  2468  							TabletAlias: &topodatapb.TabletAlias{
  2469  								Cell: "zone2",
  2470  								Uid:  200,
  2471  							},
  2472  						},
  2473  					},
  2474  				}, "zone2", "testkeyspace", "-"),
  2475  				topo.NewShardReplicationInfo(&topodatapb.ShardReplication{
  2476  					Nodes: []*topodatapb.ShardReplication_Node{
  2477  						{
  2478  							TabletAlias: &topodatapb.TabletAlias{
  2479  								Cell: "zone3",
  2480  								Uid:  300,
  2481  							},
  2482  						},
  2483  					},
  2484  				}, "zone3", "testkeyspace", "-"),
  2485  			},
  2486  			topoErr: nil,
  2487  			req: &vtctldatapb.DeleteShardsRequest{
  2488  				Shards: []*vtctldatapb.Shard{
  2489  					{
  2490  						Keyspace: "testkeyspace",
  2491  						Name:     "-",
  2492  					},
  2493  				},
  2494  			},
  2495  			expected:                &vtctldatapb.DeleteShardsResponse{},
  2496  			expectedRemainingShards: []*vtctldatapb.Shard{},
  2497  			shouldErr:               false,
  2498  		},
  2499  		{
  2500  			name: "shard has tablets/Recursive=false",
  2501  			shards: []*vtctldatapb.Shard{
  2502  				{
  2503  					Keyspace: "testkeyspace",
  2504  					Name:     "-",
  2505  				},
  2506  			},
  2507  			tablets: []*topodatapb.Tablet{
  2508  				{
  2509  					Alias: &topodatapb.TabletAlias{
  2510  						Cell: "zone1",
  2511  						Uid:  100,
  2512  					},
  2513  					Keyspace: "testkeyspace",
  2514  					Shard:    "-",
  2515  				},
  2516  			},
  2517  			topoErr: nil,
  2518  			req: &vtctldatapb.DeleteShardsRequest{
  2519  				Shards: []*vtctldatapb.Shard{
  2520  					{
  2521  						Keyspace: "testkeyspace",
  2522  						Name:     "-",
  2523  					},
  2524  				},
  2525  			},
  2526  			expected: nil,
  2527  			expectedRemainingShards: []*vtctldatapb.Shard{
  2528  				{
  2529  					Keyspace: "testkeyspace",
  2530  					Name:     "-",
  2531  				},
  2532  			},
  2533  			shouldErr: true,
  2534  		},
  2535  		{
  2536  			name: "shard has tablets/Recursive=true",
  2537  			shards: []*vtctldatapb.Shard{
  2538  				{
  2539  					Keyspace: "testkeyspace",
  2540  					Name:     "-",
  2541  				},
  2542  			},
  2543  			tablets: []*topodatapb.Tablet{
  2544  				{
  2545  					Alias: &topodatapb.TabletAlias{
  2546  						Cell: "zone1",
  2547  						Uid:  100,
  2548  					},
  2549  					Keyspace: "testkeyspace",
  2550  					Shard:    "-",
  2551  				},
  2552  			},
  2553  			topoErr: nil,
  2554  			req: &vtctldatapb.DeleteShardsRequest{
  2555  				Shards: []*vtctldatapb.Shard{
  2556  					{
  2557  						Keyspace: "testkeyspace",
  2558  						Name:     "-",
  2559  					},
  2560  				},
  2561  				Recursive: true,
  2562  			},
  2563  			expected:                &vtctldatapb.DeleteShardsResponse{},
  2564  			expectedRemainingShards: []*vtctldatapb.Shard{},
  2565  			shouldErr:               false,
  2566  		},
  2567  		{
  2568  			name: "tablets in topo belonging to other shard",
  2569  			shards: []*vtctldatapb.Shard{
  2570  				{
  2571  					Keyspace: "testkeyspace",
  2572  					Name:     "-80",
  2573  				},
  2574  				{
  2575  					Keyspace: "testkeyspace",
  2576  					Name:     "80-",
  2577  				},
  2578  			},
  2579  			tablets: []*topodatapb.Tablet{
  2580  				{
  2581  					Alias: &topodatapb.TabletAlias{
  2582  						Cell: "zone1",
  2583  						Uid:  100,
  2584  					},
  2585  					Keyspace: "testkeyspace",
  2586  					Shard:    "80-",
  2587  				},
  2588  			},
  2589  			topoErr: nil,
  2590  			req: &vtctldatapb.DeleteShardsRequest{
  2591  				Shards: []*vtctldatapb.Shard{
  2592  					{
  2593  						Keyspace: "testkeyspace",
  2594  						Name:     "-80",
  2595  					},
  2596  				},
  2597  			},
  2598  			expected: &vtctldatapb.DeleteShardsResponse{},
  2599  			expectedRemainingShards: []*vtctldatapb.Shard{
  2600  				{
  2601  					Keyspace: "testkeyspace",
  2602  					Name:     "80-",
  2603  				},
  2604  			},
  2605  			shouldErr: false,
  2606  		},
  2607  		{
  2608  			name: "shard is locked",
  2609  			shards: []*vtctldatapb.Shard{
  2610  				{
  2611  					Keyspace: "testkeyspace",
  2612  					Name:     "-",
  2613  				},
  2614  			},
  2615  			tablets: nil,
  2616  			topoErr: nil,
  2617  			before: func(t *testing.T, ts *topo.Server, tt testcase) func() {
  2618  				shard := tt.req.Shards[0]
  2619  				_, unlock, err := ts.LockShard(context.Background(), shard.Keyspace, shard.Name, "test.DeleteShard")
  2620  				require.NoError(t, err, "failed to lock shard %s/%s before test", shard.Keyspace, shard.Name)
  2621  				return func() {
  2622  					unlock(&err)
  2623  					if !topo.IsErrType(err, topo.NoNode) {
  2624  						assert.NoError(t, err, "error while unlocking shard %s/%s after test", shard.Keyspace, shard.Name)
  2625  					}
  2626  				}
  2627  			},
  2628  			req: &vtctldatapb.DeleteShardsRequest{
  2629  				Shards: []*vtctldatapb.Shard{
  2630  					{
  2631  						Keyspace: "testkeyspace",
  2632  						Name:     "-",
  2633  					},
  2634  				},
  2635  			},
  2636  			expected: nil,
  2637  			expectedRemainingShards: []*vtctldatapb.Shard{
  2638  				{
  2639  					Keyspace: "testkeyspace",
  2640  					Name:     "-",
  2641  				},
  2642  			},
  2643  			shouldErr: true,
  2644  		},
  2645  		{
  2646  			name: "shard is locked with force",
  2647  			shards: []*vtctldatapb.Shard{
  2648  				{
  2649  					Keyspace: "testkeyspace",
  2650  					Name:     "-",
  2651  				},
  2652  			},
  2653  			tablets: nil,
  2654  			topoErr: nil,
  2655  			before: func(t *testing.T, ts *topo.Server, tt testcase) func() {
  2656  				shard := tt.req.Shards[0]
  2657  				_, unlock, err := ts.LockShard(context.Background(), shard.Keyspace, shard.Name, "test.DeleteShard")
  2658  				require.NoError(t, err, "failed to lock shard %s/%s before test", shard.Keyspace, shard.Name)
  2659  				return func() {
  2660  					unlock(&err)
  2661  					if !topo.IsErrType(err, topo.NoNode) {
  2662  						assert.NoError(t, err, "error while unlocking shard %s/%s after test", shard.Keyspace, shard.Name)
  2663  					}
  2664  				}
  2665  			},
  2666  			req: &vtctldatapb.DeleteShardsRequest{
  2667  				Shards: []*vtctldatapb.Shard{
  2668  					{
  2669  						Keyspace: "testkeyspace",
  2670  						Name:     "-",
  2671  					},
  2672  				},
  2673  				Force: true,
  2674  			},
  2675  			expected:                &vtctldatapb.DeleteShardsResponse{},
  2676  			expectedRemainingShards: []*vtctldatapb.Shard{},
  2677  			shouldErr:               false,
  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  			cells := []string{"zone1", "zone2", "zone3"}
  2688  
  2689  			ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
  2690  			defer cancel()
  2691  
  2692  			ts, topofactory := memorytopo.NewServerAndFactory(cells...)
  2693  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  2694  				return NewVtctldServer(ts)
  2695  			})
  2696  
  2697  			testutil.AddShards(ctx, t, ts, tt.shards...)
  2698  			testutil.AddTablets(ctx, t, ts, nil, tt.tablets...)
  2699  			testutil.SetupReplicationGraphs(ctx, t, ts, tt.replicationGraphs...)
  2700  			testutil.UpdateSrvKeyspaces(ctx, t, ts, tt.srvKeyspaces)
  2701  
  2702  			if tt.topoErr != nil {
  2703  				topofactory.SetError(tt.topoErr)
  2704  			}
  2705  
  2706  			if tt.expectedRemainingShards != nil {
  2707  				defer func() {
  2708  					topofactory.SetError(nil)
  2709  
  2710  					actualShards := []*vtctldatapb.Shard{}
  2711  
  2712  					keyspaces, err := ts.GetKeyspaces(ctx)
  2713  					require.NoError(t, err, "cannot get keyspace names to check remaining shards")
  2714  
  2715  					for _, ks := range keyspaces {
  2716  						shards, err := ts.GetShardNames(ctx, ks)
  2717  						require.NoError(t, err, "cannot get shard names for keyspace %s", ks)
  2718  
  2719  						for _, shard := range shards {
  2720  							actualShards = append(actualShards, &vtctldatapb.Shard{
  2721  								Keyspace: ks,
  2722  								Name:     shard,
  2723  							})
  2724  						}
  2725  					}
  2726  
  2727  					assert.ElementsMatch(t, tt.expectedRemainingShards, actualShards)
  2728  				}()
  2729  			}
  2730  
  2731  			if tt.before != nil {
  2732  				if after := tt.before(t, ts, tt); after != nil {
  2733  					defer after()
  2734  				}
  2735  			}
  2736  
  2737  			resp, err := vtctld.DeleteShards(ctx, tt.req)
  2738  			if tt.shouldErr {
  2739  				assert.Error(t, err)
  2740  
  2741  				return
  2742  			}
  2743  
  2744  			assert.NoError(t, err)
  2745  			utils.MustMatch(t, tt.expected, resp)
  2746  		})
  2747  	}
  2748  }
  2749  
  2750  func TestDeleteSrvKeyspace(t *testing.T) {
  2751  	t.Parallel()
  2752  
  2753  	ctx := context.Background()
  2754  	tests := []struct {
  2755  		name      string
  2756  		vschemas  map[string]*vschemapb.SrvVSchema
  2757  		req       *vtctldatapb.DeleteSrvVSchemaRequest
  2758  		shouldErr bool
  2759  	}{
  2760  		{
  2761  			name: "success",
  2762  			vschemas: map[string]*vschemapb.SrvVSchema{
  2763  				"zone1": {
  2764  					Keyspaces: map[string]*vschemapb.Keyspace{
  2765  						"ks1": {},
  2766  						"ks2": {},
  2767  					},
  2768  					RoutingRules: &vschemapb.RoutingRules{Rules: []*vschemapb.RoutingRule{}},
  2769  				},
  2770  				"zone2": {
  2771  					Keyspaces: map[string]*vschemapb.Keyspace{
  2772  						"ks3": {},
  2773  					},
  2774  					RoutingRules: &vschemapb.RoutingRules{Rules: []*vschemapb.RoutingRule{}},
  2775  				},
  2776  			},
  2777  			req: &vtctldatapb.DeleteSrvVSchemaRequest{
  2778  				Cell: "zone2",
  2779  			},
  2780  		},
  2781  		{
  2782  			name: "cell not found",
  2783  			vschemas: map[string]*vschemapb.SrvVSchema{
  2784  				"zone1": {
  2785  					Keyspaces: map[string]*vschemapb.Keyspace{
  2786  						"ks1": {},
  2787  						"ks2": {},
  2788  					},
  2789  					RoutingRules: &vschemapb.RoutingRules{Rules: []*vschemapb.RoutingRule{}},
  2790  				},
  2791  				"zone2": {
  2792  					Keyspaces: map[string]*vschemapb.Keyspace{
  2793  						"ks3": {},
  2794  					},
  2795  					RoutingRules: &vschemapb.RoutingRules{Rules: []*vschemapb.RoutingRule{}},
  2796  				},
  2797  			},
  2798  			req: &vtctldatapb.DeleteSrvVSchemaRequest{
  2799  				Cell: "zone404",
  2800  			},
  2801  			shouldErr: true,
  2802  		},
  2803  		{
  2804  			name:      "empty cell argument",
  2805  			req:       &vtctldatapb.DeleteSrvVSchemaRequest{},
  2806  			shouldErr: true,
  2807  		},
  2808  	}
  2809  
  2810  	for _, tt := range tests {
  2811  		tt := tt
  2812  
  2813  		t.Run(tt.name, func(t *testing.T) {
  2814  			t.Parallel()
  2815  
  2816  			cells := make([]string, 0, len(tt.vschemas))
  2817  			finalVSchemas := make(map[string]*vschemapb.SrvVSchema, len(tt.vschemas)) // the set of vschemas that should be left after the Delete
  2818  			for cell, vschema := range tt.vschemas {
  2819  				cells = append(cells, cell)
  2820  
  2821  				if cell == tt.req.Cell {
  2822  					vschema = nil
  2823  				}
  2824  
  2825  				finalVSchemas[cell] = vschema
  2826  			}
  2827  
  2828  			ts := memorytopo.NewServer(cells...)
  2829  			for cell, vschema := range tt.vschemas {
  2830  				err := ts.UpdateSrvVSchema(ctx, cell, vschema)
  2831  				require.NoError(t, err, "failed to update SrvVSchema in cell = %v, vschema = %+v", cell, vschema)
  2832  			}
  2833  
  2834  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  2835  				return NewVtctldServer(ts)
  2836  			})
  2837  			_, err := vtctld.DeleteSrvVSchema(ctx, tt.req)
  2838  			if tt.shouldErr {
  2839  				assert.Error(t, err)
  2840  				return
  2841  			}
  2842  
  2843  			assert.NoError(t, err)
  2844  
  2845  			resp, err := vtctld.GetSrvVSchemas(ctx, &vtctldatapb.GetSrvVSchemasRequest{})
  2846  			require.NoError(t, err, "GetSrvVSchemas error")
  2847  			utils.MustMatch(t, resp.SrvVSchemas, finalVSchemas)
  2848  		})
  2849  	}
  2850  }
  2851  
  2852  func TestDeleteTablets(t *testing.T) {
  2853  	t.Parallel()
  2854  
  2855  	tests := []struct {
  2856  		name                     string
  2857  		tablets                  []*topodatapb.Tablet
  2858  		shardFieldUpdates        map[string]func(*topo.ShardInfo) error
  2859  		lockedShards             []*vtctldatapb.Shard
  2860  		topoError                error
  2861  		req                      *vtctldatapb.DeleteTabletsRequest
  2862  		expected                 *vtctldatapb.DeleteTabletsResponse
  2863  		expectedRemainingTablets []*topodatapb.Tablet
  2864  		shouldErr                bool
  2865  	}{
  2866  		{
  2867  			name: "single replica",
  2868  			tablets: []*topodatapb.Tablet{
  2869  				{
  2870  					Alias: &topodatapb.TabletAlias{
  2871  						Cell: "zone1",
  2872  						Uid:  100,
  2873  					},
  2874  					Type:     topodatapb.TabletType_REPLICA,
  2875  					Keyspace: "testkeyspace",
  2876  					Shard:    "-",
  2877  				},
  2878  			},
  2879  			lockedShards: nil,
  2880  			topoError:    nil,
  2881  			req: &vtctldatapb.DeleteTabletsRequest{
  2882  				TabletAliases: []*topodatapb.TabletAlias{
  2883  					{
  2884  						Cell: "zone1",
  2885  						Uid:  100,
  2886  					},
  2887  				},
  2888  			},
  2889  			expected:                 &vtctldatapb.DeleteTabletsResponse{},
  2890  			expectedRemainingTablets: []*topodatapb.Tablet{},
  2891  			shouldErr:                false,
  2892  		},
  2893  		{
  2894  			name: "single primary/no AllowPrimary",
  2895  			tablets: []*topodatapb.Tablet{
  2896  				{
  2897  					Alias: &topodatapb.TabletAlias{
  2898  						Cell: "zone1",
  2899  						Uid:  100,
  2900  					},
  2901  					Type:     topodatapb.TabletType_PRIMARY,
  2902  					Keyspace: "testkeyspace",
  2903  					Shard:    "-",
  2904  					PrimaryTermStartTime: &vttime.Time{
  2905  						Seconds:     100,
  2906  						Nanoseconds: 10,
  2907  					},
  2908  				},
  2909  			},
  2910  			lockedShards: nil,
  2911  			topoError:    nil,
  2912  			req: &vtctldatapb.DeleteTabletsRequest{
  2913  				TabletAliases: []*topodatapb.TabletAlias{
  2914  					{
  2915  						Cell: "zone1",
  2916  						Uid:  100,
  2917  					},
  2918  				},
  2919  			},
  2920  			expected:  nil,
  2921  			shouldErr: true,
  2922  		},
  2923  		{
  2924  			name: "single primary/with AllowPrimary",
  2925  			tablets: []*topodatapb.Tablet{
  2926  				{
  2927  					Alias: &topodatapb.TabletAlias{
  2928  						Cell: "zone1",
  2929  						Uid:  100,
  2930  					},
  2931  					Type:     topodatapb.TabletType_PRIMARY,
  2932  					Keyspace: "testkeyspace",
  2933  					Shard:    "-",
  2934  					PrimaryTermStartTime: &vttime.Time{
  2935  						Seconds:     100,
  2936  						Nanoseconds: 10,
  2937  					},
  2938  				},
  2939  			},
  2940  			lockedShards: nil,
  2941  			topoError:    nil,
  2942  			req: &vtctldatapb.DeleteTabletsRequest{
  2943  				TabletAliases: []*topodatapb.TabletAlias{
  2944  					{
  2945  						Cell: "zone1",
  2946  						Uid:  100,
  2947  					},
  2948  				},
  2949  				AllowPrimary: true,
  2950  			},
  2951  			expected:                 &vtctldatapb.DeleteTabletsResponse{},
  2952  			expectedRemainingTablets: []*topodatapb.Tablet{},
  2953  			shouldErr:                false,
  2954  		},
  2955  		{
  2956  			name: "multiple tablets",
  2957  			tablets: []*topodatapb.Tablet{
  2958  				{
  2959  					Alias: &topodatapb.TabletAlias{
  2960  						Cell: "zone1",
  2961  						Uid:  100,
  2962  					},
  2963  					Type:     topodatapb.TabletType_REPLICA,
  2964  					Keyspace: "testkeyspace",
  2965  					Shard:    "-",
  2966  				},
  2967  				{
  2968  					Alias: &topodatapb.TabletAlias{
  2969  						Cell: "zone1",
  2970  						Uid:  101,
  2971  					},
  2972  					Type:     topodatapb.TabletType_REPLICA,
  2973  					Keyspace: "testkeyspace",
  2974  					Shard:    "-",
  2975  				},
  2976  				{
  2977  					Alias: &topodatapb.TabletAlias{
  2978  						Cell: "zone1",
  2979  						Uid:  102,
  2980  					},
  2981  					Type:     topodatapb.TabletType_REPLICA,
  2982  					Keyspace: "testkeyspace",
  2983  					Shard:    "-",
  2984  				},
  2985  			},
  2986  			lockedShards: nil,
  2987  			topoError:    nil,
  2988  			req: &vtctldatapb.DeleteTabletsRequest{
  2989  				TabletAliases: []*topodatapb.TabletAlias{
  2990  					{
  2991  						Cell: "zone1",
  2992  						Uid:  100,
  2993  					},
  2994  					{
  2995  						Cell: "zone1",
  2996  						Uid:  102,
  2997  					},
  2998  				},
  2999  			},
  3000  			expected: &vtctldatapb.DeleteTabletsResponse{},
  3001  			expectedRemainingTablets: []*topodatapb.Tablet{
  3002  				{
  3003  					Alias: &topodatapb.TabletAlias{
  3004  						Cell: "zone1",
  3005  						Uid:  101,
  3006  					},
  3007  					Type:     topodatapb.TabletType_REPLICA,
  3008  					Keyspace: "testkeyspace",
  3009  					Shard:    "-",
  3010  				},
  3011  			},
  3012  			shouldErr: false,
  3013  		},
  3014  		{
  3015  			name: "stale primary record",
  3016  			tablets: []*topodatapb.Tablet{
  3017  				{
  3018  					// The stale primary we're going to delete.
  3019  					Alias: &topodatapb.TabletAlias{
  3020  						Cell: "zone1",
  3021  						Uid:  100,
  3022  					},
  3023  					Type:     topodatapb.TabletType_PRIMARY,
  3024  					Keyspace: "testkeyspace",
  3025  					Shard:    "-",
  3026  					PrimaryTermStartTime: &vttime.Time{
  3027  						Seconds:     100,
  3028  						Nanoseconds: 10,
  3029  					},
  3030  				},
  3031  				{
  3032  					// The real shard primary, which we'll update in the shard
  3033  					// record below.
  3034  					Alias: &topodatapb.TabletAlias{
  3035  						Cell: "zone1",
  3036  						Uid:  101,
  3037  					},
  3038  					Type:     topodatapb.TabletType_PRIMARY,
  3039  					Keyspace: "testkeyspace",
  3040  					Shard:    "-",
  3041  					PrimaryTermStartTime: &vttime.Time{
  3042  						Seconds:     1001,
  3043  						Nanoseconds: 101,
  3044  					},
  3045  				},
  3046  			},
  3047  			shardFieldUpdates: map[string]func(*topo.ShardInfo) error{
  3048  				"testkeyspace/-": func(si *topo.ShardInfo) error {
  3049  					si.PrimaryAlias = &topodatapb.TabletAlias{
  3050  						Cell: "zone1",
  3051  						Uid:  101,
  3052  					}
  3053  					si.PrimaryTermStartTime = &vttime.Time{
  3054  						Seconds:     1001,
  3055  						Nanoseconds: 101,
  3056  					}
  3057  
  3058  					return nil
  3059  				},
  3060  			},
  3061  			lockedShards: nil,
  3062  			topoError:    nil,
  3063  			req: &vtctldatapb.DeleteTabletsRequest{
  3064  				TabletAliases: []*topodatapb.TabletAlias{
  3065  					{
  3066  						Cell: "zone1",
  3067  						Uid:  100,
  3068  					},
  3069  				},
  3070  			},
  3071  			expected: &vtctldatapb.DeleteTabletsResponse{},
  3072  			expectedRemainingTablets: []*topodatapb.Tablet{
  3073  				{
  3074  					// The true shard primary still exists (phew!)
  3075  					Alias: &topodatapb.TabletAlias{
  3076  						Cell: "zone1",
  3077  						Uid:  101,
  3078  					},
  3079  					Type:     topodatapb.TabletType_PRIMARY,
  3080  					Keyspace: "testkeyspace",
  3081  					Shard:    "-",
  3082  					PrimaryTermStartTime: &vttime.Time{
  3083  						Seconds:     1001,
  3084  						Nanoseconds: 101,
  3085  					},
  3086  				},
  3087  			},
  3088  			shouldErr: false,
  3089  		},
  3090  		{
  3091  			name: "tablet not found",
  3092  			tablets: []*topodatapb.Tablet{
  3093  				{
  3094  					Alias: &topodatapb.TabletAlias{
  3095  						Cell: "zone1",
  3096  						Uid:  100,
  3097  					},
  3098  					Type:     topodatapb.TabletType_REPLICA,
  3099  					Keyspace: "testkeyspace",
  3100  					Shard:    "-",
  3101  				},
  3102  			},
  3103  			lockedShards: nil,
  3104  			topoError:    nil,
  3105  			req: &vtctldatapb.DeleteTabletsRequest{
  3106  				TabletAliases: []*topodatapb.TabletAlias{
  3107  					{
  3108  						Cell: "zone1",
  3109  						Uid:  200,
  3110  					},
  3111  				},
  3112  			},
  3113  			expected: nil,
  3114  			expectedRemainingTablets: []*topodatapb.Tablet{
  3115  				{
  3116  					Alias: &topodatapb.TabletAlias{
  3117  						Cell: "zone1",
  3118  						Uid:  100,
  3119  					},
  3120  					Type:     topodatapb.TabletType_REPLICA,
  3121  					Keyspace: "testkeyspace",
  3122  					Shard:    "-",
  3123  				},
  3124  			},
  3125  			shouldErr: true,
  3126  		},
  3127  		{
  3128  			name: "shard is locked",
  3129  			tablets: []*topodatapb.Tablet{
  3130  				{
  3131  					Alias: &topodatapb.TabletAlias{
  3132  						Cell: "zone1",
  3133  						Uid:  100,
  3134  					},
  3135  					Type:     topodatapb.TabletType_PRIMARY,
  3136  					Keyspace: "testkeyspace",
  3137  					Shard:    "-",
  3138  					PrimaryTermStartTime: &vttime.Time{
  3139  						Seconds:     100,
  3140  						Nanoseconds: 10,
  3141  					},
  3142  				},
  3143  			},
  3144  			lockedShards: []*vtctldatapb.Shard{
  3145  				{
  3146  					Keyspace: "testkeyspace",
  3147  					Name:     "-",
  3148  				},
  3149  			},
  3150  			topoError: nil,
  3151  			req: &vtctldatapb.DeleteTabletsRequest{
  3152  				TabletAliases: []*topodatapb.TabletAlias{
  3153  					{
  3154  						Cell: "zone1",
  3155  						Uid:  100,
  3156  					},
  3157  				},
  3158  				AllowPrimary: true,
  3159  			},
  3160  			expected: nil,
  3161  			expectedRemainingTablets: []*topodatapb.Tablet{
  3162  				{
  3163  					Alias: &topodatapb.TabletAlias{
  3164  						Cell: "zone1",
  3165  						Uid:  100,
  3166  					},
  3167  					Type:     topodatapb.TabletType_PRIMARY,
  3168  					Keyspace: "testkeyspace",
  3169  					Shard:    "-",
  3170  					PrimaryTermStartTime: &vttime.Time{
  3171  						Seconds:     100,
  3172  						Nanoseconds: 10,
  3173  					},
  3174  				},
  3175  			},
  3176  			shouldErr: true,
  3177  		},
  3178  		{
  3179  			name: "another shard is locked",
  3180  			tablets: []*topodatapb.Tablet{
  3181  				{
  3182  					Alias: &topodatapb.TabletAlias{
  3183  						Cell: "zone1",
  3184  						Uid:  100,
  3185  					},
  3186  					Type:     topodatapb.TabletType_PRIMARY,
  3187  					Keyspace: "testkeyspace",
  3188  					Shard:    "-80",
  3189  					PrimaryTermStartTime: &vttime.Time{
  3190  						Seconds:     100,
  3191  						Nanoseconds: 10,
  3192  					},
  3193  				},
  3194  				{
  3195  					Alias: &topodatapb.TabletAlias{
  3196  						Cell: "zone1",
  3197  						Uid:  200,
  3198  					},
  3199  					Type:     topodatapb.TabletType_PRIMARY,
  3200  					Keyspace: "testkeyspace",
  3201  					Shard:    "80-",
  3202  					PrimaryTermStartTime: &vttime.Time{
  3203  						Seconds:     200,
  3204  						Nanoseconds: 20,
  3205  					},
  3206  				},
  3207  			},
  3208  			lockedShards: []*vtctldatapb.Shard{
  3209  				{
  3210  					Keyspace: "testkeyspace",
  3211  					Name:     "80-",
  3212  				},
  3213  			},
  3214  			topoError: nil,
  3215  			req: &vtctldatapb.DeleteTabletsRequest{
  3216  				TabletAliases: []*topodatapb.TabletAlias{
  3217  					{
  3218  						// testkeyspace/-80
  3219  						Cell: "zone1",
  3220  						Uid:  100,
  3221  					},
  3222  				},
  3223  				AllowPrimary: true,
  3224  			},
  3225  			expected: &vtctldatapb.DeleteTabletsResponse{},
  3226  			expectedRemainingTablets: []*topodatapb.Tablet{
  3227  				{
  3228  					Alias: &topodatapb.TabletAlias{
  3229  						Cell: "zone1",
  3230  						Uid:  200,
  3231  					},
  3232  					Type:     topodatapb.TabletType_PRIMARY,
  3233  					Keyspace: "testkeyspace",
  3234  					Shard:    "80-",
  3235  					PrimaryTermStartTime: &vttime.Time{
  3236  						Seconds:     200,
  3237  						Nanoseconds: 20,
  3238  					},
  3239  				},
  3240  			},
  3241  			shouldErr: false,
  3242  		},
  3243  		{
  3244  			name: "topo server is down",
  3245  			tablets: []*topodatapb.Tablet{
  3246  				{
  3247  					Alias: &topodatapb.TabletAlias{
  3248  						Cell: "zone1",
  3249  						Uid:  100,
  3250  					},
  3251  					Type:     topodatapb.TabletType_REPLICA,
  3252  					Keyspace: "testkeyspace",
  3253  					Shard:    "-",
  3254  				},
  3255  			},
  3256  			lockedShards: nil,
  3257  			topoError:    assert.AnError,
  3258  			req: &vtctldatapb.DeleteTabletsRequest{
  3259  				TabletAliases: []*topodatapb.TabletAlias{
  3260  					{
  3261  						Cell: "zone1",
  3262  						Uid:  200,
  3263  					},
  3264  				},
  3265  			},
  3266  			expected: nil,
  3267  			expectedRemainingTablets: []*topodatapb.Tablet{
  3268  				{
  3269  					Alias: &topodatapb.TabletAlias{
  3270  						Cell: "zone1",
  3271  						Uid:  100,
  3272  					},
  3273  					Type:     topodatapb.TabletType_REPLICA,
  3274  					Keyspace: "testkeyspace",
  3275  					Shard:    "-",
  3276  				},
  3277  			},
  3278  			shouldErr: true,
  3279  		},
  3280  	}
  3281  
  3282  	for _, tt := range tests {
  3283  		tt := tt
  3284  
  3285  		t.Run(tt.name, func(t *testing.T) {
  3286  			t.Parallel()
  3287  
  3288  			if tt.req == nil {
  3289  				t.Skip("focusing on other tests")
  3290  			}
  3291  
  3292  			ctx := context.Background()
  3293  			ts, topofactory := memorytopo.NewServerAndFactory("zone1")
  3294  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  3295  				return NewVtctldServer(ts)
  3296  			})
  3297  
  3298  			// Setup tablets and shards
  3299  			testutil.AddTablets(ctx, t, ts, nil, tt.tablets...)
  3300  
  3301  			for key, updateFn := range tt.shardFieldUpdates {
  3302  				ks, shard, err := topoproto.ParseKeyspaceShard(key)
  3303  				require.NoError(t, err, "bad keyspace/shard provided in shardFieldUpdates: %s", key)
  3304  
  3305  				_, err = ts.UpdateShardFields(ctx, ks, shard, updateFn)
  3306  				require.NoError(t, err, "failed to update shard fields for %s", key)
  3307  			}
  3308  
  3309  			// Set locks
  3310  			for _, shard := range tt.lockedShards {
  3311  				lctx, unlock, lerr := ts.LockShard(ctx, shard.Keyspace, shard.Name, "testing locked shard")
  3312  				require.NoError(t, lerr, "cannot lock shard %s/%s", shard.Keyspace, shard.Name)
  3313  				// unlock at the end of the test, we don't care about this error
  3314  				// value anymore
  3315  				defer unlock(&lerr)
  3316  
  3317  				// we do, however, care that the lock context gets propogated
  3318  				// both to additional calls to lock, and to the actual RPC call.
  3319  				ctx = lctx
  3320  			}
  3321  
  3322  			// Set errors
  3323  			if tt.topoError != nil {
  3324  				topofactory.SetError(tt.topoError)
  3325  			}
  3326  
  3327  			checkRemainingTablets := func() {
  3328  				topofactory.SetError(nil)
  3329  
  3330  				resp, err := vtctld.GetTablets(ctx, &vtctldatapb.GetTabletsRequest{})
  3331  				assert.NoError(t, err, "cannot look up tablets from topo after issuing DeleteTablets request")
  3332  				testutil.AssertSameTablets(t, tt.expectedRemainingTablets, resp.Tablets)
  3333  			}
  3334  
  3335  			// Run the test
  3336  			resp, err := vtctld.DeleteTablets(ctx, tt.req)
  3337  			if tt.shouldErr {
  3338  				assert.Error(t, err)
  3339  
  3340  				if tt.expectedRemainingTablets != nil {
  3341  					checkRemainingTablets()
  3342  				}
  3343  
  3344  				return
  3345  			}
  3346  
  3347  			assert.NoError(t, err)
  3348  			utils.MustMatch(t, tt.expected, resp)
  3349  			checkRemainingTablets()
  3350  		})
  3351  	}
  3352  }
  3353  
  3354  func TestEmergencyReparentShard(t *testing.T) {
  3355  	t.Parallel()
  3356  
  3357  	tests := []struct {
  3358  		name    string
  3359  		ts      *topo.Server
  3360  		tmc     tmclient.TabletManagerClient
  3361  		tablets []*topodatapb.Tablet
  3362  
  3363  		req                 *vtctldatapb.EmergencyReparentShardRequest
  3364  		expected            *vtctldatapb.EmergencyReparentShardResponse
  3365  		expectEventsToOccur bool
  3366  		shouldErr           bool
  3367  	}{
  3368  		{
  3369  			name: "successful reparent",
  3370  			ts:   memorytopo.NewServer("zone1"),
  3371  			tablets: []*topodatapb.Tablet{
  3372  				{
  3373  					Alias: &topodatapb.TabletAlias{
  3374  						Cell: "zone1",
  3375  						Uid:  100,
  3376  					},
  3377  					Type: topodatapb.TabletType_PRIMARY,
  3378  					PrimaryTermStartTime: &vttime.Time{
  3379  						Seconds: 100,
  3380  					},
  3381  					Keyspace: "testkeyspace",
  3382  					Shard:    "-",
  3383  				},
  3384  				{
  3385  					Alias: &topodatapb.TabletAlias{
  3386  						Cell: "zone1",
  3387  						Uid:  200,
  3388  					},
  3389  					Type:     topodatapb.TabletType_REPLICA,
  3390  					Keyspace: "testkeyspace",
  3391  					Shard:    "-",
  3392  				},
  3393  				{
  3394  					Alias: &topodatapb.TabletAlias{
  3395  						Cell: "zone1",
  3396  						Uid:  101,
  3397  					},
  3398  					Type:     topodatapb.TabletType_RDONLY,
  3399  					Keyspace: "testkeyspace",
  3400  					Shard:    "-",
  3401  				},
  3402  			},
  3403  			tmc: &testutil.TabletManagerClient{
  3404  				DemotePrimaryResults: map[string]struct {
  3405  					Status *replicationdatapb.PrimaryStatus
  3406  					Error  error
  3407  				}{
  3408  					"zone1-0000000100": {
  3409  						Status: &replicationdatapb.PrimaryStatus{
  3410  							Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5",
  3411  						},
  3412  					},
  3413  				},
  3414  				PopulateReparentJournalResults: map[string]error{
  3415  					"zone1-0000000200": nil,
  3416  				},
  3417  				PromoteReplicaResults: map[string]struct {
  3418  					Result string
  3419  					Error  error
  3420  				}{
  3421  					"zone1-0000000200": {},
  3422  				},
  3423  				PrimaryPositionResults: map[string]struct {
  3424  					Position string
  3425  					Error    error
  3426  				}{
  3427  					"zone1-0000000200": {},
  3428  				},
  3429  				SetReplicationSourceResults: map[string]error{
  3430  					"zone1-0000000100": nil,
  3431  					"zone1-0000000101": nil,
  3432  				},
  3433  				StopReplicationAndGetStatusResults: map[string]struct {
  3434  					StopStatus *replicationdatapb.StopReplicationStatus
  3435  					Error      error
  3436  				}{
  3437  					"zone1-0000000100": {
  3438  						Error: mysql.ErrNotReplica,
  3439  					},
  3440  					"zone1-0000000101": {
  3441  						Error: assert.AnError,
  3442  					},
  3443  					"zone1-0000000200": {
  3444  						StopStatus: &replicationdatapb.StopReplicationStatus{
  3445  							Before: &replicationdatapb.Status{IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)},
  3446  							After: &replicationdatapb.Status{
  3447  								SourceUuid:       "3E11FA47-71CA-11E1-9E33-C80AA9429562",
  3448  								RelayLogPosition: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5",
  3449  								Position:         "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5",
  3450  							},
  3451  						},
  3452  					},
  3453  				},
  3454  				WaitForPositionResults: map[string]map[string]error{
  3455  					"zone1-0000000100": {
  3456  						"MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5": nil,
  3457  					},
  3458  					"zone1-0000000200": {
  3459  						"MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5": nil,
  3460  					},
  3461  				},
  3462  			},
  3463  			req: &vtctldatapb.EmergencyReparentShardRequest{
  3464  				Keyspace: "testkeyspace",
  3465  				Shard:    "-",
  3466  				NewPrimary: &topodatapb.TabletAlias{
  3467  					Cell: "zone1",
  3468  					Uid:  200,
  3469  				},
  3470  				WaitReplicasTimeout: protoutil.DurationToProto(time.Millisecond * 10),
  3471  			},
  3472  			expected: &vtctldatapb.EmergencyReparentShardResponse{
  3473  				Keyspace: "testkeyspace",
  3474  				Shard:    "-",
  3475  				PromotedPrimary: &topodatapb.TabletAlias{
  3476  					Cell: "zone1",
  3477  					Uid:  200,
  3478  				},
  3479  			},
  3480  			expectEventsToOccur: true,
  3481  			shouldErr:           false,
  3482  		},
  3483  		{
  3484  			// Note: this is testing the error-handling done in
  3485  			// (*VtctldServer).EmergencyReparentShard, not the logic of an ERS.
  3486  			// That logic is tested in reparentutil, and not here. Therefore,
  3487  			// the simplest way to trigger a failure is to attempt an ERS on a
  3488  			// shard that does not exist.
  3489  			name:    "failed reparent",
  3490  			ts:      memorytopo.NewServer("zone1"),
  3491  			tablets: nil,
  3492  
  3493  			req: &vtctldatapb.EmergencyReparentShardRequest{
  3494  				Keyspace: "testkeyspace",
  3495  				Shard:    "-",
  3496  			},
  3497  			expectEventsToOccur: false,
  3498  			shouldErr:           true,
  3499  		},
  3500  		{
  3501  			name: "invalid WaitReplicasTimeout",
  3502  			req: &vtctldatapb.EmergencyReparentShardRequest{
  3503  				WaitReplicasTimeout: &vttime.Duration{
  3504  					Seconds: -1,
  3505  					Nanos:   1,
  3506  				},
  3507  			},
  3508  			shouldErr: true,
  3509  		},
  3510  	}
  3511  
  3512  	ctx := context.Background()
  3513  
  3514  	for _, tt := range tests {
  3515  		tt := tt
  3516  
  3517  		t.Run(tt.name, func(t *testing.T) {
  3518  			t.Parallel()
  3519  
  3520  			testutil.AddTablets(ctx, t, tt.ts, &testutil.AddTabletOptions{
  3521  				AlsoSetShardPrimary:  true,
  3522  				ForceSetShardPrimary: true,
  3523  				SkipShardCreation:    false,
  3524  			}, tt.tablets...)
  3525  
  3526  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, tt.ts, tt.tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  3527  				return NewVtctldServer(ts)
  3528  			})
  3529  			resp, err := vtctld.EmergencyReparentShard(ctx, tt.req)
  3530  
  3531  			// We defer this because we want to check in both error and non-
  3532  			// error cases, but after the main set of assertions for those
  3533  			// cases.
  3534  			defer func() {
  3535  				if !tt.expectEventsToOccur {
  3536  					testutil.AssertNoLogutilEventsOccurred(t, resp, "expected no events to occur during ERS")
  3537  
  3538  					return
  3539  				}
  3540  
  3541  				testutil.AssertLogutilEventsOccurred(t, resp, "expected events to occur during ERS")
  3542  			}()
  3543  
  3544  			if tt.shouldErr {
  3545  				assert.Error(t, err)
  3546  
  3547  				return
  3548  			}
  3549  
  3550  			assert.NoError(t, err)
  3551  			testutil.AssertEmergencyReparentShardResponsesEqual(t, tt.expected, resp)
  3552  		})
  3553  	}
  3554  }
  3555  
  3556  func TestExecuteFetchAsApp(t *testing.T) {
  3557  	t.Parallel()
  3558  
  3559  	tests := []struct {
  3560  		name      string
  3561  		tablet    *topodatapb.Tablet
  3562  		tmc       *testutil.TabletManagerClient
  3563  		req       *vtctldatapb.ExecuteFetchAsAppRequest
  3564  		expected  *vtctldatapb.ExecuteFetchAsAppResponse
  3565  		shouldErr bool
  3566  	}{
  3567  		{
  3568  			name: "ok",
  3569  			tablet: &topodatapb.Tablet{
  3570  				Alias: &topodatapb.TabletAlias{
  3571  					Cell: "zone1",
  3572  					Uid:  100,
  3573  				},
  3574  			},
  3575  			tmc: &testutil.TabletManagerClient{
  3576  				ExecuteFetchAsAppResults: map[string]struct {
  3577  					Response *querypb.QueryResult
  3578  					Error    error
  3579  				}{
  3580  					"zone1-0000000100": {
  3581  						Response: &querypb.QueryResult{
  3582  							InsertId: 100,
  3583  						},
  3584  					},
  3585  				},
  3586  			},
  3587  			req: &vtctldatapb.ExecuteFetchAsAppRequest{
  3588  				TabletAlias: &topodatapb.TabletAlias{
  3589  					Cell: "zone1",
  3590  					Uid:  100,
  3591  				},
  3592  				Query: "select 1;",
  3593  			},
  3594  			expected: &vtctldatapb.ExecuteFetchAsAppResponse{
  3595  				Result: &querypb.QueryResult{
  3596  					InsertId: 100,
  3597  				},
  3598  			},
  3599  		},
  3600  		{
  3601  			name: "tablet not found",
  3602  			tablet: &topodatapb.Tablet{
  3603  				Alias: &topodatapb.TabletAlias{
  3604  					Cell: "zone1",
  3605  					Uid:  100,
  3606  				},
  3607  			},
  3608  			tmc: &testutil.TabletManagerClient{
  3609  				ExecuteFetchAsAppResults: map[string]struct {
  3610  					Response *querypb.QueryResult
  3611  					Error    error
  3612  				}{
  3613  					"zone1-0000000100": {
  3614  						Response: &querypb.QueryResult{
  3615  							InsertId: 100,
  3616  						},
  3617  					},
  3618  				},
  3619  			},
  3620  			req: &vtctldatapb.ExecuteFetchAsAppRequest{
  3621  				TabletAlias: &topodatapb.TabletAlias{
  3622  					Cell: "zone1",
  3623  					Uid:  404,
  3624  				},
  3625  				Query: "select 1;",
  3626  			},
  3627  			shouldErr: true,
  3628  		},
  3629  		{
  3630  			name: "query error",
  3631  			tablet: &topodatapb.Tablet{
  3632  				Alias: &topodatapb.TabletAlias{
  3633  					Cell: "zone1",
  3634  					Uid:  100,
  3635  				},
  3636  			},
  3637  			tmc: &testutil.TabletManagerClient{
  3638  				ExecuteFetchAsAppResults: map[string]struct {
  3639  					Response *querypb.QueryResult
  3640  					Error    error
  3641  				}{
  3642  					"zone1-0000000100": {
  3643  						Error: assert.AnError,
  3644  					},
  3645  				},
  3646  			},
  3647  			req: &vtctldatapb.ExecuteFetchAsAppRequest{
  3648  				TabletAlias: &topodatapb.TabletAlias{
  3649  					Cell: "zone1",
  3650  					Uid:  100,
  3651  				},
  3652  				Query: "select 1;",
  3653  			},
  3654  			shouldErr: true,
  3655  		},
  3656  	}
  3657  
  3658  	for _, tt := range tests {
  3659  		tt := tt
  3660  		t.Run(tt.name, func(t *testing.T) {
  3661  			t.Parallel()
  3662  
  3663  			ctx := context.Background()
  3664  			ts := memorytopo.NewServer("zone1")
  3665  			testutil.AddTablet(ctx, t, ts, tt.tablet, nil)
  3666  
  3667  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, tt.tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  3668  				return NewVtctldServer(ts)
  3669  			})
  3670  			resp, err := vtctld.ExecuteFetchAsApp(ctx, tt.req)
  3671  			if tt.shouldErr {
  3672  				assert.Error(t, err)
  3673  				return
  3674  			}
  3675  
  3676  			require.NoError(t, err)
  3677  			utils.MustMatch(t, tt.expected, resp)
  3678  		})
  3679  	}
  3680  }
  3681  
  3682  func TestExecuteFetchAsDBA(t *testing.T) {
  3683  	t.Parallel()
  3684  
  3685  	tests := []struct {
  3686  		name      string
  3687  		tablet    *topodatapb.Tablet
  3688  		tmc       *testutil.TabletManagerClient
  3689  		req       *vtctldatapb.ExecuteFetchAsDBARequest
  3690  		expected  *vtctldatapb.ExecuteFetchAsDBAResponse
  3691  		shouldErr bool
  3692  	}{
  3693  		{
  3694  			name: "ok",
  3695  			tablet: &topodatapb.Tablet{
  3696  				Alias: &topodatapb.TabletAlias{
  3697  					Cell: "zone1",
  3698  					Uid:  100,
  3699  				},
  3700  			},
  3701  			tmc: &testutil.TabletManagerClient{
  3702  				ExecuteFetchAsDbaResults: map[string]struct {
  3703  					Response *querypb.QueryResult
  3704  					Error    error
  3705  				}{
  3706  					"zone1-0000000100": {
  3707  						Response: &querypb.QueryResult{
  3708  							InsertId: 100,
  3709  						},
  3710  					},
  3711  				},
  3712  			},
  3713  			req: &vtctldatapb.ExecuteFetchAsDBARequest{
  3714  				TabletAlias: &topodatapb.TabletAlias{
  3715  					Cell: "zone1",
  3716  					Uid:  100,
  3717  				},
  3718  				Query: "select 1;",
  3719  			},
  3720  			expected: &vtctldatapb.ExecuteFetchAsDBAResponse{
  3721  				Result: &querypb.QueryResult{
  3722  					InsertId: 100,
  3723  				},
  3724  			},
  3725  		},
  3726  		{
  3727  			name: "tablet not found",
  3728  			tablet: &topodatapb.Tablet{
  3729  				Alias: &topodatapb.TabletAlias{
  3730  					Cell: "zone1",
  3731  					Uid:  100,
  3732  				},
  3733  			},
  3734  			tmc: &testutil.TabletManagerClient{
  3735  				ExecuteFetchAsDbaResults: map[string]struct {
  3736  					Response *querypb.QueryResult
  3737  					Error    error
  3738  				}{
  3739  					"zone1-0000000100": {
  3740  						Response: &querypb.QueryResult{
  3741  							InsertId: 100,
  3742  						},
  3743  					},
  3744  				},
  3745  			},
  3746  			req: &vtctldatapb.ExecuteFetchAsDBARequest{
  3747  				TabletAlias: &topodatapb.TabletAlias{
  3748  					Cell: "zone1",
  3749  					Uid:  404,
  3750  				},
  3751  				Query: "select 1;",
  3752  			},
  3753  			shouldErr: true,
  3754  		},
  3755  		{
  3756  			name: "query error",
  3757  			tablet: &topodatapb.Tablet{
  3758  				Alias: &topodatapb.TabletAlias{
  3759  					Cell: "zone1",
  3760  					Uid:  100,
  3761  				},
  3762  			},
  3763  			tmc: &testutil.TabletManagerClient{
  3764  				ExecuteFetchAsDbaResults: map[string]struct {
  3765  					Response *querypb.QueryResult
  3766  					Error    error
  3767  				}{
  3768  					"zone1-0000000100": {
  3769  						Error: assert.AnError,
  3770  					},
  3771  				},
  3772  			},
  3773  			req: &vtctldatapb.ExecuteFetchAsDBARequest{
  3774  				TabletAlias: &topodatapb.TabletAlias{
  3775  					Cell: "zone1",
  3776  					Uid:  100,
  3777  				},
  3778  				Query: "select 1;",
  3779  			},
  3780  			shouldErr: true,
  3781  		},
  3782  	}
  3783  
  3784  	for _, tt := range tests {
  3785  		tt := tt
  3786  		t.Run(tt.name, func(t *testing.T) {
  3787  			t.Parallel()
  3788  
  3789  			ctx := context.Background()
  3790  			ts := memorytopo.NewServer("zone1")
  3791  			testutil.AddTablet(ctx, t, ts, tt.tablet, nil)
  3792  
  3793  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, tt.tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  3794  				return NewVtctldServer(ts)
  3795  			})
  3796  			resp, err := vtctld.ExecuteFetchAsDBA(ctx, tt.req)
  3797  			if tt.shouldErr {
  3798  				assert.Error(t, err)
  3799  				return
  3800  			}
  3801  
  3802  			require.NoError(t, err)
  3803  			utils.MustMatch(t, tt.expected, resp)
  3804  		})
  3805  	}
  3806  }
  3807  
  3808  func TestExecuteHook(t *testing.T) {
  3809  	t.Parallel()
  3810  
  3811  	tests := []struct {
  3812  		name      string
  3813  		ts        *topo.Server
  3814  		tmc       tmclient.TabletManagerClient
  3815  		tablets   []*topodatapb.Tablet
  3816  		req       *vtctldatapb.ExecuteHookRequest
  3817  		shouldErr bool
  3818  	}{
  3819  		{
  3820  			name: "ok",
  3821  			ts:   memorytopo.NewServer("zone1"),
  3822  			tmc: &testutil.TabletManagerClient{
  3823  				ExecuteHookResults: map[string]struct {
  3824  					Response *hk.HookResult
  3825  					Error    error
  3826  				}{
  3827  					"zone1-0000000100": {
  3828  						Response: &hk.HookResult{},
  3829  					},
  3830  				},
  3831  			},
  3832  			tablets: []*topodatapb.Tablet{
  3833  				{
  3834  					Alias: &topodatapb.TabletAlias{
  3835  						Cell: "zone1",
  3836  						Uid:  100,
  3837  					},
  3838  				},
  3839  			},
  3840  			req: &vtctldatapb.ExecuteHookRequest{
  3841  				TabletAlias: &topodatapb.TabletAlias{
  3842  					Cell: "zone1",
  3843  					Uid:  100,
  3844  				},
  3845  				TabletHookRequest: &tabletmanagerdatapb.ExecuteHookRequest{},
  3846  			},
  3847  		},
  3848  		{
  3849  			name: "nil hook request",
  3850  			ts:   memorytopo.NewServer("zone1"),
  3851  			tmc: &testutil.TabletManagerClient{
  3852  				ExecuteHookResults: map[string]struct {
  3853  					Response *hk.HookResult
  3854  					Error    error
  3855  				}{
  3856  					"zone1-0000000100": {
  3857  						Response: &hk.HookResult{},
  3858  					},
  3859  				},
  3860  			},
  3861  			tablets: []*topodatapb.Tablet{
  3862  				{
  3863  					Alias: &topodatapb.TabletAlias{
  3864  						Cell: "zone1",
  3865  						Uid:  100,
  3866  					},
  3867  				},
  3868  			},
  3869  			req: &vtctldatapb.ExecuteHookRequest{
  3870  				TabletAlias: &topodatapb.TabletAlias{
  3871  					Cell: "zone1",
  3872  					Uid:  100,
  3873  				},
  3874  				TabletHookRequest: nil,
  3875  			},
  3876  			shouldErr: true,
  3877  		},
  3878  		{
  3879  			name: "hook with slash",
  3880  			ts:   memorytopo.NewServer("zone1"),
  3881  			tmc: &testutil.TabletManagerClient{
  3882  				ExecuteHookResults: map[string]struct {
  3883  					Response *hk.HookResult
  3884  					Error    error
  3885  				}{
  3886  					"zone1-0000000100": {
  3887  						Response: &hk.HookResult{},
  3888  					},
  3889  				},
  3890  			},
  3891  			tablets: []*topodatapb.Tablet{
  3892  				{
  3893  					Alias: &topodatapb.TabletAlias{
  3894  						Cell: "zone1",
  3895  						Uid:  100,
  3896  					},
  3897  				},
  3898  			},
  3899  			req: &vtctldatapb.ExecuteHookRequest{
  3900  				TabletAlias: &topodatapb.TabletAlias{
  3901  					Cell: "zone1",
  3902  					Uid:  100,
  3903  				},
  3904  				TabletHookRequest: &tabletmanagerdatapb.ExecuteHookRequest{
  3905  					Name: "hooks/cannot/contain/slashes",
  3906  				},
  3907  			},
  3908  			shouldErr: true,
  3909  		},
  3910  		{
  3911  			name: "no such tablet",
  3912  			ts:   memorytopo.NewServer("zone1"),
  3913  			tmc: &testutil.TabletManagerClient{
  3914  				ExecuteHookResults: map[string]struct {
  3915  					Response *hk.HookResult
  3916  					Error    error
  3917  				}{
  3918  					"zone1-0000000100": {
  3919  						Response: &hk.HookResult{},
  3920  					},
  3921  				},
  3922  			},
  3923  			tablets: []*topodatapb.Tablet{
  3924  				{
  3925  					Alias: &topodatapb.TabletAlias{
  3926  						Cell: "zone1",
  3927  						Uid:  100,
  3928  					},
  3929  				},
  3930  			},
  3931  			req: &vtctldatapb.ExecuteHookRequest{
  3932  				TabletAlias: &topodatapb.TabletAlias{
  3933  					Cell: "zone1",
  3934  					Uid:  404,
  3935  				},
  3936  				TabletHookRequest: &tabletmanagerdatapb.ExecuteHookRequest{},
  3937  			},
  3938  			shouldErr: true,
  3939  		},
  3940  		{
  3941  			name: "tablet hook failure",
  3942  			ts:   memorytopo.NewServer("zone1"),
  3943  			tmc: &testutil.TabletManagerClient{
  3944  				ExecuteHookResults: map[string]struct {
  3945  					Response *hk.HookResult
  3946  					Error    error
  3947  				}{
  3948  					"zone1-0000000100": {
  3949  						Error: assert.AnError,
  3950  					},
  3951  				},
  3952  			},
  3953  			tablets: []*topodatapb.Tablet{
  3954  				{
  3955  					Alias: &topodatapb.TabletAlias{
  3956  						Cell: "zone1",
  3957  						Uid:  100,
  3958  					},
  3959  				},
  3960  			},
  3961  			req: &vtctldatapb.ExecuteHookRequest{
  3962  				TabletAlias: &topodatapb.TabletAlias{
  3963  					Cell: "zone1",
  3964  					Uid:  100,
  3965  				},
  3966  				TabletHookRequest: &tabletmanagerdatapb.ExecuteHookRequest{},
  3967  			},
  3968  			shouldErr: true,
  3969  		},
  3970  	}
  3971  
  3972  	ctx := context.Background()
  3973  	for _, tt := range tests {
  3974  		tt := tt
  3975  		t.Run(tt.name, func(t *testing.T) {
  3976  			t.Parallel()
  3977  
  3978  			testutil.AddTablets(ctx, t, tt.ts, nil, tt.tablets...)
  3979  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, tt.ts, tt.tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  3980  				return NewVtctldServer(ts)
  3981  			})
  3982  
  3983  			_, err := vtctld.ExecuteHook(ctx, tt.req)
  3984  			if tt.shouldErr {
  3985  				assert.Error(t, err)
  3986  				return
  3987  			}
  3988  
  3989  			assert.NoError(t, err)
  3990  		})
  3991  	}
  3992  }
  3993  
  3994  func TestFindAllShardsInKeyspace(t *testing.T) {
  3995  	t.Parallel()
  3996  
  3997  	ctx := context.Background()
  3998  	ts := memorytopo.NewServer("cell1")
  3999  	vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  4000  		return NewVtctldServer(ts)
  4001  	})
  4002  
  4003  	ks := &vtctldatapb.Keyspace{
  4004  		Name:     "testkeyspace",
  4005  		Keyspace: &topodatapb.Keyspace{},
  4006  	}
  4007  	testutil.AddKeyspace(ctx, t, ts, ks)
  4008  
  4009  	si1, err := ts.GetOrCreateShard(ctx, ks.Name, "-80")
  4010  	require.NoError(t, err)
  4011  	si2, err := ts.GetOrCreateShard(ctx, ks.Name, "80-")
  4012  	require.NoError(t, err)
  4013  
  4014  	resp, err := vtctld.FindAllShardsInKeyspace(ctx, &vtctldatapb.FindAllShardsInKeyspaceRequest{Keyspace: ks.Name})
  4015  	assert.NoError(t, err)
  4016  	assert.NotNil(t, resp)
  4017  
  4018  	expected := map[string]*vtctldatapb.Shard{
  4019  		"-80": {
  4020  			Keyspace: ks.Name,
  4021  			Name:     "-80",
  4022  			Shard:    si1.Shard,
  4023  		},
  4024  		"80-": {
  4025  			Keyspace: ks.Name,
  4026  			Name:     "80-",
  4027  			Shard:    si2.Shard,
  4028  		},
  4029  	}
  4030  
  4031  	utils.MustMatch(t, expected, resp.Shards)
  4032  
  4033  	_, err = vtctld.FindAllShardsInKeyspace(ctx, &vtctldatapb.FindAllShardsInKeyspaceRequest{Keyspace: "nothing"})
  4034  	assert.Error(t, err)
  4035  }
  4036  
  4037  func TestGetBackups(t *testing.T) {
  4038  	ctx := context.Background()
  4039  	ts := memorytopo.NewServer()
  4040  	vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  4041  		return NewVtctldServer(ts)
  4042  	})
  4043  
  4044  	testutil.BackupStorage.Backups = map[string][]string{
  4045  		"testkeyspace/-": {"backup1", "backup2"},
  4046  	}
  4047  
  4048  	expected := &vtctldatapb.GetBackupsResponse{
  4049  		Backups: []*mysqlctlpb.BackupInfo{
  4050  			{
  4051  				Directory: "testkeyspace/-",
  4052  				Name:      "backup1",
  4053  				Keyspace:  "testkeyspace",
  4054  				Shard:     "-",
  4055  			},
  4056  			{
  4057  				Directory: "testkeyspace/-",
  4058  				Name:      "backup2",
  4059  				Keyspace:  "testkeyspace",
  4060  				Shard:     "-",
  4061  			},
  4062  		},
  4063  	}
  4064  
  4065  	resp, err := vtctld.GetBackups(ctx, &vtctldatapb.GetBackupsRequest{
  4066  		Keyspace: "testkeyspace",
  4067  		Shard:    "-",
  4068  	})
  4069  	assert.NoError(t, err)
  4070  	utils.MustMatch(t, expected, resp)
  4071  
  4072  	t.Run("no backupstorage", func(t *testing.T) {
  4073  		backupstorage.BackupStorageImplementation = "doesnotexist"
  4074  		defer func() { backupstorage.BackupStorageImplementation = testutil.BackupStorageImplementation }()
  4075  
  4076  		_, err := vtctld.GetBackups(ctx, &vtctldatapb.GetBackupsRequest{
  4077  			Keyspace: "testkeyspace",
  4078  			Shard:    "-",
  4079  		})
  4080  		assert.Error(t, err)
  4081  	})
  4082  
  4083  	t.Run("listbackups error", func(t *testing.T) {
  4084  		testutil.BackupStorage.ListBackupsError = assert.AnError
  4085  		defer func() { testutil.BackupStorage.ListBackupsError = nil }()
  4086  
  4087  		_, err := vtctld.GetBackups(ctx, &vtctldatapb.GetBackupsRequest{
  4088  			Keyspace: "testkeyspace",
  4089  			Shard:    "-",
  4090  		})
  4091  		assert.Error(t, err)
  4092  	})
  4093  
  4094  	t.Run("parsing times and aliases", func(t *testing.T) {
  4095  		testutil.BackupStorage.Backups["ks2/-80"] = []string{
  4096  			"2021-06-11.123456.zone1-101",
  4097  		}
  4098  
  4099  		resp, err := vtctld.GetBackups(ctx, &vtctldatapb.GetBackupsRequest{
  4100  			Keyspace: "ks2",
  4101  			Shard:    "-80",
  4102  		})
  4103  		require.NoError(t, err)
  4104  		expected := &vtctldatapb.GetBackupsResponse{
  4105  			Backups: []*mysqlctlpb.BackupInfo{
  4106  				{
  4107  					Directory: "ks2/-80",
  4108  					Name:      "2021-06-11.123456.zone1-101",
  4109  					Keyspace:  "ks2",
  4110  					Shard:     "-80",
  4111  					Time:      protoutil.TimeToProto(time.Date(2021, time.June, 11, 12, 34, 56, 0, time.UTC)),
  4112  					TabletAlias: &topodatapb.TabletAlias{
  4113  						Cell: "zone1",
  4114  						Uid:  101,
  4115  					},
  4116  				},
  4117  			},
  4118  		}
  4119  		utils.MustMatch(t, expected, resp)
  4120  	})
  4121  
  4122  	t.Run("limiting", func(t *testing.T) {
  4123  		unlimited, err := vtctld.GetBackups(ctx, &vtctldatapb.GetBackupsRequest{
  4124  			Keyspace: "testkeyspace",
  4125  			Shard:    "-",
  4126  		})
  4127  		require.NoError(t, err)
  4128  
  4129  		limited, err := vtctld.GetBackups(ctx, &vtctldatapb.GetBackupsRequest{
  4130  			Keyspace: "testkeyspace",
  4131  			Shard:    "-",
  4132  			Limit:    1,
  4133  		})
  4134  		require.NoError(t, err)
  4135  
  4136  		assert.Equal(t, len(limited.Backups), 1, "expected limited backups to have length 1")
  4137  		assert.Less(t, len(limited.Backups), len(unlimited.Backups), "expected limited backups to be less than unlimited")
  4138  		utils.MustMatch(t, limited.Backups[0], unlimited.Backups[len(unlimited.Backups)-1], "expected limiting to keep N most recent")
  4139  	})
  4140  }
  4141  
  4142  func TestGetKeyspace(t *testing.T) {
  4143  	t.Parallel()
  4144  
  4145  	ctx := context.Background()
  4146  	ts := memorytopo.NewServer("cell1")
  4147  	vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  4148  		return NewVtctldServer(ts)
  4149  	})
  4150  
  4151  	expected := &vtctldatapb.GetKeyspaceResponse{
  4152  		Keyspace: &vtctldatapb.Keyspace{
  4153  			Name:     "testkeyspace",
  4154  			Keyspace: &topodatapb.Keyspace{},
  4155  		},
  4156  	}
  4157  	testutil.AddKeyspace(ctx, t, ts, expected.Keyspace)
  4158  
  4159  	ks, err := vtctld.GetKeyspace(ctx, &vtctldatapb.GetKeyspaceRequest{Keyspace: expected.Keyspace.Name})
  4160  	assert.NoError(t, err)
  4161  	utils.MustMatch(t, expected, ks)
  4162  
  4163  	_, err = vtctld.GetKeyspace(ctx, &vtctldatapb.GetKeyspaceRequest{Keyspace: "notfound"})
  4164  	assert.Error(t, err)
  4165  }
  4166  
  4167  func TestGetCellInfoNames(t *testing.T) {
  4168  	t.Parallel()
  4169  
  4170  	ctx := context.Background()
  4171  	ts := memorytopo.NewServer("cell1", "cell2", "cell3")
  4172  	vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  4173  		return NewVtctldServer(ts)
  4174  	})
  4175  
  4176  	resp, err := vtctld.GetCellInfoNames(ctx, &vtctldatapb.GetCellInfoNamesRequest{})
  4177  	assert.NoError(t, err)
  4178  	assert.ElementsMatch(t, []string{"cell1", "cell2", "cell3"}, resp.Names)
  4179  
  4180  	ts = memorytopo.NewServer()
  4181  	vtctld = testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  4182  		return NewVtctldServer(ts)
  4183  	})
  4184  
  4185  	resp, err = vtctld.GetCellInfoNames(ctx, &vtctldatapb.GetCellInfoNamesRequest{})
  4186  	assert.NoError(t, err)
  4187  	assert.Empty(t, resp.Names)
  4188  
  4189  	ts, topofactory := memorytopo.NewServerAndFactory("cell1")
  4190  	vtctld = testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  4191  		return NewVtctldServer(ts)
  4192  	})
  4193  
  4194  	topofactory.SetError(assert.AnError)
  4195  	_, err = vtctld.GetCellInfoNames(ctx, &vtctldatapb.GetCellInfoNamesRequest{})
  4196  	assert.Error(t, err)
  4197  }
  4198  
  4199  func TestGetCellInfo(t *testing.T) {
  4200  	t.Parallel()
  4201  
  4202  	ctx := context.Background()
  4203  	ts := memorytopo.NewServer()
  4204  	vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  4205  		return NewVtctldServer(ts)
  4206  	})
  4207  
  4208  	expected := &topodatapb.CellInfo{
  4209  		ServerAddress: "example.com",
  4210  		Root:          "vitess",
  4211  	}
  4212  	input := proto.Clone(expected).(*topodatapb.CellInfo)
  4213  	require.NoError(t, ts.CreateCellInfo(ctx, "cell1", input))
  4214  
  4215  	resp, err := vtctld.GetCellInfo(ctx, &vtctldatapb.GetCellInfoRequest{Cell: "cell1"})
  4216  	assert.NoError(t, err)
  4217  	utils.MustMatch(t, expected, resp.CellInfo)
  4218  
  4219  	_, err = vtctld.GetCellInfo(ctx, &vtctldatapb.GetCellInfoRequest{Cell: "does_not_exist"})
  4220  	assert.Error(t, err)
  4221  
  4222  	_, err = vtctld.GetCellInfo(ctx, &vtctldatapb.GetCellInfoRequest{})
  4223  	assert.Error(t, err)
  4224  }
  4225  
  4226  func TestGetCellsAliases(t *testing.T) {
  4227  	t.Parallel()
  4228  
  4229  	ctx := context.Background()
  4230  	ts := memorytopo.NewServer("c11", "c12", "c13", "c21", "c22")
  4231  	vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  4232  		return NewVtctldServer(ts)
  4233  	})
  4234  
  4235  	alias1 := &topodatapb.CellsAlias{
  4236  		Cells: []string{"c11", "c12", "c13"},
  4237  	}
  4238  	alias2 := &topodatapb.CellsAlias{
  4239  		Cells: []string{"c21", "c22"},
  4240  	}
  4241  
  4242  	for i, alias := range []*topodatapb.CellsAlias{alias1, alias2} {
  4243  		input := proto.Clone(alias).(*topodatapb.CellsAlias)
  4244  		name := fmt.Sprintf("a%d", i+1)
  4245  		require.NoError(t, ts.CreateCellsAlias(ctx, name, input), "cannot create cells alias %d (idx = %d) = %+v", i+1, i, input)
  4246  	}
  4247  
  4248  	expected := map[string]*topodatapb.CellsAlias{
  4249  		"a1": alias1,
  4250  		"a2": alias2,
  4251  	}
  4252  
  4253  	resp, err := vtctld.GetCellsAliases(ctx, &vtctldatapb.GetCellsAliasesRequest{})
  4254  	assert.NoError(t, err)
  4255  	utils.MustMatch(t, expected, resp.Aliases)
  4256  
  4257  	ts, topofactory := memorytopo.NewServerAndFactory()
  4258  	vtctld = testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  4259  		return NewVtctldServer(ts)
  4260  	})
  4261  
  4262  	topofactory.SetError(assert.AnError)
  4263  	_, err = vtctld.GetCellsAliases(ctx, &vtctldatapb.GetCellsAliasesRequest{})
  4264  	assert.Error(t, err)
  4265  }
  4266  
  4267  func TestGetFullStatus(t *testing.T) {
  4268  	t.Parallel()
  4269  
  4270  	tests := []struct {
  4271  		name       string
  4272  		cells      []string
  4273  		tablets    []*topodatapb.Tablet
  4274  		req        *vtctldatapb.GetFullStatusRequest
  4275  		serverUUID string
  4276  		shouldErr  bool
  4277  	}{
  4278  		{
  4279  			name:  "success",
  4280  			cells: []string{"zone1"},
  4281  			tablets: []*topodatapb.Tablet{
  4282  				{
  4283  					Alias: &topodatapb.TabletAlias{
  4284  						Cell: "zone1",
  4285  						Uid:  100,
  4286  					},
  4287  					Keyspace: "ks",
  4288  					Shard:    "0",
  4289  					Type:     topodatapb.TabletType_REPLICA,
  4290  				},
  4291  			},
  4292  			req: &vtctldatapb.GetFullStatusRequest{
  4293  				TabletAlias: &topodatapb.TabletAlias{
  4294  					Cell: "zone1",
  4295  					Uid:  100,
  4296  				},
  4297  			},
  4298  			serverUUID: "abcd",
  4299  			shouldErr:  false,
  4300  		},
  4301  		{
  4302  			name:  "tablet not found",
  4303  			cells: []string{"zone1"},
  4304  			tablets: []*topodatapb.Tablet{
  4305  				{
  4306  					Alias: &topodatapb.TabletAlias{
  4307  						Cell: "zone1",
  4308  						Uid:  200,
  4309  					},
  4310  					Keyspace: "ks",
  4311  					Shard:    "0",
  4312  					Type:     topodatapb.TabletType_REPLICA,
  4313  				},
  4314  			},
  4315  			req: &vtctldatapb.GetFullStatusRequest{
  4316  				TabletAlias: &topodatapb.TabletAlias{
  4317  					Cell: "zone1",
  4318  					Uid:  100,
  4319  				},
  4320  			},
  4321  			shouldErr: true,
  4322  		},
  4323  	}
  4324  
  4325  	for _, tt := range tests {
  4326  		tt := tt
  4327  
  4328  		t.Run(tt.name, func(t *testing.T) {
  4329  			t.Parallel()
  4330  
  4331  			ctx := context.Background()
  4332  			ts := memorytopo.NewServer(tt.cells...)
  4333  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, &testutil.TabletManagerClient{
  4334  				TopoServer: ts,
  4335  				FullStatusResult: &replicationdatapb.FullStatus{
  4336  					ServerUuid: tt.serverUUID,
  4337  				},
  4338  			}, func(ts *topo.Server) vtctlservicepb.VtctldServer { return NewVtctldServer(ts) })
  4339  
  4340  			testutil.AddTablets(ctx, t, ts, &testutil.AddTabletOptions{
  4341  				AlsoSetShardPrimary: true,
  4342  			}, tt.tablets...)
  4343  
  4344  			resp, err := vtctld.GetFullStatus(ctx, tt.req)
  4345  			if tt.shouldErr {
  4346  				assert.Error(t, err)
  4347  				return
  4348  			}
  4349  
  4350  			assert.NoError(t, err)
  4351  			utils.MustMatch(t, tt.serverUUID, resp.Status.ServerUuid)
  4352  		})
  4353  	}
  4354  }
  4355  
  4356  func TestGetKeyspaces(t *testing.T) {
  4357  	t.Parallel()
  4358  
  4359  	ctx := context.Background()
  4360  	ts, topofactory := memorytopo.NewServerAndFactory("cell1")
  4361  	vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  4362  		return NewVtctldServer(ts)
  4363  	})
  4364  
  4365  	resp, err := vtctld.GetKeyspaces(ctx, &vtctldatapb.GetKeyspacesRequest{})
  4366  	assert.NoError(t, err)
  4367  	assert.Empty(t, resp.Keyspaces)
  4368  
  4369  	expected := []*vtctldatapb.Keyspace{
  4370  		{
  4371  			Name:     "ks1",
  4372  			Keyspace: &topodatapb.Keyspace{},
  4373  		},
  4374  		{
  4375  			Name:     "ks2",
  4376  			Keyspace: &topodatapb.Keyspace{},
  4377  		},
  4378  		{
  4379  			Name:     "ks3",
  4380  			Keyspace: &topodatapb.Keyspace{},
  4381  		},
  4382  	}
  4383  	for _, ks := range expected {
  4384  		testutil.AddKeyspace(ctx, t, ts, ks)
  4385  	}
  4386  
  4387  	resp, err = vtctld.GetKeyspaces(ctx, &vtctldatapb.GetKeyspacesRequest{})
  4388  	assert.NoError(t, err)
  4389  	utils.MustMatch(t, expected, resp.Keyspaces)
  4390  
  4391  	topofactory.SetError(errors.New("error from toposerver"))
  4392  
  4393  	_, err = vtctld.GetKeyspaces(ctx, &vtctldatapb.GetKeyspacesRequest{})
  4394  	assert.Error(t, err)
  4395  }
  4396  
  4397  func TestGetPermissions(t *testing.T) {
  4398  	t.Parallel()
  4399  
  4400  	ctx := context.Background()
  4401  	var testGetPermissionsReply = &tabletmanagerdatapb.Permissions{
  4402  		UserPermissions: []*tabletmanagerdatapb.UserPermission{
  4403  			{
  4404  				Host: "host1",
  4405  				User: "user1",
  4406  				Privileges: map[string]string{
  4407  					"create": "yes",
  4408  					"delete": "no",
  4409  				},
  4410  			},
  4411  		},
  4412  		DbPermissions: []*tabletmanagerdatapb.DbPermission{
  4413  			{
  4414  				Host: "host2",
  4415  				Db:   "db1",
  4416  				User: "user2",
  4417  				Privileges: map[string]string{
  4418  					"create": "no",
  4419  					"delete": "yes",
  4420  				},
  4421  			},
  4422  		},
  4423  	}
  4424  	tests := []struct {
  4425  		name      string
  4426  		tablets   []*topodatapb.Tablet
  4427  		tmc       testutil.TabletManagerClient
  4428  		req       *vtctldatapb.GetPermissionsRequest
  4429  		shouldErr bool
  4430  	}{
  4431  		{
  4432  			name: "ok",
  4433  			tablets: []*topodatapb.Tablet{
  4434  				{
  4435  					Alias: &topodatapb.TabletAlias{
  4436  						Cell: "zone1",
  4437  						Uid:  100,
  4438  					},
  4439  				},
  4440  			},
  4441  			tmc: testutil.TabletManagerClient{
  4442  				GetPermissionsResults: map[string]struct {
  4443  					Permissions *tabletmanagerdatapb.Permissions
  4444  					Error       error
  4445  				}{
  4446  					"zone1-0000000100": {
  4447  						Permissions: testGetPermissionsReply,
  4448  						Error:       nil,
  4449  					},
  4450  				},
  4451  			},
  4452  			req: &vtctldatapb.GetPermissionsRequest{
  4453  				TabletAlias: &topodatapb.TabletAlias{
  4454  					Cell: "zone1",
  4455  					Uid:  100,
  4456  				},
  4457  			},
  4458  		},
  4459  		{
  4460  			name: "no tablet",
  4461  			tablets: []*topodatapb.Tablet{
  4462  				{
  4463  					Alias: &topodatapb.TabletAlias{
  4464  						Cell: "zone1",
  4465  						Uid:  404,
  4466  					},
  4467  				},
  4468  			},
  4469  			tmc: testutil.TabletManagerClient{
  4470  				GetPermissionsResults: map[string]struct {
  4471  					Permissions *tabletmanagerdatapb.Permissions
  4472  					Error       error
  4473  				}{
  4474  					"zone1-0000000100": {
  4475  						Permissions: testGetPermissionsReply,
  4476  						Error:       nil,
  4477  					},
  4478  				},
  4479  			},
  4480  			req: &vtctldatapb.GetPermissionsRequest{
  4481  				TabletAlias: &topodatapb.TabletAlias{
  4482  					Cell: "zone1",
  4483  					Uid:  100,
  4484  				},
  4485  			},
  4486  			shouldErr: true,
  4487  		},
  4488  		{
  4489  			name: "tmc call failed",
  4490  			tablets: []*topodatapb.Tablet{
  4491  				{
  4492  					Alias: &topodatapb.TabletAlias{
  4493  						Cell: "zone1",
  4494  						Uid:  100,
  4495  					},
  4496  				},
  4497  			},
  4498  			tmc: testutil.TabletManagerClient{
  4499  				GetPermissionsResults: map[string]struct {
  4500  					Permissions *tabletmanagerdatapb.Permissions
  4501  					Error       error
  4502  				}{
  4503  					"zone1-0000000100": {
  4504  						Permissions: testGetPermissionsReply,
  4505  						Error:       assert.AnError,
  4506  					},
  4507  				},
  4508  			},
  4509  			req: &vtctldatapb.GetPermissionsRequest{
  4510  				TabletAlias: &topodatapb.TabletAlias{
  4511  					Cell: "zone1",
  4512  					Uid:  100,
  4513  				},
  4514  			},
  4515  			shouldErr: true,
  4516  		},
  4517  	}
  4518  
  4519  	for _, tt := range tests {
  4520  		tt := tt
  4521  		t.Run(tt.name, func(t *testing.T) {
  4522  			t.Parallel()
  4523  
  4524  			ts := memorytopo.NewServer("zone1")
  4525  			testutil.AddTablets(ctx, t, ts, nil, tt.tablets...)
  4526  
  4527  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, &tt.tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  4528  				return NewVtctldServer(ts)
  4529  			})
  4530  			resp, err := vtctld.GetPermissions(ctx, tt.req)
  4531  			if tt.shouldErr {
  4532  				assert.Error(t, err)
  4533  				return
  4534  			}
  4535  			// we should expect same user and DB permissions as assigned
  4536  			assert.Equal(t, resp.Permissions.DbPermissions[0].Host, "host2")
  4537  			assert.Equal(t, resp.Permissions.UserPermissions[0].Host, "host1")
  4538  
  4539  			require.NoError(t, err)
  4540  		})
  4541  	}
  4542  }
  4543  
  4544  func TestGetRoutingRules(t *testing.T) {
  4545  	t.Parallel()
  4546  
  4547  	ctx := context.Background()
  4548  	tests := []struct {
  4549  		name      string
  4550  		topoDown  bool
  4551  		rrIn      *vschemapb.RoutingRules
  4552  		expected  *vschemapb.RoutingRules
  4553  		shouldErr bool
  4554  	}{
  4555  		{
  4556  			name: "success",
  4557  			rrIn: &vschemapb.RoutingRules{
  4558  				Rules: []*vschemapb.RoutingRule{
  4559  					{
  4560  						FromTable: "t1",
  4561  						ToTables:  []string{"t2", "t3"},
  4562  					},
  4563  				},
  4564  			},
  4565  			expected: &vschemapb.RoutingRules{
  4566  				Rules: []*vschemapb.RoutingRule{
  4567  					{
  4568  						FromTable: "t1",
  4569  						ToTables:  []string{"t2", "t3"},
  4570  					},
  4571  				},
  4572  			},
  4573  		},
  4574  		{
  4575  			name:     "empty routing rules",
  4576  			rrIn:     nil,
  4577  			expected: &vschemapb.RoutingRules{},
  4578  		},
  4579  		{
  4580  			name:      "topo error",
  4581  			topoDown:  true,
  4582  			shouldErr: true,
  4583  		},
  4584  	}
  4585  
  4586  	for _, tt := range tests {
  4587  		tt := tt
  4588  		t.Run(tt.name, func(t *testing.T) {
  4589  			t.Parallel()
  4590  
  4591  			ts, factory := memorytopo.NewServerAndFactory()
  4592  			if tt.rrIn != nil {
  4593  				err := ts.SaveRoutingRules(ctx, tt.rrIn)
  4594  				require.NoError(t, err, "could not save routing rules: %+v", tt.rrIn)
  4595  			}
  4596  
  4597  			if tt.topoDown {
  4598  				factory.SetError(errors.New("topo down for testing"))
  4599  			}
  4600  
  4601  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  4602  				return NewVtctldServer(ts)
  4603  			})
  4604  			resp, err := vtctld.GetRoutingRules(ctx, &vtctldatapb.GetRoutingRulesRequest{})
  4605  			if tt.shouldErr {
  4606  				assert.Error(t, err)
  4607  				return
  4608  			}
  4609  
  4610  			require.NoError(t, err)
  4611  			utils.MustMatch(t, resp.RoutingRules, tt.expected)
  4612  		})
  4613  	}
  4614  }
  4615  
  4616  func TestGetSchema(t *testing.T) {
  4617  	ctx := context.Background()
  4618  	ts := memorytopo.NewServer("zone1")
  4619  	tmc := testutil.TabletManagerClient{
  4620  		GetSchemaResults: map[string]struct {
  4621  			Schema *tabletmanagerdatapb.SchemaDefinition
  4622  			Error  error
  4623  		}{},
  4624  	}
  4625  	vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, &tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  4626  		return NewVtctldServer(ts)
  4627  	})
  4628  
  4629  	validAlias := &topodatapb.TabletAlias{
  4630  		Cell: "zone1",
  4631  		Uid:  100,
  4632  	}
  4633  	testutil.AddTablet(ctx, t, ts, &topodatapb.Tablet{
  4634  		Alias: validAlias,
  4635  	}, nil)
  4636  	otherAlias := &topodatapb.TabletAlias{
  4637  		Cell: "zone1",
  4638  		Uid:  101,
  4639  	}
  4640  	testutil.AddTablet(ctx, t, ts, &topodatapb.Tablet{
  4641  		Alias: otherAlias,
  4642  	}, nil)
  4643  
  4644  	// we need to run this on each test case or they will pollute each other
  4645  	setupSchema := func() {
  4646  		tmc.GetSchemaResults[topoproto.TabletAliasString(validAlias)] = struct {
  4647  			Schema *tabletmanagerdatapb.SchemaDefinition
  4648  			Error  error
  4649  		}{
  4650  			Schema: &tabletmanagerdatapb.SchemaDefinition{
  4651  				DatabaseSchema: "CREATE DATABASE vt_testkeyspace",
  4652  				TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  4653  					{
  4654  						Name: "t1",
  4655  						Schema: `CREATE TABLE t1 (
  4656  	id int(11) not null,
  4657  	PRIMARY KEY (id)
  4658  );`,
  4659  						Type:       "BASE",
  4660  						Columns:    []string{"id"},
  4661  						DataLength: 100,
  4662  						RowCount:   50,
  4663  						Fields: []*querypb.Field{
  4664  							{
  4665  								Name: "id",
  4666  								Type: querypb.Type_INT32,
  4667  							},
  4668  						},
  4669  					},
  4670  				},
  4671  			},
  4672  			Error: nil,
  4673  		}
  4674  	}
  4675  
  4676  	tests := []*struct {
  4677  		name      string
  4678  		req       *vtctldatapb.GetSchemaRequest
  4679  		expected  *vtctldatapb.GetSchemaResponse
  4680  		shouldErr bool
  4681  	}{
  4682  		{
  4683  			name: "normal path",
  4684  			req: &vtctldatapb.GetSchemaRequest{
  4685  				TabletAlias: validAlias,
  4686  			},
  4687  			expected: &vtctldatapb.GetSchemaResponse{
  4688  				Schema: &tabletmanagerdatapb.SchemaDefinition{
  4689  					DatabaseSchema: "CREATE DATABASE vt_testkeyspace",
  4690  					TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  4691  						{
  4692  							Name: "t1",
  4693  							Schema: `CREATE TABLE t1 (
  4694  	id int(11) not null,
  4695  	PRIMARY KEY (id)
  4696  );`,
  4697  							Type:       "BASE",
  4698  							Columns:    []string{"id"},
  4699  							DataLength: 100,
  4700  							RowCount:   50,
  4701  							Fields: []*querypb.Field{
  4702  								{
  4703  									Name: "id",
  4704  									Type: querypb.Type_INT32,
  4705  								},
  4706  							},
  4707  						},
  4708  					},
  4709  				},
  4710  			},
  4711  			shouldErr: false,
  4712  		},
  4713  		{
  4714  			name: "table names only",
  4715  			req: &vtctldatapb.GetSchemaRequest{
  4716  				TabletAlias:    validAlias,
  4717  				TableNamesOnly: true,
  4718  			},
  4719  			expected: &vtctldatapb.GetSchemaResponse{
  4720  				Schema: &tabletmanagerdatapb.SchemaDefinition{
  4721  					DatabaseSchema: "CREATE DATABASE vt_testkeyspace",
  4722  					TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  4723  						{
  4724  							Name: "t1",
  4725  						},
  4726  					},
  4727  				},
  4728  			},
  4729  			shouldErr: false,
  4730  		},
  4731  		{
  4732  			name: "table sizes only",
  4733  			req: &vtctldatapb.GetSchemaRequest{
  4734  				TabletAlias:    validAlias,
  4735  				TableSizesOnly: true,
  4736  			},
  4737  			expected: &vtctldatapb.GetSchemaResponse{
  4738  				Schema: &tabletmanagerdatapb.SchemaDefinition{
  4739  					DatabaseSchema: "CREATE DATABASE vt_testkeyspace",
  4740  					TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  4741  						{
  4742  							Name:       "t1",
  4743  							Type:       "BASE",
  4744  							DataLength: 100,
  4745  							RowCount:   50,
  4746  						},
  4747  					},
  4748  				},
  4749  			},
  4750  			shouldErr: false,
  4751  		},
  4752  		{
  4753  			name: "table names take precedence over table sizes",
  4754  			req: &vtctldatapb.GetSchemaRequest{
  4755  				TabletAlias:    validAlias,
  4756  				TableNamesOnly: true,
  4757  				TableSizesOnly: true,
  4758  			},
  4759  			expected: &vtctldatapb.GetSchemaResponse{
  4760  				Schema: &tabletmanagerdatapb.SchemaDefinition{
  4761  					DatabaseSchema: "CREATE DATABASE vt_testkeyspace",
  4762  					TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
  4763  						{
  4764  							Name: "t1",
  4765  						},
  4766  					},
  4767  				},
  4768  			},
  4769  			shouldErr: false,
  4770  		},
  4771  		// error cases
  4772  		{
  4773  			name: "no tablet",
  4774  			req: &vtctldatapb.GetSchemaRequest{
  4775  				TabletAlias: &topodatapb.TabletAlias{
  4776  					Cell: "notfound",
  4777  					Uid:  100,
  4778  				},
  4779  			},
  4780  			expected:  nil,
  4781  			shouldErr: true,
  4782  		},
  4783  		{
  4784  			name: "no schema",
  4785  			req: &vtctldatapb.GetSchemaRequest{
  4786  				TabletAlias: otherAlias,
  4787  			},
  4788  			expected:  nil,
  4789  			shouldErr: true,
  4790  		},
  4791  	}
  4792  
  4793  	for _, tt := range tests {
  4794  		t.Run(tt.name, func(t *testing.T) {
  4795  			setupSchema()
  4796  
  4797  			resp, err := vtctld.GetSchema(ctx, tt.req)
  4798  			if tt.shouldErr {
  4799  				assert.Error(t, err)
  4800  				return
  4801  			}
  4802  
  4803  			assert.NoError(t, err)
  4804  			utils.MustMatch(t, tt.expected, resp)
  4805  		})
  4806  	}
  4807  }
  4808  
  4809  func TestGetShard(t *testing.T) {
  4810  	t.Parallel()
  4811  
  4812  	tests := []struct {
  4813  		name      string
  4814  		topo      []*vtctldatapb.Shard
  4815  		topoError error
  4816  		req       *vtctldatapb.GetShardRequest
  4817  		expected  *vtctldatapb.GetShardResponse
  4818  		shouldErr bool
  4819  	}{
  4820  		{
  4821  			name: "success",
  4822  			topo: []*vtctldatapb.Shard{
  4823  				{
  4824  					Keyspace: "testkeyspace",
  4825  					Name:     "-",
  4826  				},
  4827  			},
  4828  			topoError: nil,
  4829  			req: &vtctldatapb.GetShardRequest{
  4830  				Keyspace:  "testkeyspace",
  4831  				ShardName: "-",
  4832  			},
  4833  			expected: &vtctldatapb.GetShardResponse{
  4834  				Shard: &vtctldatapb.Shard{
  4835  					Keyspace: "testkeyspace",
  4836  					Name:     "-",
  4837  					Shard: &topodatapb.Shard{
  4838  						KeyRange:         &topodatapb.KeyRange{},
  4839  						IsPrimaryServing: true,
  4840  					},
  4841  				},
  4842  			},
  4843  			shouldErr: false,
  4844  		},
  4845  		{
  4846  			name:      "shard not found",
  4847  			topo:      nil,
  4848  			topoError: nil,
  4849  			req: &vtctldatapb.GetShardRequest{
  4850  				Keyspace:  "testkeyspace",
  4851  				ShardName: "-",
  4852  			},
  4853  			shouldErr: true,
  4854  		},
  4855  		{
  4856  			name: "unavailable topo server",
  4857  			topo: []*vtctldatapb.Shard{
  4858  				{
  4859  					Keyspace: "testkeyspace",
  4860  					Name:     "-",
  4861  				},
  4862  			},
  4863  			topoError: assert.AnError,
  4864  			req:       &vtctldatapb.GetShardRequest{},
  4865  			shouldErr: true,
  4866  		},
  4867  	}
  4868  
  4869  	for _, tt := range tests {
  4870  		tt := tt
  4871  
  4872  		t.Run(tt.name, func(t *testing.T) {
  4873  			t.Parallel()
  4874  
  4875  			cells := []string{"zone1", "zone2", "zone3"}
  4876  
  4877  			ctx := context.Background()
  4878  			ts, topofactory := memorytopo.NewServerAndFactory(cells...)
  4879  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  4880  				return NewVtctldServer(ts)
  4881  			})
  4882  
  4883  			testutil.AddShards(ctx, t, ts, tt.topo...)
  4884  
  4885  			if tt.topoError != nil {
  4886  				topofactory.SetError(tt.topoError)
  4887  			}
  4888  
  4889  			resp, err := vtctld.GetShard(ctx, tt.req)
  4890  			if tt.shouldErr {
  4891  				assert.Error(t, err)
  4892  				return
  4893  			}
  4894  
  4895  			utils.MustMatch(t, tt.expected, resp)
  4896  		})
  4897  	}
  4898  }
  4899  
  4900  func TestGetSrvKeyspaceNames(t *testing.T) {
  4901  	t.Parallel()
  4902  
  4903  	ctx := context.Background()
  4904  	tests := []struct {
  4905  		name               string
  4906  		srvKeyspacesByCell map[string]map[string]*topodatapb.SrvKeyspace
  4907  		topoError          error
  4908  		req                *vtctldatapb.GetSrvKeyspaceNamesRequest
  4909  		expected           *vtctldatapb.GetSrvKeyspaceNamesResponse
  4910  		shouldErr          bool
  4911  	}{
  4912  		{
  4913  			name: "success",
  4914  			srvKeyspacesByCell: map[string]map[string]*topodatapb.SrvKeyspace{
  4915  				"zone1": {
  4916  					"ks1": {},
  4917  					"ks2": {},
  4918  				},
  4919  				"zone2": {
  4920  					"ks1": {},
  4921  				},
  4922  			},
  4923  			req: &vtctldatapb.GetSrvKeyspaceNamesRequest{},
  4924  			expected: &vtctldatapb.GetSrvKeyspaceNamesResponse{
  4925  				Names: map[string]*vtctldatapb.GetSrvKeyspaceNamesResponse_NameList{
  4926  					"zone1": {
  4927  						Names: []string{"ks1", "ks2"},
  4928  					},
  4929  					"zone2": {
  4930  						Names: []string{"ks1"},
  4931  					},
  4932  				},
  4933  			},
  4934  		},
  4935  		{
  4936  			name: "cell filtering",
  4937  			srvKeyspacesByCell: map[string]map[string]*topodatapb.SrvKeyspace{
  4938  				"zone1": {
  4939  					"ks1": {},
  4940  					"ks2": {},
  4941  				},
  4942  				"zone2": {
  4943  					"ks1": {},
  4944  				},
  4945  			},
  4946  			req: &vtctldatapb.GetSrvKeyspaceNamesRequest{
  4947  				Cells: []string{"zone2"},
  4948  			},
  4949  			expected: &vtctldatapb.GetSrvKeyspaceNamesResponse{
  4950  				Names: map[string]*vtctldatapb.GetSrvKeyspaceNamesResponse_NameList{
  4951  					"zone2": {
  4952  						Names: []string{"ks1"},
  4953  					},
  4954  				},
  4955  			},
  4956  		},
  4957  		{
  4958  			name: "all cells topo down",
  4959  			srvKeyspacesByCell: map[string]map[string]*topodatapb.SrvKeyspace{
  4960  				"zone1": {
  4961  					"ks1": {},
  4962  					"ks2": {},
  4963  				},
  4964  				"zone2": {
  4965  					"ks1": {},
  4966  				},
  4967  			},
  4968  			req:       &vtctldatapb.GetSrvKeyspaceNamesRequest{},
  4969  			topoError: errors.New("topo down for testing"),
  4970  			shouldErr: true,
  4971  		},
  4972  		{
  4973  			name: "cell filtering topo down",
  4974  			srvKeyspacesByCell: map[string]map[string]*topodatapb.SrvKeyspace{
  4975  				"zone1": {
  4976  					"ks1": {},
  4977  					"ks2": {},
  4978  				},
  4979  				"zone2": {
  4980  					"ks1": {},
  4981  				},
  4982  			},
  4983  			req: &vtctldatapb.GetSrvKeyspaceNamesRequest{
  4984  				Cells: []string{"zone2"},
  4985  			},
  4986  			topoError: errors.New("topo down for testing"),
  4987  			shouldErr: true,
  4988  		},
  4989  	}
  4990  
  4991  	for _, tt := range tests {
  4992  		tt := tt
  4993  
  4994  		t.Run(tt.name, func(t *testing.T) {
  4995  			t.Parallel()
  4996  
  4997  			cells := make([]string, 0, len(tt.srvKeyspacesByCell))
  4998  			for cell := range tt.srvKeyspacesByCell {
  4999  				cells = append(cells, cell)
  5000  			}
  5001  
  5002  			ts, factory := memorytopo.NewServerAndFactory(cells...)
  5003  
  5004  			for cell, srvKeyspaces := range tt.srvKeyspacesByCell {
  5005  				for ks, srvks := range srvKeyspaces {
  5006  					err := ts.UpdateSrvKeyspace(ctx, cell, ks, srvks)
  5007  					require.NoError(t, err, "UpdateSrvKeyspace(%s, %s, %+v) failed", cell, ks, srvks)
  5008  				}
  5009  			}
  5010  
  5011  			if tt.topoError != nil {
  5012  				factory.SetError(tt.topoError)
  5013  			}
  5014  
  5015  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  5016  				return NewVtctldServer(ts)
  5017  			})
  5018  			resp, err := vtctld.GetSrvKeyspaceNames(ctx, tt.req)
  5019  			if tt.shouldErr {
  5020  				assert.Error(t, err)
  5021  				return
  5022  			}
  5023  
  5024  			require.NoError(t, err)
  5025  			for _, names := range resp.Names {
  5026  				sort.Strings(names.Names)
  5027  			}
  5028  			for _, names := range tt.expected.Names {
  5029  				sort.Strings(names.Names)
  5030  			}
  5031  			utils.MustMatch(t, tt.expected, resp)
  5032  		})
  5033  	}
  5034  }
  5035  
  5036  func TestGetSrvKeyspaces(t *testing.T) {
  5037  	t.Parallel()
  5038  
  5039  	tests := []struct {
  5040  		name         string
  5041  		cells        []string
  5042  		srvKeyspaces []*testutil.SrvKeyspace
  5043  		topoErr      error
  5044  		req          *vtctldatapb.GetSrvKeyspacesRequest
  5045  		expected     *vtctldatapb.GetSrvKeyspacesResponse
  5046  		shouldErr    bool
  5047  	}{
  5048  		{
  5049  			name:  "success",
  5050  			cells: []string{"zone1", "zone2"},
  5051  			srvKeyspaces: []*testutil.SrvKeyspace{
  5052  				{
  5053  					Cell:        "zone1",
  5054  					Keyspace:    "testkeyspace",
  5055  					SrvKeyspace: &topodatapb.SrvKeyspace{},
  5056  				},
  5057  				{
  5058  					Cell:        "zone2",
  5059  					Keyspace:    "testkeyspace",
  5060  					SrvKeyspace: &topodatapb.SrvKeyspace{},
  5061  				},
  5062  			},
  5063  			req: &vtctldatapb.GetSrvKeyspacesRequest{
  5064  				Keyspace: "testkeyspace",
  5065  			},
  5066  			expected: &vtctldatapb.GetSrvKeyspacesResponse{
  5067  				SrvKeyspaces: map[string]*topodatapb.SrvKeyspace{
  5068  					"zone1": {},
  5069  					"zone2": {},
  5070  				},
  5071  			},
  5072  			shouldErr: false,
  5073  		},
  5074  		{
  5075  			name:  "filtering by cell",
  5076  			cells: []string{"zone1", "zone2"},
  5077  			srvKeyspaces: []*testutil.SrvKeyspace{
  5078  				{
  5079  					Cell:        "zone1",
  5080  					Keyspace:    "testkeyspace",
  5081  					SrvKeyspace: &topodatapb.SrvKeyspace{},
  5082  				},
  5083  				{
  5084  					Cell:        "zone2",
  5085  					Keyspace:    "testkeyspace",
  5086  					SrvKeyspace: &topodatapb.SrvKeyspace{},
  5087  				},
  5088  			},
  5089  			req: &vtctldatapb.GetSrvKeyspacesRequest{
  5090  				Keyspace: "testkeyspace",
  5091  				Cells:    []string{"zone1"},
  5092  			},
  5093  			expected: &vtctldatapb.GetSrvKeyspacesResponse{
  5094  				SrvKeyspaces: map[string]*topodatapb.SrvKeyspace{
  5095  					"zone1": {},
  5096  				},
  5097  			},
  5098  			shouldErr: false,
  5099  		},
  5100  		{
  5101  			name:  "no srvkeyspace for single cell",
  5102  			cells: []string{"zone1", "zone2"},
  5103  			srvKeyspaces: []*testutil.SrvKeyspace{
  5104  				{
  5105  					Cell:        "zone1",
  5106  					Keyspace:    "testkeyspace",
  5107  					SrvKeyspace: &topodatapb.SrvKeyspace{},
  5108  				},
  5109  			},
  5110  			req: &vtctldatapb.GetSrvKeyspacesRequest{
  5111  				Keyspace: "testkeyspace",
  5112  			},
  5113  			expected: &vtctldatapb.GetSrvKeyspacesResponse{
  5114  				SrvKeyspaces: map[string]*topodatapb.SrvKeyspace{
  5115  					"zone1": {},
  5116  					"zone2": nil,
  5117  				},
  5118  			},
  5119  			shouldErr: false,
  5120  		},
  5121  		{
  5122  			name:  "error getting cell names",
  5123  			cells: []string{"zone1"},
  5124  			srvKeyspaces: []*testutil.SrvKeyspace{
  5125  				{
  5126  					Cell:        "zone1",
  5127  					Keyspace:    "testkeyspace",
  5128  					SrvKeyspace: &topodatapb.SrvKeyspace{},
  5129  				},
  5130  			},
  5131  			topoErr: assert.AnError,
  5132  			req: &vtctldatapb.GetSrvKeyspacesRequest{
  5133  				Keyspace: "testkeyspace",
  5134  			},
  5135  			shouldErr: true,
  5136  		},
  5137  		{
  5138  			name:  "error getting srvkeyspace",
  5139  			cells: []string{"zone1"},
  5140  			srvKeyspaces: []*testutil.SrvKeyspace{
  5141  				{
  5142  					Cell:        "zone1",
  5143  					Keyspace:    "testkeyspace",
  5144  					SrvKeyspace: &topodatapb.SrvKeyspace{},
  5145  				},
  5146  			},
  5147  			topoErr: assert.AnError,
  5148  			req: &vtctldatapb.GetSrvKeyspacesRequest{
  5149  				Keyspace: "testkeyspace",
  5150  				Cells:    []string{"zone1"},
  5151  			},
  5152  			shouldErr: true,
  5153  		},
  5154  	}
  5155  
  5156  	ctx := context.Background()
  5157  
  5158  	for _, tt := range tests {
  5159  		tt := tt
  5160  
  5161  		t.Run(tt.name, func(t *testing.T) {
  5162  			t.Parallel()
  5163  
  5164  			if tt.req == nil {
  5165  				t.SkipNow()
  5166  			}
  5167  
  5168  			ts, topofactory := memorytopo.NewServerAndFactory(tt.cells...)
  5169  
  5170  			testutil.AddSrvKeyspaces(t, ts, tt.srvKeyspaces...)
  5171  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  5172  				return NewVtctldServer(ts)
  5173  			})
  5174  
  5175  			if tt.topoErr != nil {
  5176  				topofactory.SetError(tt.topoErr)
  5177  			}
  5178  
  5179  			resp, err := vtctld.GetSrvKeyspaces(ctx, tt.req)
  5180  			if tt.shouldErr {
  5181  				assert.Error(t, err)
  5182  
  5183  				return
  5184  			}
  5185  
  5186  			assert.NoError(t, err)
  5187  			utils.MustMatch(t, tt.expected, resp)
  5188  		})
  5189  	}
  5190  }
  5191  
  5192  func TestGetSrvVSchema(t *testing.T) {
  5193  	t.Parallel()
  5194  
  5195  	ctx := context.Background()
  5196  	ts, topofactory := memorytopo.NewServerAndFactory("zone1", "zone2")
  5197  	vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  5198  		return NewVtctldServer(ts)
  5199  	})
  5200  
  5201  	zone1SrvVSchema := &vschemapb.SrvVSchema{
  5202  		Keyspaces: map[string]*vschemapb.Keyspace{
  5203  			"testkeyspace": {
  5204  				Sharded:                true,
  5205  				RequireExplicitRouting: false,
  5206  			},
  5207  		},
  5208  		RoutingRules: &vschemapb.RoutingRules{
  5209  			Rules: []*vschemapb.RoutingRule{},
  5210  		},
  5211  	}
  5212  	zone2SrvVSchema := &vschemapb.SrvVSchema{
  5213  		Keyspaces: map[string]*vschemapb.Keyspace{
  5214  			"testkeyspace": {
  5215  				Sharded:                true,
  5216  				RequireExplicitRouting: false,
  5217  			},
  5218  			"unsharded": {
  5219  				Sharded:                false,
  5220  				RequireExplicitRouting: false,
  5221  			},
  5222  		},
  5223  		RoutingRules: &vschemapb.RoutingRules{
  5224  			Rules: []*vschemapb.RoutingRule{},
  5225  		},
  5226  	}
  5227  
  5228  	err := ts.UpdateSrvVSchema(ctx, "zone1", zone1SrvVSchema)
  5229  	require.NoError(t, err, "cannot add zone1 srv vschema")
  5230  	err = ts.UpdateSrvVSchema(ctx, "zone2", zone2SrvVSchema)
  5231  	require.NoError(t, err, "cannot add zone2 srv vschema")
  5232  
  5233  	expected := &vschemapb.SrvVSchema{ // have to copy our structs because of proto marshal artifacts
  5234  		Keyspaces: map[string]*vschemapb.Keyspace{
  5235  			"testkeyspace": {
  5236  				Sharded:                true,
  5237  				RequireExplicitRouting: false,
  5238  			},
  5239  		},
  5240  		RoutingRules: &vschemapb.RoutingRules{
  5241  			Rules: []*vschemapb.RoutingRule{},
  5242  		},
  5243  	}
  5244  	resp, err := vtctld.GetSrvVSchema(ctx, &vtctldatapb.GetSrvVSchemaRequest{Cell: "zone1"})
  5245  	assert.NoError(t, err)
  5246  	utils.MustMatch(t, expected.Keyspaces, resp.SrvVSchema.Keyspaces, "GetSrvVSchema(zone1) mismatch")
  5247  	assert.ElementsMatch(t, expected.RoutingRules.Rules, resp.SrvVSchema.RoutingRules.Rules, "GetSrvVSchema(zone1) rules mismatch")
  5248  
  5249  	expected = &vschemapb.SrvVSchema{ // have to copy our structs because of proto marshal artifacts
  5250  		Keyspaces: map[string]*vschemapb.Keyspace{
  5251  			"testkeyspace": {
  5252  				Sharded:                true,
  5253  				RequireExplicitRouting: false,
  5254  			},
  5255  			"unsharded": {
  5256  				Sharded:                false,
  5257  				RequireExplicitRouting: false,
  5258  			},
  5259  		},
  5260  		RoutingRules: &vschemapb.RoutingRules{
  5261  			Rules: []*vschemapb.RoutingRule{},
  5262  		},
  5263  	}
  5264  	resp, err = vtctld.GetSrvVSchema(ctx, &vtctldatapb.GetSrvVSchemaRequest{Cell: "zone2"})
  5265  	assert.NoError(t, err)
  5266  	utils.MustMatch(t, expected.Keyspaces, resp.SrvVSchema.Keyspaces, "GetSrvVSchema(zone2) mismatch")
  5267  	assert.ElementsMatch(t, expected.RoutingRules.Rules, resp.SrvVSchema.RoutingRules.Rules, "GetSrvVSchema(zone2) rules mismatch")
  5268  
  5269  	resp, err = vtctld.GetSrvVSchema(ctx, &vtctldatapb.GetSrvVSchemaRequest{Cell: "dne"})
  5270  	assert.Error(t, err, "GetSrvVSchema(dne)")
  5271  	assert.Nil(t, resp, "GetSrvVSchema(dne)")
  5272  
  5273  	topofactory.SetError(assert.AnError)
  5274  	_, err = vtctld.GetSrvVSchema(ctx, &vtctldatapb.GetSrvVSchemaRequest{Cell: "zone1"})
  5275  	assert.Error(t, err)
  5276  }
  5277  
  5278  func TestGetSrvVSchemas(t *testing.T) {
  5279  	t.Parallel()
  5280  
  5281  	tests := []struct {
  5282  		name      string
  5283  		req       *vtctldatapb.GetSrvVSchemasRequest
  5284  		expected  *vtctldatapb.GetSrvVSchemasResponse
  5285  		topoErr   error
  5286  		shouldErr bool
  5287  	}{
  5288  		{
  5289  			name: "success",
  5290  			req:  &vtctldatapb.GetSrvVSchemasRequest{},
  5291  			expected: &vtctldatapb.GetSrvVSchemasResponse{
  5292  				SrvVSchemas: map[string]*vschemapb.SrvVSchema{
  5293  					"zone1": {
  5294  						Keyspaces: map[string]*vschemapb.Keyspace{
  5295  							"testkeyspace": {
  5296  								Sharded:                true,
  5297  								RequireExplicitRouting: false,
  5298  							},
  5299  						},
  5300  						RoutingRules: &vschemapb.RoutingRules{
  5301  							Rules: []*vschemapb.RoutingRule{},
  5302  						},
  5303  					},
  5304  					"zone2": {
  5305  						Keyspaces: map[string]*vschemapb.Keyspace{
  5306  							"testkeyspace": {
  5307  								Sharded:                true,
  5308  								RequireExplicitRouting: false,
  5309  							},
  5310  							"unsharded": {
  5311  								Sharded:                false,
  5312  								RequireExplicitRouting: false,
  5313  							},
  5314  						},
  5315  						RoutingRules: &vschemapb.RoutingRules{
  5316  							Rules: []*vschemapb.RoutingRule{},
  5317  						},
  5318  					},
  5319  					"zone3": {},
  5320  				},
  5321  			},
  5322  		},
  5323  		{
  5324  			name: "filtering by cell",
  5325  			req: &vtctldatapb.GetSrvVSchemasRequest{
  5326  				Cells: []string{"zone2"},
  5327  			},
  5328  			expected: &vtctldatapb.GetSrvVSchemasResponse{
  5329  				SrvVSchemas: map[string]*vschemapb.SrvVSchema{
  5330  					"zone2": {
  5331  						Keyspaces: map[string]*vschemapb.Keyspace{
  5332  							"testkeyspace": {
  5333  								Sharded:                true,
  5334  								RequireExplicitRouting: false,
  5335  							},
  5336  							"unsharded": {
  5337  								Sharded:                false,
  5338  								RequireExplicitRouting: false,
  5339  							},
  5340  						},
  5341  						RoutingRules: &vschemapb.RoutingRules{
  5342  							Rules: []*vschemapb.RoutingRule{},
  5343  						},
  5344  					},
  5345  				},
  5346  			},
  5347  		},
  5348  		{
  5349  			name: "no SrvVSchema for single cell",
  5350  			req: &vtctldatapb.GetSrvVSchemasRequest{
  5351  				Cells: []string{"zone3"},
  5352  			},
  5353  			expected: &vtctldatapb.GetSrvVSchemasResponse{
  5354  				SrvVSchemas: map[string]*vschemapb.SrvVSchema{
  5355  					"zone3": {},
  5356  				},
  5357  			},
  5358  		},
  5359  		{
  5360  			name: "topology error",
  5361  			req: &vtctldatapb.GetSrvVSchemasRequest{
  5362  				Cells: []string{"zone2"},
  5363  			},
  5364  			topoErr:   assert.AnError,
  5365  			shouldErr: true,
  5366  		},
  5367  		{
  5368  			name: "cell doesn't exist",
  5369  			req: &vtctldatapb.GetSrvVSchemasRequest{
  5370  				Cells: []string{"doesnt-exist"},
  5371  			},
  5372  			expected: &vtctldatapb.GetSrvVSchemasResponse{
  5373  				SrvVSchemas: map[string]*vschemapb.SrvVSchema{},
  5374  			},
  5375  		},
  5376  		{
  5377  			name: "one of many cells doesn't exist",
  5378  			req: &vtctldatapb.GetSrvVSchemasRequest{
  5379  				Cells: []string{"zone1", "doesnt-exist"},
  5380  			},
  5381  			expected: &vtctldatapb.GetSrvVSchemasResponse{
  5382  				SrvVSchemas: map[string]*vschemapb.SrvVSchema{
  5383  					"zone1": {
  5384  						Keyspaces: map[string]*vschemapb.Keyspace{
  5385  							"testkeyspace": {
  5386  								Sharded:                true,
  5387  								RequireExplicitRouting: false,
  5388  							},
  5389  						},
  5390  						RoutingRules: &vschemapb.RoutingRules{
  5391  							Rules: []*vschemapb.RoutingRule{},
  5392  						},
  5393  					},
  5394  				},
  5395  			},
  5396  		},
  5397  	}
  5398  
  5399  	for _, tt := range tests {
  5400  		tt := tt
  5401  
  5402  		t.Run(tt.name, func(t *testing.T) {
  5403  			t.Parallel()
  5404  
  5405  			ctx := context.Background()
  5406  			ts, topofactory := memorytopo.NewServerAndFactory("zone1", "zone2", "zone3")
  5407  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  5408  				return NewVtctldServer(ts)
  5409  			})
  5410  
  5411  			zone1SrvVSchema := &vschemapb.SrvVSchema{
  5412  				Keyspaces: map[string]*vschemapb.Keyspace{
  5413  					"testkeyspace": {
  5414  						Sharded:                true,
  5415  						RequireExplicitRouting: false,
  5416  					},
  5417  				},
  5418  				RoutingRules: &vschemapb.RoutingRules{
  5419  					Rules: []*vschemapb.RoutingRule{},
  5420  				},
  5421  			}
  5422  
  5423  			zone2SrvVSchema := &vschemapb.SrvVSchema{
  5424  				Keyspaces: map[string]*vschemapb.Keyspace{
  5425  					"testkeyspace": {
  5426  						Sharded:                true,
  5427  						RequireExplicitRouting: false,
  5428  					},
  5429  					"unsharded": {
  5430  						Sharded:                false,
  5431  						RequireExplicitRouting: false,
  5432  					},
  5433  				},
  5434  				RoutingRules: &vschemapb.RoutingRules{
  5435  					Rules: []*vschemapb.RoutingRule{},
  5436  				},
  5437  			}
  5438  
  5439  			err := ts.UpdateSrvVSchema(ctx, "zone1", zone1SrvVSchema)
  5440  			require.NoError(t, err, "cannot add zone1 srv vschema")
  5441  			err = ts.UpdateSrvVSchema(ctx, "zone2", zone2SrvVSchema)
  5442  			require.NoError(t, err, "cannot add zone2 srv vschema")
  5443  
  5444  			if tt.topoErr != nil {
  5445  				topofactory.SetError(tt.topoErr)
  5446  			}
  5447  
  5448  			resp, err := vtctld.GetSrvVSchemas(ctx, tt.req)
  5449  
  5450  			if tt.shouldErr {
  5451  				assert.Error(t, err)
  5452  				return
  5453  			}
  5454  
  5455  			assert.NoError(t, err)
  5456  			utils.MustMatch(t, tt.expected, resp)
  5457  		})
  5458  	}
  5459  }
  5460  
  5461  func TestGetTablet(t *testing.T) {
  5462  	t.Parallel()
  5463  
  5464  	ctx := context.Background()
  5465  	ts := memorytopo.NewServer("cell1")
  5466  	vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  5467  		return NewVtctldServer(ts)
  5468  	})
  5469  
  5470  	tablet := &topodatapb.Tablet{
  5471  		Alias: &topodatapb.TabletAlias{
  5472  			Cell: "cell1",
  5473  			Uid:  100,
  5474  		},
  5475  		Hostname: "localhost",
  5476  		Keyspace: "testkeyspace",
  5477  		Shard:    "-",
  5478  		Type:     topodatapb.TabletType_REPLICA,
  5479  	}
  5480  
  5481  	testutil.AddTablet(ctx, t, ts, tablet, nil)
  5482  
  5483  	resp, err := vtctld.GetTablet(ctx, &vtctldatapb.GetTabletRequest{
  5484  		TabletAlias: &topodatapb.TabletAlias{
  5485  			Cell: "cell1",
  5486  			Uid:  100,
  5487  		},
  5488  	})
  5489  	assert.NoError(t, err)
  5490  	utils.MustMatch(t, resp.Tablet, tablet)
  5491  
  5492  	// not found
  5493  	_, err = vtctld.GetTablet(ctx, &vtctldatapb.GetTabletRequest{
  5494  		TabletAlias: &topodatapb.TabletAlias{
  5495  			Cell: "cell1",
  5496  			Uid:  101,
  5497  		},
  5498  	})
  5499  	assert.Error(t, err)
  5500  }
  5501  
  5502  func TestGetTablets(t *testing.T) {
  5503  	t.Parallel()
  5504  
  5505  	tests := []struct {
  5506  		name      string
  5507  		cells     []string
  5508  		tablets   []*topodatapb.Tablet
  5509  		req       *vtctldatapb.GetTabletsRequest
  5510  		expected  []*topodatapb.Tablet
  5511  		shouldErr bool
  5512  	}{
  5513  		{
  5514  			name:      "no tablets",
  5515  			cells:     []string{"cell1"},
  5516  			tablets:   []*topodatapb.Tablet{},
  5517  			req:       &vtctldatapb.GetTabletsRequest{},
  5518  			expected:  []*topodatapb.Tablet{},
  5519  			shouldErr: false,
  5520  		},
  5521  		{
  5522  			name:  "keyspace and shard filter",
  5523  			cells: []string{"cell1"},
  5524  			tablets: []*topodatapb.Tablet{
  5525  				{
  5526  					Alias: &topodatapb.TabletAlias{
  5527  						Cell: "cell1",
  5528  						Uid:  100,
  5529  					},
  5530  					Keyspace: "ks1",
  5531  					Shard:    "-80",
  5532  				},
  5533  				{
  5534  					Alias: &topodatapb.TabletAlias{
  5535  						Cell: "cell1",
  5536  						Uid:  101,
  5537  					},
  5538  					Keyspace: "ks1",
  5539  					Shard:    "80-",
  5540  				},
  5541  				{
  5542  					Alias: &topodatapb.TabletAlias{
  5543  						Cell: "cell1",
  5544  						Uid:  102,
  5545  					},
  5546  					Keyspace: "ks2",
  5547  					Shard:    "-",
  5548  				},
  5549  			},
  5550  			req: &vtctldatapb.GetTabletsRequest{
  5551  				Keyspace: "ks1",
  5552  				Shard:    "80-",
  5553  			},
  5554  			expected: []*topodatapb.Tablet{
  5555  				{
  5556  					Alias: &topodatapb.TabletAlias{
  5557  						Cell: "cell1",
  5558  						Uid:  101,
  5559  					},
  5560  					Keyspace: "ks1",
  5561  					Shard:    "80-",
  5562  				},
  5563  			},
  5564  			shouldErr: false,
  5565  		},
  5566  		{
  5567  			name:  "keyspace filter",
  5568  			cells: []string{"cell1"},
  5569  			tablets: []*topodatapb.Tablet{
  5570  				{
  5571  					Alias: &topodatapb.TabletAlias{
  5572  						Cell: "cell1",
  5573  						Uid:  100,
  5574  					},
  5575  					Keyspace: "ks1",
  5576  				},
  5577  				{
  5578  					Alias: &topodatapb.TabletAlias{
  5579  						Cell: "cell1",
  5580  						Uid:  101,
  5581  					},
  5582  					Keyspace: "ks1",
  5583  				},
  5584  				{
  5585  					Alias: &topodatapb.TabletAlias{
  5586  						Cell: "cell1",
  5587  						Uid:  102,
  5588  					},
  5589  					Keyspace: "otherkeyspace",
  5590  				},
  5591  			},
  5592  			req: &vtctldatapb.GetTabletsRequest{
  5593  				Keyspace: "ks1",
  5594  			},
  5595  			expected: []*topodatapb.Tablet{
  5596  				{
  5597  					Alias: &topodatapb.TabletAlias{
  5598  						Cell: "cell1",
  5599  						Uid:  100,
  5600  					},
  5601  					Keyspace: "ks1",
  5602  				},
  5603  				{
  5604  					Alias: &topodatapb.TabletAlias{
  5605  						Cell: "cell1",
  5606  						Uid:  101,
  5607  					},
  5608  					Keyspace: "ks1",
  5609  				},
  5610  			},
  5611  			shouldErr: false,
  5612  		},
  5613  		{
  5614  			name:  "keyspace and shard filter - stale primary",
  5615  			cells: []string{"cell1"},
  5616  			tablets: []*topodatapb.Tablet{
  5617  				{
  5618  					Alias: &topodatapb.TabletAlias{
  5619  						Cell: "cell1",
  5620  						Uid:  100,
  5621  					},
  5622  					Keyspace: "ks1",
  5623  					Shard:    "-80",
  5624  				},
  5625  				{
  5626  					Alias: &topodatapb.TabletAlias{
  5627  						Cell: "cell1",
  5628  						Uid:  101,
  5629  					},
  5630  					Keyspace: "ks1",
  5631  					Shard:    "80-",
  5632  				},
  5633  				{
  5634  					Alias: &topodatapb.TabletAlias{
  5635  						Cell: "cell1",
  5636  						Uid:  102,
  5637  					},
  5638  					Keyspace:             "ks2",
  5639  					Shard:                "-",
  5640  					Type:                 topodatapb.TabletType_PRIMARY,
  5641  					PrimaryTermStartTime: logutil.TimeToProto(time.Date(2006, time.January, 2, 15, 4, 5, 0, time.UTC)),
  5642  				},
  5643  				{
  5644  					Alias: &topodatapb.TabletAlias{
  5645  						Cell: "cell1",
  5646  						Uid:  103,
  5647  					},
  5648  					Keyspace:             "ks2",
  5649  					Shard:                "-",
  5650  					Hostname:             "stale.primary",
  5651  					Type:                 topodatapb.TabletType_PRIMARY,
  5652  					PrimaryTermStartTime: logutil.TimeToProto(time.Date(2006, time.January, 2, 14, 4, 5, 0, time.UTC)),
  5653  				},
  5654  			},
  5655  			req: &vtctldatapb.GetTabletsRequest{
  5656  				Keyspace: "ks2",
  5657  				Shard:    "-",
  5658  			},
  5659  			expected: []*topodatapb.Tablet{
  5660  				{
  5661  					Alias: &topodatapb.TabletAlias{
  5662  						Cell: "cell1",
  5663  						Uid:  102,
  5664  					},
  5665  					Keyspace:             "ks2",
  5666  					Shard:                "-",
  5667  					Type:                 topodatapb.TabletType_PRIMARY,
  5668  					PrimaryTermStartTime: logutil.TimeToProto(time.Date(2006, time.January, 2, 15, 4, 5, 0, time.UTC)),
  5669  				},
  5670  				{
  5671  					Alias: &topodatapb.TabletAlias{
  5672  						Cell: "cell1",
  5673  						Uid:  103,
  5674  					},
  5675  					Keyspace:             "ks2",
  5676  					Shard:                "-",
  5677  					Hostname:             "stale.primary",
  5678  					Type:                 topodatapb.TabletType_UNKNOWN,
  5679  					PrimaryTermStartTime: logutil.TimeToProto(time.Date(2006, time.January, 2, 14, 4, 5, 0, time.UTC)),
  5680  				},
  5681  			},
  5682  			shouldErr: false,
  5683  		},
  5684  		{
  5685  			name:  "stale primary",
  5686  			cells: []string{"cell1"},
  5687  			tablets: []*topodatapb.Tablet{
  5688  				{
  5689  					Alias: &topodatapb.TabletAlias{
  5690  						Cell: "cell1",
  5691  						Uid:  100,
  5692  					},
  5693  					Keyspace:             "ks1",
  5694  					Shard:                "-",
  5695  					Hostname:             "slightly less stale",
  5696  					Type:                 topodatapb.TabletType_PRIMARY,
  5697  					PrimaryTermStartTime: logutil.TimeToProto(time.Date(2006, time.January, 2, 15, 4, 5, 0, time.UTC)),
  5698  				},
  5699  				{
  5700  					Alias: &topodatapb.TabletAlias{
  5701  						Cell: "cell1",
  5702  						Uid:  101,
  5703  					},
  5704  					Hostname:             "stale primary",
  5705  					Keyspace:             "ks1",
  5706  					Shard:                "-",
  5707  					Type:                 topodatapb.TabletType_PRIMARY,
  5708  					PrimaryTermStartTime: logutil.TimeToProto(time.Date(2006, time.January, 2, 14, 4, 5, 0, time.UTC)),
  5709  				},
  5710  				{
  5711  					Alias: &topodatapb.TabletAlias{
  5712  						Cell: "cell1",
  5713  						Uid:  103,
  5714  					},
  5715  					Hostname:             "true primary",
  5716  					Keyspace:             "ks1",
  5717  					Shard:                "-",
  5718  					Type:                 topodatapb.TabletType_PRIMARY,
  5719  					PrimaryTermStartTime: logutil.TimeToProto(time.Date(2006, time.January, 2, 16, 4, 5, 0, time.UTC)),
  5720  				},
  5721  			},
  5722  			req: &vtctldatapb.GetTabletsRequest{},
  5723  			expected: []*topodatapb.Tablet{
  5724  				{
  5725  					Alias: &topodatapb.TabletAlias{
  5726  						Cell: "cell1",
  5727  						Uid:  100,
  5728  					},
  5729  					Keyspace:             "ks1",
  5730  					Shard:                "-",
  5731  					Hostname:             "slightly less stale",
  5732  					Type:                 topodatapb.TabletType_UNKNOWN,
  5733  					PrimaryTermStartTime: logutil.TimeToProto(time.Date(2006, time.January, 2, 15, 4, 5, 0, time.UTC)),
  5734  				},
  5735  				{
  5736  					Alias: &topodatapb.TabletAlias{
  5737  						Cell: "cell1",
  5738  						Uid:  101,
  5739  					},
  5740  					Hostname:             "stale primary",
  5741  					Keyspace:             "ks1",
  5742  					Shard:                "-",
  5743  					Type:                 topodatapb.TabletType_UNKNOWN,
  5744  					PrimaryTermStartTime: logutil.TimeToProto(time.Date(2006, time.January, 2, 14, 4, 5, 0, time.UTC)),
  5745  				},
  5746  				{
  5747  					Alias: &topodatapb.TabletAlias{
  5748  						Cell: "cell1",
  5749  						Uid:  103,
  5750  					},
  5751  					Hostname:             "true primary",
  5752  					Keyspace:             "ks1",
  5753  					Shard:                "-",
  5754  					Type:                 topodatapb.TabletType_PRIMARY,
  5755  					PrimaryTermStartTime: logutil.TimeToProto(time.Date(2006, time.January, 2, 16, 4, 5, 0, time.UTC)),
  5756  				},
  5757  			},
  5758  			shouldErr: false,
  5759  		},
  5760  		{
  5761  			name:    "keyspace and shard filter - error",
  5762  			cells:   []string{"cell1"},
  5763  			tablets: []*topodatapb.Tablet{},
  5764  			req: &vtctldatapb.GetTabletsRequest{
  5765  				Keyspace: "ks1",
  5766  				Shard:    "-",
  5767  			},
  5768  			expected:  []*topodatapb.Tablet{},
  5769  			shouldErr: true,
  5770  		},
  5771  		{
  5772  			name:  "cells filter",
  5773  			cells: []string{"cell1", "cell2", "cell3"},
  5774  			tablets: []*topodatapb.Tablet{
  5775  				{
  5776  					Alias: &topodatapb.TabletAlias{
  5777  						Cell: "cell1",
  5778  						Uid:  100,
  5779  					},
  5780  				},
  5781  				{
  5782  					Alias: &topodatapb.TabletAlias{
  5783  						Cell: "cell2",
  5784  						Uid:  200,
  5785  					},
  5786  				},
  5787  				{
  5788  					Alias: &topodatapb.TabletAlias{
  5789  						Cell: "cell3",
  5790  						Uid:  300,
  5791  					},
  5792  				},
  5793  			},
  5794  			req: &vtctldatapb.GetTabletsRequest{
  5795  				Cells: []string{"cell1", "cell3"},
  5796  			},
  5797  			expected: []*topodatapb.Tablet{
  5798  				{
  5799  					Alias: &topodatapb.TabletAlias{
  5800  						Cell: "cell1",
  5801  						Uid:  100,
  5802  					},
  5803  				},
  5804  				{
  5805  					Alias: &topodatapb.TabletAlias{
  5806  						Cell: "cell3",
  5807  						Uid:  300,
  5808  					},
  5809  				},
  5810  			},
  5811  			shouldErr: false,
  5812  		},
  5813  		{
  5814  			name:  "cells filter with single error is nonfatal",
  5815  			cells: []string{"cell1"},
  5816  			tablets: []*topodatapb.Tablet{
  5817  				{
  5818  					Alias: &topodatapb.TabletAlias{
  5819  						Cell: "cell1",
  5820  						Uid:  100,
  5821  					},
  5822  					Keyspace: "ks1",
  5823  					Shard:    "-",
  5824  				},
  5825  			},
  5826  			req: &vtctldatapb.GetTabletsRequest{
  5827  				Cells: []string{"cell1", "doesnotexist"},
  5828  			},
  5829  			expected: []*topodatapb.Tablet{
  5830  				{
  5831  					Alias: &topodatapb.TabletAlias{
  5832  						Cell: "cell1",
  5833  						Uid:  100,
  5834  					},
  5835  					Keyspace: "ks1",
  5836  					Shard:    "-",
  5837  				},
  5838  			},
  5839  			shouldErr: false,
  5840  		},
  5841  		{
  5842  			name:  "cells filter with single error is fatal in strict mode",
  5843  			cells: []string{"cell1"},
  5844  			tablets: []*topodatapb.Tablet{
  5845  				{
  5846  					Alias: &topodatapb.TabletAlias{
  5847  						Cell: "cell1",
  5848  						Uid:  100,
  5849  					},
  5850  					Keyspace: "ks1",
  5851  					Shard:    "-",
  5852  				},
  5853  			},
  5854  			req: &vtctldatapb.GetTabletsRequest{
  5855  				Cells:  []string{"cell1", "doesnotexist"},
  5856  				Strict: true,
  5857  			},
  5858  			shouldErr: true,
  5859  		},
  5860  		{
  5861  			name:  "in nonstrict mode if all cells fail the request fails",
  5862  			cells: []string{"cell1"},
  5863  			tablets: []*topodatapb.Tablet{
  5864  				{
  5865  					Alias: &topodatapb.TabletAlias{
  5866  						Cell: "cell1",
  5867  						Uid:  100,
  5868  					},
  5869  					Keyspace: "ks1",
  5870  					Shard:    "-",
  5871  				},
  5872  			},
  5873  			req: &vtctldatapb.GetTabletsRequest{
  5874  				Cells: []string{"doesnotexist", "alsodoesnotexist"},
  5875  			},
  5876  			shouldErr: true,
  5877  		},
  5878  		{
  5879  			name:  "tablet alias filtering",
  5880  			cells: []string{"zone1"},
  5881  			tablets: []*topodatapb.Tablet{
  5882  				{
  5883  					Alias: &topodatapb.TabletAlias{
  5884  						Cell: "zone1",
  5885  						Uid:  100,
  5886  					},
  5887  					Keyspace: "testkeyspace",
  5888  					Shard:    "-",
  5889  				},
  5890  			},
  5891  			req: &vtctldatapb.GetTabletsRequest{
  5892  				TabletAliases: []*topodatapb.TabletAlias{
  5893  					{
  5894  						Cell: "zone1",
  5895  						Uid:  100,
  5896  					},
  5897  					{
  5898  						// This tablet doesn't exist, but doesn't cause a failure.
  5899  						Cell: "zone404",
  5900  						Uid:  404,
  5901  					},
  5902  				},
  5903  				// The below filters are ignored, because TabletAliases always
  5904  				// takes precedence.
  5905  				Keyspace: "another_keyspace",
  5906  				Shard:    "-80",
  5907  				Cells:    []string{"zone404"},
  5908  			},
  5909  			expected: []*topodatapb.Tablet{
  5910  				{
  5911  					Alias: &topodatapb.TabletAlias{
  5912  						Cell: "zone1",
  5913  						Uid:  100,
  5914  					},
  5915  					Keyspace: "testkeyspace",
  5916  					Shard:    "-",
  5917  				},
  5918  			},
  5919  			shouldErr: false,
  5920  		},
  5921  		{
  5922  			name:    "tablet alias filter with none found",
  5923  			tablets: []*topodatapb.Tablet{},
  5924  			req: &vtctldatapb.GetTabletsRequest{
  5925  				TabletAliases: []*topodatapb.TabletAlias{
  5926  					{
  5927  						Cell: "zone1",
  5928  						Uid:  101,
  5929  					},
  5930  				},
  5931  			},
  5932  			expected:  []*topodatapb.Tablet{},
  5933  			shouldErr: false,
  5934  		},
  5935  	}
  5936  
  5937  	for _, tt := range tests {
  5938  		tt := tt
  5939  
  5940  		t.Run(tt.name, func(t *testing.T) {
  5941  			t.Parallel()
  5942  
  5943  			ctx := context.Background()
  5944  			ts := memorytopo.NewServer(tt.cells...)
  5945  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  5946  				return NewVtctldServer(ts)
  5947  			})
  5948  
  5949  			testutil.AddTablets(ctx, t, ts, nil, tt.tablets...)
  5950  
  5951  			resp, err := vtctld.GetTablets(ctx, tt.req)
  5952  			if tt.shouldErr {
  5953  				assert.Error(t, err)
  5954  				return
  5955  			}
  5956  
  5957  			assert.NoError(t, err)
  5958  			testutil.AssertSameTablets(t, tt.expected, resp.Tablets)
  5959  		})
  5960  	}
  5961  }
  5962  
  5963  func TestGetTopologyPath(t *testing.T) {
  5964  	t.Parallel()
  5965  
  5966  	ctx := context.Background()
  5967  	ts := memorytopo.NewServer("cell1", "cell2", "cell3")
  5968  	vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  5969  		return NewVtctldServer(ts)
  5970  	})
  5971  
  5972  	err := ts.CreateKeyspace(ctx, "keyspace1", &topodatapb.Keyspace{})
  5973  	require.NoError(t, err)
  5974  
  5975  	testutil.AddTablets(ctx, t, ts, nil, &topodatapb.Tablet{
  5976  		Alias:         &topodatapb.TabletAlias{Cell: "cell1", Uid: 100},
  5977  		Hostname:      "localhost",
  5978  		Keyspace:      "keyspace1",
  5979  		MysqlHostname: "localhost",
  5980  		MysqlPort:     17100,
  5981  	})
  5982  	require.NoError(t, err)
  5983  
  5984  	tests := []struct {
  5985  		name      string
  5986  		path      string
  5987  		shouldErr bool
  5988  		expected  *vtctldatapb.GetTopologyPathResponse
  5989  	}{
  5990  		{
  5991  			name: "root path",
  5992  			path: "/",
  5993  			expected: &vtctldatapb.GetTopologyPathResponse{
  5994  				Cell: &vtctldatapb.TopologyCell{
  5995  					Path:     "/",
  5996  					Children: []string{"global", "cell1", "cell2", "cell3"},
  5997  				},
  5998  			},
  5999  		},
  6000  		{
  6001  			name:      "invalid path",
  6002  			path:      "",
  6003  			shouldErr: true,
  6004  		},
  6005  		{
  6006  			name: "global path",
  6007  			path: "/global",
  6008  			expected: &vtctldatapb.GetTopologyPathResponse{
  6009  				Cell: &vtctldatapb.TopologyCell{
  6010  					Name:     "global",
  6011  					Path:     "/global",
  6012  					Children: []string{"cells", "keyspaces"},
  6013  				},
  6014  			},
  6015  		},
  6016  		{
  6017  			name: "terminal data path",
  6018  			path: "/cell1/tablets/cell1-0000000100/Tablet",
  6019  			expected: &vtctldatapb.GetTopologyPathResponse{
  6020  				Cell: &vtctldatapb.TopologyCell{
  6021  					Name: "Tablet",
  6022  					Path: "/cell1/tablets/cell1-0000000100/Tablet",
  6023  					Data: "alias:{cell:\"cell1\" uid:100} hostname:\"localhost\" keyspace:\"keyspace1\" mysql_hostname:\"localhost\" mysql_port:17100",
  6024  				},
  6025  			},
  6026  		},
  6027  	}
  6028  
  6029  	for _, tt := range tests {
  6030  		tt := tt
  6031  
  6032  		t.Run(tt.name, func(t *testing.T) {
  6033  			t.Parallel()
  6034  
  6035  			ctx := context.Background()
  6036  			resp, err := vtctld.GetTopologyPath(ctx, &vtctldatapb.GetTopologyPathRequest{
  6037  				Path: tt.path,
  6038  			})
  6039  
  6040  			if tt.shouldErr {
  6041  				assert.Error(t, err)
  6042  				return
  6043  			}
  6044  
  6045  			assert.NoError(t, err)
  6046  			utils.MustMatch(t, tt.expected, resp)
  6047  		})
  6048  	}
  6049  }
  6050  
  6051  func TestGetVSchema(t *testing.T) {
  6052  	t.Parallel()
  6053  
  6054  	ctx := context.Background()
  6055  	ts := memorytopo.NewServer("zone1")
  6056  	vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  6057  		return NewVtctldServer(ts)
  6058  	})
  6059  
  6060  	t.Run("found", func(t *testing.T) {
  6061  		t.Parallel()
  6062  
  6063  		err := ts.SaveVSchema(ctx, "testkeyspace", &vschemapb.Keyspace{
  6064  			Sharded: true,
  6065  			Vindexes: map[string]*vschemapb.Vindex{
  6066  				"v1": {
  6067  					Type: "hash",
  6068  				},
  6069  			},
  6070  		})
  6071  		require.NoError(t, err)
  6072  
  6073  		expected := &vtctldatapb.GetVSchemaResponse{
  6074  			VSchema: &vschemapb.Keyspace{
  6075  				Sharded: true,
  6076  				Vindexes: map[string]*vschemapb.Vindex{
  6077  					"v1": {
  6078  						Type: "hash",
  6079  					},
  6080  				},
  6081  			},
  6082  		}
  6083  
  6084  		resp, err := vtctld.GetVSchema(ctx, &vtctldatapb.GetVSchemaRequest{
  6085  			Keyspace: "testkeyspace",
  6086  		})
  6087  		assert.NoError(t, err)
  6088  		utils.MustMatch(t, expected, resp)
  6089  	})
  6090  
  6091  	t.Run("not found", func(t *testing.T) {
  6092  		t.Parallel()
  6093  
  6094  		_, err := vtctld.GetVSchema(ctx, &vtctldatapb.GetVSchemaRequest{
  6095  			Keyspace: "doesnotexist",
  6096  		})
  6097  		assert.Error(t, err)
  6098  	})
  6099  }
  6100  
  6101  func TestPingTablet(t *testing.T) {
  6102  	t.Parallel()
  6103  
  6104  	ctx := context.Background()
  6105  	ts := memorytopo.NewServer("zone1")
  6106  	testutil.AddTablet(ctx, t, ts, &topodatapb.Tablet{
  6107  		Alias: &topodatapb.TabletAlias{
  6108  			Cell: "zone1",
  6109  			Uid:  100,
  6110  		},
  6111  		Keyspace: "testkeyspace",
  6112  		Shard:    "-",
  6113  	}, nil)
  6114  
  6115  	tests := []struct {
  6116  		name      string
  6117  		tmc       testutil.TabletManagerClient
  6118  		req       *vtctldatapb.PingTabletRequest
  6119  		expected  *vtctldatapb.PingTabletResponse
  6120  		shouldErr bool
  6121  	}{
  6122  		{
  6123  			name: "ok",
  6124  			tmc: testutil.TabletManagerClient{
  6125  				PingResults: map[string]error{
  6126  					"zone1-0000000100": nil,
  6127  				},
  6128  			},
  6129  			req: &vtctldatapb.PingTabletRequest{
  6130  				TabletAlias: &topodatapb.TabletAlias{
  6131  					Cell: "zone1",
  6132  					Uid:  100,
  6133  				},
  6134  			},
  6135  			expected: &vtctldatapb.PingTabletResponse{},
  6136  		},
  6137  		{
  6138  			name: "tablet not found",
  6139  			tmc: testutil.TabletManagerClient{
  6140  				PingResults: map[string]error{
  6141  					"zone1-0000000100": nil,
  6142  				},
  6143  			},
  6144  			req: &vtctldatapb.PingTabletRequest{
  6145  				TabletAlias: &topodatapb.TabletAlias{
  6146  					Cell: "zone2",
  6147  					Uid:  404,
  6148  				},
  6149  			},
  6150  			shouldErr: true,
  6151  		},
  6152  		{
  6153  			name: "ping rpc error",
  6154  			tmc: testutil.TabletManagerClient{
  6155  				PingResults: map[string]error{
  6156  					"zone1-0000000100": assert.AnError,
  6157  				},
  6158  			},
  6159  			req: &vtctldatapb.PingTabletRequest{
  6160  				TabletAlias: &topodatapb.TabletAlias{
  6161  					Cell: "zone1",
  6162  					Uid:  100,
  6163  				},
  6164  			},
  6165  			shouldErr: true,
  6166  		},
  6167  	}
  6168  
  6169  	for _, tt := range tests {
  6170  		tt := tt
  6171  		t.Run(tt.name, func(t *testing.T) {
  6172  			t.Parallel()
  6173  
  6174  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, &tt.tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  6175  				return NewVtctldServer(ts)
  6176  			})
  6177  
  6178  			resp, err := vtctld.PingTablet(ctx, tt.req)
  6179  			if tt.shouldErr {
  6180  				assert.Error(t, err)
  6181  				assert.Nil(t, resp)
  6182  				return
  6183  			}
  6184  
  6185  			require.NoError(t, err)
  6186  			assert.Equal(t, tt.expected, resp)
  6187  		})
  6188  	}
  6189  }
  6190  
  6191  func TestPlannedReparentShard(t *testing.T) {
  6192  	t.Parallel()
  6193  
  6194  	tests := []struct {
  6195  		name    string
  6196  		ts      *topo.Server
  6197  		tmc     tmclient.TabletManagerClient
  6198  		tablets []*topodatapb.Tablet
  6199  
  6200  		req                 *vtctldatapb.PlannedReparentShardRequest
  6201  		expected            *vtctldatapb.PlannedReparentShardResponse
  6202  		expectEventsToOccur bool
  6203  		shouldErr           bool
  6204  	}{
  6205  		{
  6206  			name: "successful reparent",
  6207  			ts:   memorytopo.NewServer("zone1"),
  6208  			tablets: []*topodatapb.Tablet{
  6209  				{
  6210  					Alias: &topodatapb.TabletAlias{
  6211  						Cell: "zone1",
  6212  						Uid:  100,
  6213  					},
  6214  					Type: topodatapb.TabletType_PRIMARY,
  6215  					PrimaryTermStartTime: &vttime.Time{
  6216  						Seconds: 100,
  6217  					},
  6218  					Keyspace: "testkeyspace",
  6219  					Shard:    "-",
  6220  				},
  6221  				{
  6222  					Alias: &topodatapb.TabletAlias{
  6223  						Cell: "zone1",
  6224  						Uid:  200,
  6225  					},
  6226  					Type:     topodatapb.TabletType_REPLICA,
  6227  					Keyspace: "testkeyspace",
  6228  					Shard:    "-",
  6229  				},
  6230  				{
  6231  					Alias: &topodatapb.TabletAlias{
  6232  						Cell: "zone1",
  6233  						Uid:  101,
  6234  					},
  6235  					Type:     topodatapb.TabletType_RDONLY,
  6236  					Keyspace: "testkeyspace",
  6237  					Shard:    "-",
  6238  				},
  6239  			},
  6240  			tmc: &testutil.TabletManagerClient{
  6241  				DemotePrimaryResults: map[string]struct {
  6242  					Status *replicationdatapb.PrimaryStatus
  6243  					Error  error
  6244  				}{
  6245  					"zone1-0000000100": {
  6246  						Status: &replicationdatapb.PrimaryStatus{
  6247  							Position: "primary-demotion position",
  6248  						},
  6249  						Error: nil,
  6250  					},
  6251  				},
  6252  				PrimaryPositionResults: map[string]struct {
  6253  					Position string
  6254  					Error    error
  6255  				}{
  6256  					"zone1-0000000100": {
  6257  						Position: "doesn't matter",
  6258  						Error:    nil,
  6259  					},
  6260  				},
  6261  				PopulateReparentJournalResults: map[string]error{
  6262  					"zone1-0000000200": nil,
  6263  				},
  6264  				PromoteReplicaResults: map[string]struct {
  6265  					Result string
  6266  					Error  error
  6267  				}{
  6268  					"zone1-0000000200": {
  6269  						Result: "promotion position",
  6270  						Error:  nil,
  6271  					},
  6272  				},
  6273  				SetReplicationSourceResults: map[string]error{
  6274  					"zone1-0000000200": nil, // waiting for primary-position during promotion
  6275  					// reparent SetReplicationSource calls
  6276  					"zone1-0000000100": nil,
  6277  					"zone1-0000000101": nil,
  6278  				},
  6279  				WaitForPositionResults: map[string]map[string]error{
  6280  					"zone1-0000000200": {
  6281  						"primary-demotion position": nil,
  6282  					},
  6283  				},
  6284  			},
  6285  			req: &vtctldatapb.PlannedReparentShardRequest{
  6286  				Keyspace: "testkeyspace",
  6287  				Shard:    "-",
  6288  				NewPrimary: &topodatapb.TabletAlias{
  6289  					Cell: "zone1",
  6290  					Uid:  200,
  6291  				},
  6292  				WaitReplicasTimeout: protoutil.DurationToProto(time.Millisecond * 10),
  6293  			},
  6294  			expected: &vtctldatapb.PlannedReparentShardResponse{
  6295  				Keyspace: "testkeyspace",
  6296  				Shard:    "-",
  6297  				PromotedPrimary: &topodatapb.TabletAlias{
  6298  					Cell: "zone1",
  6299  					Uid:  200,
  6300  				},
  6301  			},
  6302  			expectEventsToOccur: true,
  6303  			shouldErr:           false,
  6304  		},
  6305  		{
  6306  			// Note: this is testing the error-handling done in
  6307  			// (*VtctldServer).PlannedReparentShard, not the logic of an PRS.
  6308  			// That logic is tested in reparentutil, and not here. Therefore,
  6309  			// the simplest way to trigger a failure is to attempt an PRS on a
  6310  			// shard that does not exist.
  6311  			name:    "failed reparent",
  6312  			ts:      memorytopo.NewServer("zone1"),
  6313  			tablets: nil,
  6314  			req: &vtctldatapb.PlannedReparentShardRequest{
  6315  				Keyspace: "testkeyspace",
  6316  				Shard:    "-",
  6317  			},
  6318  			expectEventsToOccur: false,
  6319  			shouldErr:           true,
  6320  		},
  6321  		{
  6322  			name: "invalid WaitReplicasTimeout",
  6323  			req: &vtctldatapb.PlannedReparentShardRequest{
  6324  				WaitReplicasTimeout: &vttime.Duration{
  6325  					Seconds: -1,
  6326  					Nanos:   1,
  6327  				},
  6328  			},
  6329  			shouldErr: true,
  6330  		},
  6331  	}
  6332  
  6333  	ctx := context.Background()
  6334  
  6335  	for _, tt := range tests {
  6336  		tt := tt
  6337  
  6338  		t.Run(tt.name, func(t *testing.T) {
  6339  			t.Parallel()
  6340  
  6341  			testutil.AddTablets(ctx, t, tt.ts, &testutil.AddTabletOptions{
  6342  				AlsoSetShardPrimary:  true,
  6343  				ForceSetShardPrimary: true,
  6344  				SkipShardCreation:    false,
  6345  			}, tt.tablets...)
  6346  
  6347  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, tt.ts, tt.tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  6348  				return NewVtctldServer(ts)
  6349  			})
  6350  			resp, err := vtctld.PlannedReparentShard(ctx, tt.req)
  6351  
  6352  			// We defer this because we want to check in both error and non-
  6353  			// error cases, but after the main set of assertions for those
  6354  			// cases.
  6355  			defer func() {
  6356  				if !tt.expectEventsToOccur {
  6357  					testutil.AssertNoLogutilEventsOccurred(t, resp, "expected no events to occur during ERS")
  6358  
  6359  					return
  6360  				}
  6361  
  6362  				testutil.AssertLogutilEventsOccurred(t, resp, "expected events to occur during ERS")
  6363  			}()
  6364  
  6365  			if tt.shouldErr {
  6366  				assert.Error(t, err)
  6367  
  6368  				return
  6369  			}
  6370  
  6371  			assert.NoError(t, err)
  6372  			testutil.AssertPlannedReparentShardResponsesEqual(t, tt.expected, resp)
  6373  		})
  6374  	}
  6375  }
  6376  
  6377  func TestRebuildKeyspaceGraph(t *testing.T) {
  6378  	t.Parallel()
  6379  
  6380  	t.Run("ok", func(t *testing.T) {
  6381  		t.Parallel()
  6382  
  6383  		ctx := context.Background()
  6384  		ts := memorytopo.NewServer("zone1")
  6385  		testutil.AddKeyspace(ctx, t, ts, &vtctldatapb.Keyspace{
  6386  			Name: "testkeyspace",
  6387  		})
  6388  		vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  6389  			return NewVtctldServer(ts)
  6390  		})
  6391  
  6392  		_, err := vtctld.RebuildKeyspaceGraph(ctx, &vtctldatapb.RebuildKeyspaceGraphRequest{
  6393  			Keyspace: "testkeyspace",
  6394  		})
  6395  		assert.NoError(t, err)
  6396  	})
  6397  
  6398  	t.Run("no such keyspace", func(t *testing.T) {
  6399  		t.Parallel()
  6400  
  6401  		ts := memorytopo.NewServer("zone1")
  6402  		vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  6403  			return NewVtctldServer(ts)
  6404  		})
  6405  
  6406  		_, err := vtctld.RebuildKeyspaceGraph(context.Background(), &vtctldatapb.RebuildKeyspaceGraphRequest{
  6407  			Keyspace: "testkeyspace",
  6408  		})
  6409  		assert.Error(t, err)
  6410  	})
  6411  
  6412  	t.Run("topo unavailable", func(t *testing.T) {
  6413  		t.Parallel()
  6414  
  6415  		ctx := context.Background()
  6416  		ts, factory := memorytopo.NewServerAndFactory("zone1")
  6417  		testutil.AddKeyspace(ctx, t, ts, &vtctldatapb.Keyspace{
  6418  			Name: "testkeyspace",
  6419  		})
  6420  		vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  6421  			return NewVtctldServer(ts)
  6422  		})
  6423  		factory.SetError(assert.AnError)
  6424  
  6425  		_, err := vtctld.RebuildKeyspaceGraph(ctx, &vtctldatapb.RebuildKeyspaceGraphRequest{
  6426  			Keyspace: "testkeyspace",
  6427  		})
  6428  		assert.Error(t, err)
  6429  	})
  6430  
  6431  	t.Run("lock error", func(t *testing.T) {
  6432  		t.Parallel()
  6433  
  6434  		ctx := context.Background()
  6435  		ts := memorytopo.NewServer("zone1")
  6436  		testutil.AddKeyspace(ctx, t, ts, &vtctldatapb.Keyspace{
  6437  			Name: "testkeyspace",
  6438  		})
  6439  		vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  6440  			return NewVtctldServer(ts)
  6441  		})
  6442  
  6443  		_, unlock, lerr := ts.LockKeyspace(context.Background(), "testkeyspace", "test lock")
  6444  		require.NoError(t, lerr, "could not lock keyspace for testing")
  6445  
  6446  		defer unlock(&lerr)
  6447  		defer func() { require.NoError(t, lerr, "could not unlock testkeyspace after test") }()
  6448  
  6449  		ctx, cancel := context.WithTimeout(ctx, time.Millisecond*50)
  6450  		defer cancel()
  6451  		_, err := vtctld.RebuildKeyspaceGraph(ctx, &vtctldatapb.RebuildKeyspaceGraphRequest{
  6452  			Keyspace: "testkeyspace",
  6453  		})
  6454  		assert.Error(t, err)
  6455  	})
  6456  }
  6457  
  6458  func TestRebuildVSchemaGraph(t *testing.T) {
  6459  	t.Parallel()
  6460  
  6461  	ctx := context.Background()
  6462  	req := &vtctldatapb.RebuildVSchemaGraphRequest{}
  6463  	tests := []struct {
  6464  		name      string
  6465  		topoDown  bool
  6466  		shouldErr bool
  6467  	}{
  6468  		{
  6469  			name: "success",
  6470  		},
  6471  		{
  6472  			name:      "topo down",
  6473  			topoDown:  true,
  6474  			shouldErr: true,
  6475  		},
  6476  	}
  6477  
  6478  	for _, tt := range tests {
  6479  		tt := tt
  6480  		t.Run(tt.name, func(t *testing.T) {
  6481  			t.Parallel()
  6482  
  6483  			ts, factory := memorytopo.NewServerAndFactory("zone1")
  6484  			if tt.topoDown {
  6485  				factory.SetError(errors.New("topo down for testing"))
  6486  			}
  6487  
  6488  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  6489  				return NewVtctldServer(ts)
  6490  			})
  6491  			_, err := vtctld.RebuildVSchemaGraph(ctx, req)
  6492  			if tt.shouldErr {
  6493  				assert.Error(t, err)
  6494  				return
  6495  			}
  6496  
  6497  			assert.NoError(t, err)
  6498  		})
  6499  	}
  6500  }
  6501  
  6502  func TestRefreshState(t *testing.T) {
  6503  	t.Parallel()
  6504  
  6505  	ctx := context.Background()
  6506  	tests := []struct {
  6507  		name              string
  6508  		ts                *topo.Server
  6509  		tablet            *topodatapb.Tablet
  6510  		refreshStateError error
  6511  		req               *vtctldatapb.RefreshStateRequest
  6512  		shouldErr         bool
  6513  	}{
  6514  		{
  6515  			name: "success",
  6516  			ts:   memorytopo.NewServer("zone1"),
  6517  			tablet: &topodatapb.Tablet{
  6518  				Alias: &topodatapb.TabletAlias{
  6519  					Cell: "zone1",
  6520  					Uid:  100,
  6521  				},
  6522  			},
  6523  			refreshStateError: nil,
  6524  			req: &vtctldatapb.RefreshStateRequest{
  6525  				TabletAlias: &topodatapb.TabletAlias{
  6526  					Cell: "zone1",
  6527  					Uid:  100,
  6528  				},
  6529  			},
  6530  		},
  6531  		{
  6532  			name:      "tablet alias nil",
  6533  			ts:        memorytopo.NewServer(),
  6534  			req:       &vtctldatapb.RefreshStateRequest{},
  6535  			shouldErr: true,
  6536  		},
  6537  		{
  6538  			name: "tablet not found",
  6539  			ts:   memorytopo.NewServer("zone1"),
  6540  			tablet: &topodatapb.Tablet{
  6541  				Alias: &topodatapb.TabletAlias{
  6542  					Cell: "zone1",
  6543  					Uid:  100,
  6544  				},
  6545  			},
  6546  			refreshStateError: nil,
  6547  			req: &vtctldatapb.RefreshStateRequest{
  6548  				TabletAlias: &topodatapb.TabletAlias{
  6549  					Cell: "zone1",
  6550  					Uid:  400,
  6551  				},
  6552  			},
  6553  			shouldErr: true,
  6554  		},
  6555  		{
  6556  			name: "RefreshState failed",
  6557  			ts:   memorytopo.NewServer("zone1"),
  6558  			tablet: &topodatapb.Tablet{
  6559  				Alias: &topodatapb.TabletAlias{
  6560  					Cell: "zone1",
  6561  					Uid:  100,
  6562  				},
  6563  			},
  6564  			refreshStateError: fmt.Errorf("%w: RefreshState failed", assert.AnError),
  6565  			req: &vtctldatapb.RefreshStateRequest{
  6566  				TabletAlias: &topodatapb.TabletAlias{
  6567  					Cell: "zone1",
  6568  					Uid:  100,
  6569  				},
  6570  			},
  6571  			shouldErr: true,
  6572  		},
  6573  	}
  6574  
  6575  	for _, tt := range tests {
  6576  		tt := tt
  6577  
  6578  		t.Run(tt.name, func(t *testing.T) {
  6579  			t.Parallel()
  6580  
  6581  			var tmc testutil.TabletManagerClient
  6582  			if tt.tablet != nil {
  6583  				testutil.AddTablet(ctx, t, tt.ts, tt.tablet, nil)
  6584  				tmc.RefreshStateResults = map[string]error{
  6585  					topoproto.TabletAliasString(tt.tablet.Alias): tt.refreshStateError,
  6586  				}
  6587  			}
  6588  
  6589  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, tt.ts, &tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  6590  				return NewVtctldServer(ts)
  6591  			})
  6592  			_, err := vtctld.RefreshState(ctx, tt.req)
  6593  			if tt.shouldErr {
  6594  				assert.Error(t, err)
  6595  				return
  6596  			}
  6597  
  6598  			assert.NoError(t, err)
  6599  		})
  6600  	}
  6601  }
  6602  
  6603  func TestRefreshStateByShard(t *testing.T) {
  6604  	t.Parallel()
  6605  
  6606  	ctx := context.Background()
  6607  	tests := []struct {
  6608  		name               string
  6609  		ts                 *topo.Server
  6610  		tablets            []*topodatapb.Tablet
  6611  		refreshStateErrors []error // must have len(tablets)
  6612  		req                *vtctldatapb.RefreshStateByShardRequest
  6613  		expected           *vtctldatapb.RefreshStateByShardResponse
  6614  		shouldErr          bool
  6615  	}{
  6616  		{
  6617  			name: "success",
  6618  			ts:   memorytopo.NewServer("zone1", "zone2"),
  6619  			tablets: []*topodatapb.Tablet{
  6620  				{
  6621  					Hostname: "zone1-100",
  6622  					Alias: &topodatapb.TabletAlias{
  6623  						Cell: "zone1",
  6624  						Uid:  100,
  6625  					},
  6626  					Keyspace: "ks",
  6627  					Shard:    "-",
  6628  				},
  6629  				{
  6630  					Hostname: "zone2-100",
  6631  					Alias: &topodatapb.TabletAlias{
  6632  						Cell: "zone2",
  6633  						Uid:  100,
  6634  					},
  6635  					Keyspace: "ks",
  6636  					Shard:    "-",
  6637  				},
  6638  			},
  6639  			refreshStateErrors: []error{
  6640  				nil, // zone1-100
  6641  				nil, // zone2-100
  6642  			},
  6643  			req: &vtctldatapb.RefreshStateByShardRequest{
  6644  				Keyspace: "ks",
  6645  				Shard:    "-",
  6646  			},
  6647  			expected: &vtctldatapb.RefreshStateByShardResponse{},
  6648  		},
  6649  		{
  6650  			name: "cell filtering",
  6651  			ts:   memorytopo.NewServer("zone1", "zone2"),
  6652  			tablets: []*topodatapb.Tablet{
  6653  				{
  6654  					Hostname: "zone1-100",
  6655  					Alias: &topodatapb.TabletAlias{
  6656  						Cell: "zone1",
  6657  						Uid:  100,
  6658  					},
  6659  					Keyspace: "ks",
  6660  					Shard:    "-",
  6661  				},
  6662  				{
  6663  					Hostname: "zone2-100",
  6664  					Alias: &topodatapb.TabletAlias{
  6665  						Cell: "zone2",
  6666  						Uid:  100,
  6667  					},
  6668  					Keyspace: "ks",
  6669  					Shard:    "-",
  6670  				},
  6671  			},
  6672  			refreshStateErrors: []error{
  6673  				nil,
  6674  				fmt.Errorf("%w: RefreshState failed on zone2-100", assert.AnError),
  6675  			},
  6676  			req: &vtctldatapb.RefreshStateByShardRequest{
  6677  				Keyspace: "ks",
  6678  				Shard:    "-",
  6679  				Cells:    []string{"zone1"}, // If we didn't filter, we would get IsPartialRefresh=true because of the failure in zone2.
  6680  			},
  6681  			expected: &vtctldatapb.RefreshStateByShardResponse{
  6682  				IsPartialRefresh:      false,
  6683  				PartialRefreshDetails: "",
  6684  			},
  6685  			shouldErr: false,
  6686  		},
  6687  		{
  6688  			name: "partial result",
  6689  			ts:   memorytopo.NewServer("zone1", "zone2"),
  6690  			tablets: []*topodatapb.Tablet{
  6691  				{
  6692  					Hostname: "zone1-100",
  6693  					Alias: &topodatapb.TabletAlias{
  6694  						Cell: "zone1",
  6695  						Uid:  100,
  6696  					},
  6697  					Keyspace: "ks",
  6698  					Shard:    "-",
  6699  				},
  6700  				{
  6701  					Hostname: "zone2-100",
  6702  					Alias: &topodatapb.TabletAlias{
  6703  						Cell: "zone2",
  6704  						Uid:  100,
  6705  					},
  6706  					Keyspace: "ks",
  6707  					Shard:    "-",
  6708  				},
  6709  			},
  6710  			refreshStateErrors: []error{
  6711  				nil,
  6712  				fmt.Errorf("%w: RefreshState failed on zone2-100", assert.AnError),
  6713  			},
  6714  			req: &vtctldatapb.RefreshStateByShardRequest{
  6715  				Keyspace: "ks",
  6716  				Shard:    "-",
  6717  			},
  6718  			expected: &vtctldatapb.RefreshStateByShardResponse{
  6719  				IsPartialRefresh:      true,
  6720  				PartialRefreshDetails: "failed to refresh tablet zone2-0000000100: assert.AnError general error for testing: RefreshState failed on zone2-100",
  6721  			},
  6722  			shouldErr: false,
  6723  		},
  6724  		{
  6725  			name:      "missing keyspace argument",
  6726  			ts:        memorytopo.NewServer(),
  6727  			req:       &vtctldatapb.RefreshStateByShardRequest{},
  6728  			shouldErr: true,
  6729  		},
  6730  		{
  6731  			name: "missing shard argument",
  6732  			ts:   memorytopo.NewServer(),
  6733  			req: &vtctldatapb.RefreshStateByShardRequest{
  6734  				Keyspace: "ks",
  6735  			},
  6736  			shouldErr: true,
  6737  		},
  6738  		{
  6739  			name: "shard not found",
  6740  			ts:   memorytopo.NewServer("zone1"),
  6741  			tablets: []*topodatapb.Tablet{
  6742  				{
  6743  					Alias: &topodatapb.TabletAlias{
  6744  						Cell: "zone1",
  6745  						Uid:  100,
  6746  					},
  6747  					Keyspace: "ks",
  6748  					Shard:    "-80",
  6749  				},
  6750  			},
  6751  			refreshStateErrors: []error{nil},
  6752  			req: &vtctldatapb.RefreshStateByShardRequest{
  6753  				Keyspace: "ks2",
  6754  				Shard:    "-",
  6755  			},
  6756  			shouldErr: true,
  6757  		},
  6758  	}
  6759  
  6760  	for _, tt := range tests {
  6761  		tt := tt
  6762  
  6763  		t.Run(tt.name, func(t *testing.T) {
  6764  			t.Parallel()
  6765  
  6766  			require.Equal(t, len(tt.tablets), len(tt.refreshStateErrors), "Invalid test case: must have one refreshStateError for each tablet")
  6767  
  6768  			tmc := &testutil.TabletManagerClient{
  6769  				RefreshStateResults: make(map[string]error, len(tt.tablets)),
  6770  			}
  6771  			testutil.AddTablets(ctx, t, tt.ts, nil, tt.tablets...)
  6772  			for i, tablet := range tt.tablets {
  6773  				key := topoproto.TabletAliasString(tablet.Alias)
  6774  				tmc.RefreshStateResults[key] = tt.refreshStateErrors[i]
  6775  			}
  6776  
  6777  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, tt.ts, tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  6778  				return NewVtctldServer(ts)
  6779  			})
  6780  			resp, err := vtctld.RefreshStateByShard(ctx, tt.req)
  6781  			if tt.shouldErr {
  6782  				assert.Error(t, err)
  6783  				return
  6784  			}
  6785  
  6786  			require.NoError(t, err)
  6787  			utils.MustMatch(t, tt.expected, resp)
  6788  		})
  6789  	}
  6790  }
  6791  
  6792  func TestReloadSchema(t *testing.T) {
  6793  	t.Parallel()
  6794  
  6795  	tests := []struct {
  6796  		name      string
  6797  		tablets   []*topodatapb.Tablet
  6798  		tmc       testutil.TabletManagerClient
  6799  		req       *vtctldatapb.ReloadSchemaRequest
  6800  		shouldErr bool
  6801  	}{
  6802  		{
  6803  			name: "ok",
  6804  			tablets: []*topodatapb.Tablet{
  6805  				{
  6806  					Alias: &topodatapb.TabletAlias{
  6807  						Cell: "zone1",
  6808  						Uid:  100,
  6809  					},
  6810  				},
  6811  			},
  6812  			tmc: testutil.TabletManagerClient{
  6813  				ReloadSchemaResults: map[string]error{
  6814  					"zone1-0000000100": nil,
  6815  				},
  6816  			},
  6817  			req: &vtctldatapb.ReloadSchemaRequest{
  6818  				TabletAlias: &topodatapb.TabletAlias{
  6819  					Cell: "zone1",
  6820  					Uid:  100,
  6821  				},
  6822  			},
  6823  		},
  6824  		{
  6825  			name: "tablet not found",
  6826  			tablets: []*topodatapb.Tablet{
  6827  				{
  6828  					Alias: &topodatapb.TabletAlias{
  6829  						Cell: "zone1",
  6830  						Uid:  100,
  6831  					},
  6832  				},
  6833  			},
  6834  			tmc: testutil.TabletManagerClient{
  6835  				ReloadSchemaResults: map[string]error{
  6836  					"zone1-0000000100": nil,
  6837  				},
  6838  			},
  6839  			req: &vtctldatapb.ReloadSchemaRequest{
  6840  				TabletAlias: &topodatapb.TabletAlias{
  6841  					Cell: "zone1",
  6842  					Uid:  404,
  6843  				},
  6844  			},
  6845  			shouldErr: true,
  6846  		},
  6847  		{
  6848  			name: "tmc failure",
  6849  			tablets: []*topodatapb.Tablet{
  6850  				{
  6851  					Alias: &topodatapb.TabletAlias{
  6852  						Cell: "zone1",
  6853  						Uid:  100,
  6854  					},
  6855  				},
  6856  			},
  6857  			tmc: testutil.TabletManagerClient{
  6858  				ReloadSchemaResults: map[string]error{
  6859  					"zone1-0000000100": assert.AnError,
  6860  				},
  6861  			},
  6862  			req: &vtctldatapb.ReloadSchemaRequest{
  6863  				TabletAlias: &topodatapb.TabletAlias{
  6864  					Cell: "zone1",
  6865  					Uid:  100,
  6866  				},
  6867  			},
  6868  			shouldErr: true,
  6869  		},
  6870  	}
  6871  
  6872  	ctx := context.Background()
  6873  	for _, tt := range tests {
  6874  		tt := tt
  6875  		t.Run(tt.name, func(t *testing.T) {
  6876  			ts := memorytopo.NewServer("zone1")
  6877  			testutil.AddTablets(ctx, t, ts, nil, tt.tablets...)
  6878  
  6879  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, &tt.tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  6880  				return NewVtctldServer(ts)
  6881  			})
  6882  			_, err := vtctld.ReloadSchema(ctx, tt.req)
  6883  			if tt.shouldErr {
  6884  				assert.Error(t, err)
  6885  				return
  6886  			}
  6887  
  6888  			require.NoError(t, err)
  6889  		})
  6890  	}
  6891  }
  6892  
  6893  func TestReloadSchemaKeyspace(t *testing.T) {
  6894  	t.Parallel()
  6895  
  6896  	tests := []struct {
  6897  		name      string
  6898  		tablets   []*topodatapb.Tablet
  6899  		tmc       testutil.TabletManagerClient
  6900  		req       *vtctldatapb.ReloadSchemaKeyspaceRequest
  6901  		expected  *vtctldatapb.ReloadSchemaKeyspaceResponse
  6902  		shouldErr bool
  6903  	}{
  6904  		{
  6905  			name: "ok",
  6906  			tablets: []*topodatapb.Tablet{
  6907  				{
  6908  					Keyspace: "ks1",
  6909  					Shard:    "-80",
  6910  					Alias: &topodatapb.TabletAlias{
  6911  						Cell: "zone1",
  6912  						Uid:  100,
  6913  					},
  6914  					Type: topodatapb.TabletType_PRIMARY,
  6915  				},
  6916  				{
  6917  					Keyspace: "ks1",
  6918  					Shard:    "-80",
  6919  					Alias: &topodatapb.TabletAlias{
  6920  						Cell: "zone2",
  6921  						Uid:  200,
  6922  					},
  6923  					Type: topodatapb.TabletType_REPLICA,
  6924  				},
  6925  				{
  6926  					Keyspace: "ks1",
  6927  					Shard:    "80-",
  6928  					Alias: &topodatapb.TabletAlias{
  6929  						Cell: "zone1",
  6930  						Uid:  101,
  6931  					},
  6932  					Type: topodatapb.TabletType_PRIMARY,
  6933  				},
  6934  				{
  6935  					Keyspace: "ks1",
  6936  					Shard:    "80-",
  6937  					Alias: &topodatapb.TabletAlias{
  6938  						Cell: "zone2",
  6939  						Uid:  201,
  6940  					},
  6941  					Type: topodatapb.TabletType_REPLICA,
  6942  				},
  6943  			},
  6944  			tmc: testutil.TabletManagerClient{
  6945  				ReloadSchemaResults: map[string]error{
  6946  					"zone2-0000000200": nil,
  6947  					"zone2-0000000201": nil,
  6948  				},
  6949  			},
  6950  			req: &vtctldatapb.ReloadSchemaKeyspaceRequest{
  6951  				Keyspace: "ks1",
  6952  			},
  6953  			expected: &vtctldatapb.ReloadSchemaKeyspaceResponse{},
  6954  		},
  6955  		{
  6956  			name: "keyspace not found",
  6957  			req: &vtctldatapb.ReloadSchemaKeyspaceRequest{
  6958  				Keyspace: "ks1",
  6959  			},
  6960  			shouldErr: true,
  6961  		},
  6962  	}
  6963  
  6964  	ctx := context.Background()
  6965  	for _, tt := range tests {
  6966  		tt := tt
  6967  		t.Run(tt.name, func(t *testing.T) {
  6968  			t.Parallel()
  6969  
  6970  			ts := memorytopo.NewServer("zone1", "zone2", "zone3")
  6971  			testutil.AddTablets(ctx, t, ts, &testutil.AddTabletOptions{
  6972  				AlsoSetShardPrimary: true,
  6973  			}, tt.tablets...)
  6974  
  6975  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, &tt.tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  6976  				return NewVtctldServer(ts)
  6977  			})
  6978  			resp, err := vtctld.ReloadSchemaKeyspace(ctx, tt.req)
  6979  			if tt.shouldErr {
  6980  				assert.Error(t, err)
  6981  				return
  6982  			}
  6983  
  6984  			require.NoError(t, err)
  6985  
  6986  			// ReloadSchemaKeyspace does each shard concurrently, so we sort
  6987  			// here to reduce flakes.
  6988  			sort.Sort(testutil.EventValueSorter(resp.Events))
  6989  			sort.Sort(testutil.EventValueSorter(tt.expected.Events))
  6990  
  6991  			testutil.AssertLogutilEventsMatch(t, tt.expected.Events, resp.Events)
  6992  		})
  6993  	}
  6994  }
  6995  
  6996  func TestReloadSchemaShard(t *testing.T) {
  6997  	t.Parallel()
  6998  
  6999  	tests := []struct {
  7000  		name      string
  7001  		tablets   []*topodatapb.Tablet
  7002  		tmc       testutil.TabletManagerClient
  7003  		req       *vtctldatapb.ReloadSchemaShardRequest
  7004  		expected  *vtctldatapb.ReloadSchemaShardResponse
  7005  		shouldErr bool
  7006  	}{
  7007  		{
  7008  			name: "ok",
  7009  			tablets: []*topodatapb.Tablet{
  7010  				{
  7011  					Keyspace: "ks1",
  7012  					Shard:    "-",
  7013  					Type:     topodatapb.TabletType_PRIMARY,
  7014  					Alias: &topodatapb.TabletAlias{
  7015  						Cell: "zone1",
  7016  						Uid:  100,
  7017  					},
  7018  				},
  7019  				{
  7020  					Keyspace: "ks1",
  7021  					Shard:    "-",
  7022  					Type:     topodatapb.TabletType_REPLICA,
  7023  					Alias: &topodatapb.TabletAlias{
  7024  						Cell: "zone2",
  7025  						Uid:  200,
  7026  					},
  7027  				},
  7028  				{
  7029  					Keyspace: "ks1",
  7030  					Shard:    "-",
  7031  					Type:     topodatapb.TabletType_REPLICA,
  7032  					Alias: &topodatapb.TabletAlias{
  7033  						Cell: "zone3",
  7034  						Uid:  300,
  7035  					},
  7036  				},
  7037  			},
  7038  			tmc: testutil.TabletManagerClient{
  7039  				ReloadSchemaResults: map[string]error{
  7040  					"zone1-0000000100": assert.AnError, // without IncludePrimary this is fine.
  7041  					"zone2-0000000200": nil,
  7042  					"zone3-0000000300": nil,
  7043  				},
  7044  			},
  7045  			req: &vtctldatapb.ReloadSchemaShardRequest{
  7046  				Keyspace: "ks1",
  7047  				Shard:    "-",
  7048  			},
  7049  			expected:  &vtctldatapb.ReloadSchemaShardResponse{},
  7050  			shouldErr: false,
  7051  		},
  7052  		{
  7053  			name: "shard not found",
  7054  			req: &vtctldatapb.ReloadSchemaShardRequest{
  7055  				Keyspace: "ks1",
  7056  				Shard:    "-",
  7057  			},
  7058  			expected: &vtctldatapb.ReloadSchemaShardResponse{
  7059  				Events: []*logutilpb.Event{
  7060  					{
  7061  						Value: `ReloadSchemaShard\(ks1/-\) failed to load tablet list, will not reload schema \(use vtctl ReloadSchemaShard to try again\):.*$`,
  7062  					},
  7063  				},
  7064  			},
  7065  		},
  7066  		{
  7067  			name: "include primary, with failure",
  7068  			tablets: []*topodatapb.Tablet{
  7069  				{
  7070  					Keyspace: "ks1",
  7071  					Shard:    "-",
  7072  					Type:     topodatapb.TabletType_PRIMARY,
  7073  					Alias: &topodatapb.TabletAlias{
  7074  						Cell: "zone1",
  7075  						Uid:  100,
  7076  					},
  7077  				},
  7078  				{
  7079  					Keyspace: "ks1",
  7080  					Shard:    "-",
  7081  					Type:     topodatapb.TabletType_REPLICA,
  7082  					Alias: &topodatapb.TabletAlias{
  7083  						Cell: "zone2",
  7084  						Uid:  200,
  7085  					},
  7086  				},
  7087  				{
  7088  					Keyspace: "ks1",
  7089  					Shard:    "-",
  7090  					Type:     topodatapb.TabletType_REPLICA,
  7091  					Alias: &topodatapb.TabletAlias{
  7092  						Cell: "zone3",
  7093  						Uid:  300,
  7094  					},
  7095  				},
  7096  			},
  7097  			tmc: testutil.TabletManagerClient{
  7098  				ReloadSchemaResults: map[string]error{
  7099  					"zone1-0000000100": assert.AnError, // with IncludePrimary this triggers an event
  7100  					"zone2-0000000200": nil,
  7101  					"zone3-0000000300": nil,
  7102  				},
  7103  			},
  7104  			req: &vtctldatapb.ReloadSchemaShardRequest{
  7105  				Keyspace:       "ks1",
  7106  				Shard:          "-",
  7107  				IncludePrimary: true,
  7108  			},
  7109  			expected: &vtctldatapb.ReloadSchemaShardResponse{
  7110  				Events: []*logutilpb.Event{
  7111  					{
  7112  						Value: "Failed to reload schema on replica tablet zone1-0000000100 in ks1/-.*$",
  7113  					},
  7114  				},
  7115  			},
  7116  			shouldErr: false,
  7117  		},
  7118  	}
  7119  
  7120  	ctx := context.Background()
  7121  	for _, tt := range tests {
  7122  		tt := tt
  7123  		t.Run(tt.name, func(t *testing.T) {
  7124  			t.Parallel()
  7125  
  7126  			ts := memorytopo.NewServer("zone1", "zone2", "zone3")
  7127  			testutil.AddTablets(ctx, t, ts, &testutil.AddTabletOptions{
  7128  				AlsoSetShardPrimary: true,
  7129  			}, tt.tablets...)
  7130  
  7131  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, &tt.tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  7132  				return NewVtctldServer(ts)
  7133  			})
  7134  			resp, err := vtctld.ReloadSchemaShard(ctx, tt.req)
  7135  			if tt.shouldErr {
  7136  				assert.Error(t, err)
  7137  				return
  7138  			}
  7139  
  7140  			require.NoError(t, err)
  7141  			testutil.AssertLogutilEventsMatch(t, tt.expected.Events, resp.Events)
  7142  		})
  7143  	}
  7144  }
  7145  
  7146  func TestRemoveBackup(t *testing.T) {
  7147  	ctx := context.Background()
  7148  	ts := memorytopo.NewServer()
  7149  	vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  7150  		return NewVtctldServer(ts)
  7151  	})
  7152  
  7153  	setup := func() {
  7154  		testutil.BackupStorage.Backups = map[string][]string{
  7155  			"testkeyspace/-": {"backup1", "backup2", "backup3"},
  7156  		}
  7157  	}
  7158  
  7159  	t.Run("ok", func(t *testing.T) {
  7160  		setup()
  7161  		_, err := vtctld.RemoveBackup(ctx, &vtctldatapb.RemoveBackupRequest{
  7162  			Keyspace: "testkeyspace",
  7163  			Shard:    "-",
  7164  			Name:     "backup2",
  7165  		})
  7166  
  7167  		assert.NoError(t, err)
  7168  
  7169  		resp, err := vtctld.GetBackups(ctx, &vtctldatapb.GetBackupsRequest{
  7170  			Keyspace: "testkeyspace",
  7171  			Shard:    "-",
  7172  		})
  7173  		require.NoError(t, err)
  7174  
  7175  		var backupNames []string
  7176  		for _, bi := range resp.Backups {
  7177  			backupNames = append(backupNames, bi.Name)
  7178  		}
  7179  		utils.MustMatch(t, []string{"backup1", "backup3"}, backupNames, "expected \"backup2\" to be removed")
  7180  	})
  7181  
  7182  	t.Run("no bucket found", func(t *testing.T) {
  7183  		setup()
  7184  		_, err := vtctld.RemoveBackup(ctx, &vtctldatapb.RemoveBackupRequest{
  7185  			Keyspace: "notfound",
  7186  			Shard:    "-",
  7187  			Name:     "somebackup",
  7188  		})
  7189  		assert.Error(t, err)
  7190  	})
  7191  
  7192  	t.Run("no backup found in bucket", func(t *testing.T) {
  7193  		setup()
  7194  		_, err := vtctld.RemoveBackup(ctx, &vtctldatapb.RemoveBackupRequest{
  7195  			Keyspace: "testkeyspace",
  7196  			Shard:    "-",
  7197  			Name:     "notfound",
  7198  		})
  7199  		assert.Error(t, err)
  7200  	})
  7201  }
  7202  
  7203  func TestRemoveKeyspaceCell(t *testing.T) {
  7204  	t.Parallel()
  7205  
  7206  	tests := []struct {
  7207  		name                    string
  7208  		keyspace                *vtctldatapb.Keyspace
  7209  		shards                  []*vtctldatapb.Shard
  7210  		topoError               error
  7211  		topoIsLocked            bool
  7212  		srvKeyspaceDoesNotExist bool
  7213  		req                     *vtctldatapb.RemoveKeyspaceCellRequest
  7214  		expected                *vtctldatapb.RemoveKeyspaceCellResponse
  7215  		shouldErr               bool
  7216  	}{
  7217  		{
  7218  			name:     "success",
  7219  			keyspace: nil,
  7220  			shards: []*vtctldatapb.Shard{
  7221  				{
  7222  					Keyspace: "testkeyspace",
  7223  					Name:     "-",
  7224  				},
  7225  			},
  7226  			topoError:               nil,
  7227  			topoIsLocked:            false,
  7228  			srvKeyspaceDoesNotExist: false,
  7229  			req: &vtctldatapb.RemoveKeyspaceCellRequest{
  7230  				Keyspace: "testkeyspace",
  7231  				Cell:     "zone1",
  7232  			},
  7233  			expected:  &vtctldatapb.RemoveKeyspaceCellResponse{},
  7234  			shouldErr: false,
  7235  		},
  7236  		{
  7237  			name: "success/empty keyspace",
  7238  			keyspace: &vtctldatapb.Keyspace{
  7239  				Name:     "testkeyspace",
  7240  				Keyspace: &topodatapb.Keyspace{},
  7241  			},
  7242  			shards:                  nil,
  7243  			topoError:               nil,
  7244  			topoIsLocked:            false,
  7245  			srvKeyspaceDoesNotExist: false,
  7246  			req: &vtctldatapb.RemoveKeyspaceCellRequest{
  7247  				Keyspace: "testkeyspace",
  7248  				Cell:     "zone1",
  7249  			},
  7250  			expected:  &vtctldatapb.RemoveKeyspaceCellResponse{},
  7251  			shouldErr: false,
  7252  		},
  7253  		{
  7254  			name: "keyspace not found",
  7255  			keyspace: &vtctldatapb.Keyspace{
  7256  				Name:     "otherkeyspace",
  7257  				Keyspace: &topodatapb.Keyspace{},
  7258  			},
  7259  			shards:                  nil,
  7260  			topoError:               nil,
  7261  			topoIsLocked:            false,
  7262  			srvKeyspaceDoesNotExist: false,
  7263  			req: &vtctldatapb.RemoveKeyspaceCellRequest{
  7264  				Keyspace: "testkeyspace",
  7265  				Cell:     "zone1",
  7266  			},
  7267  			expected:  nil,
  7268  			shouldErr: true,
  7269  		},
  7270  		{
  7271  			name:     "topo is down",
  7272  			keyspace: nil,
  7273  			shards: []*vtctldatapb.Shard{
  7274  				{
  7275  					Keyspace: "testkeyspace",
  7276  					Name:     "-",
  7277  				},
  7278  			},
  7279  			topoError:               assert.AnError,
  7280  			topoIsLocked:            false,
  7281  			srvKeyspaceDoesNotExist: false,
  7282  			req: &vtctldatapb.RemoveKeyspaceCellRequest{
  7283  				Keyspace: "testkeyspace",
  7284  				Cell:     "zone1",
  7285  			},
  7286  			expected:  nil,
  7287  			shouldErr: true,
  7288  		},
  7289  		{
  7290  			name:     "topo is locked",
  7291  			keyspace: nil,
  7292  			shards: []*vtctldatapb.Shard{
  7293  				{
  7294  					Keyspace: "testkeyspace",
  7295  					Name:     "-",
  7296  				},
  7297  			},
  7298  			topoError:               nil,
  7299  			topoIsLocked:            true,
  7300  			srvKeyspaceDoesNotExist: false,
  7301  			req: &vtctldatapb.RemoveKeyspaceCellRequest{
  7302  				Keyspace: "testkeyspace",
  7303  				Cell:     "zone1",
  7304  			},
  7305  			expected:  nil,
  7306  			shouldErr: true,
  7307  		},
  7308  		{
  7309  			name:     "srvkeyspace already deleted",
  7310  			keyspace: nil,
  7311  			shards: []*vtctldatapb.Shard{
  7312  				{
  7313  					Keyspace: "testkeyspace",
  7314  					Name:     "-",
  7315  				},
  7316  			},
  7317  			topoError:               nil,
  7318  			topoIsLocked:            false,
  7319  			srvKeyspaceDoesNotExist: true,
  7320  			req: &vtctldatapb.RemoveKeyspaceCellRequest{
  7321  				Keyspace: "testkeyspace",
  7322  				Cell:     "zone1",
  7323  			},
  7324  			expected:  nil,
  7325  			shouldErr: true,
  7326  		},
  7327  	}
  7328  
  7329  	for _, tt := range tests {
  7330  		tt := tt
  7331  
  7332  		t.Run(tt.name, func(t *testing.T) {
  7333  			t.Parallel()
  7334  
  7335  			cells := []string{"zone1", "zone2", "zone3"}
  7336  
  7337  			ctx := context.Background()
  7338  			ts, topofactory := memorytopo.NewServerAndFactory(cells...)
  7339  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  7340  				return NewVtctldServer(ts)
  7341  			})
  7342  
  7343  			// Setup topo
  7344  			if tt.keyspace != nil {
  7345  				testutil.AddKeyspace(ctx, t, ts, tt.keyspace)
  7346  			}
  7347  
  7348  			testutil.AddShards(ctx, t, ts, tt.shards...)
  7349  
  7350  			// For certain tests, we don't actually create the SrvKeyspace
  7351  			// object.
  7352  			if !tt.srvKeyspaceDoesNotExist {
  7353  				updateSrvKeyspace := func(keyspace string) {
  7354  					for _, cell := range cells {
  7355  						err := ts.UpdateSrvKeyspace(ctx, cell, keyspace, &topodatapb.SrvKeyspace{})
  7356  						require.NoError(t, err, "could not create empty SrvKeyspace for keyspace %s in cell %s", tt.req.Keyspace, cell)
  7357  					}
  7358  				}
  7359  
  7360  				updateSrvKeyspace(tt.req.Keyspace)
  7361  				if tt.keyspace != nil {
  7362  					updateSrvKeyspace(tt.keyspace.Name)
  7363  				}
  7364  			}
  7365  
  7366  			// Set errors and locks
  7367  			if tt.topoError != nil {
  7368  				topofactory.SetError(tt.topoError)
  7369  			}
  7370  
  7371  			if tt.topoIsLocked {
  7372  				lctx, unlock, err := ts.LockKeyspace(ctx, tt.req.Keyspace, "testing locked keyspace")
  7373  				require.NoError(t, err, "cannot lock keyspace %s", tt.req.Keyspace)
  7374  				defer unlock(&err)
  7375  
  7376  				ctx = lctx
  7377  			}
  7378  
  7379  			resp, err := vtctld.RemoveKeyspaceCell(ctx, tt.req)
  7380  			if tt.shouldErr {
  7381  				assert.Error(t, err)
  7382  				return
  7383  			}
  7384  
  7385  			assert.NoError(t, err)
  7386  			utils.MustMatch(t, tt.expected, resp)
  7387  		})
  7388  	}
  7389  }
  7390  
  7391  func TestRemoveShardCell(t *testing.T) {
  7392  	t.Parallel()
  7393  
  7394  	tests := []struct {
  7395  		name              string
  7396  		servingCells      []string
  7397  		shards            []*vtctldatapb.Shard
  7398  		replicationGraphs []*topo.ShardReplicationInfo
  7399  		topoError         error
  7400  		topoIsLocked      bool
  7401  		req               *vtctldatapb.RemoveShardCellRequest
  7402  		expected          *vtctldatapb.RemoveShardCellResponse
  7403  		shouldErr         bool
  7404  	}{
  7405  		{
  7406  			name: "success",
  7407  			shards: []*vtctldatapb.Shard{
  7408  				{
  7409  					Keyspace: "testkeyspace",
  7410  					Name:     "-",
  7411  				},
  7412  			},
  7413  			replicationGraphs: []*topo.ShardReplicationInfo{
  7414  				topo.NewShardReplicationInfo(&topodatapb.ShardReplication{
  7415  					Nodes: []*topodatapb.ShardReplication_Node{
  7416  						{
  7417  							TabletAlias: &topodatapb.TabletAlias{
  7418  								Cell: "zone1",
  7419  								Uid:  100,
  7420  							},
  7421  						},
  7422  					},
  7423  				}, "zone1", "testkeyspace", "-"),
  7424  				topo.NewShardReplicationInfo(&topodatapb.ShardReplication{
  7425  					Nodes: []*topodatapb.ShardReplication_Node{
  7426  						{
  7427  							TabletAlias: &topodatapb.TabletAlias{
  7428  								Cell: "zone2",
  7429  								Uid:  200,
  7430  							},
  7431  						},
  7432  					},
  7433  				}, "zone2", "testkeyspace", "-"),
  7434  				topo.NewShardReplicationInfo(&topodatapb.ShardReplication{
  7435  					Nodes: []*topodatapb.ShardReplication_Node{
  7436  						{
  7437  							TabletAlias: &topodatapb.TabletAlias{
  7438  								Cell: "zone3",
  7439  								Uid:  300,
  7440  							},
  7441  						},
  7442  					},
  7443  				}, "zone3", "testkeyspace", "-"),
  7444  			},
  7445  			req: &vtctldatapb.RemoveShardCellRequest{
  7446  				Keyspace:  "testkeyspace",
  7447  				ShardName: "-",
  7448  				Cell:      "zone2",
  7449  				Recursive: true,
  7450  			},
  7451  			expected:  &vtctldatapb.RemoveShardCellResponse{},
  7452  			shouldErr: false,
  7453  		},
  7454  		{
  7455  			name: "success/no tablets",
  7456  			shards: []*vtctldatapb.Shard{
  7457  				{
  7458  					Keyspace: "testkeyspace",
  7459  					Name:     "-",
  7460  				},
  7461  			},
  7462  			req: &vtctldatapb.RemoveShardCellRequest{
  7463  				Keyspace:  "testkeyspace",
  7464  				ShardName: "-",
  7465  				Cell:      "zone2",
  7466  			},
  7467  			expected:  &vtctldatapb.RemoveShardCellResponse{},
  7468  			shouldErr: false,
  7469  		},
  7470  		{
  7471  			name:              "nonexistent shard",
  7472  			shards:            nil,
  7473  			replicationGraphs: nil,
  7474  			req: &vtctldatapb.RemoveShardCellRequest{
  7475  				Keyspace:  "testkeyspace",
  7476  				ShardName: "-",
  7477  				Cell:      "zone2",
  7478  			},
  7479  			expected:  nil,
  7480  			shouldErr: true,
  7481  		},
  7482  		{
  7483  			name: "cell does not exist",
  7484  			shards: []*vtctldatapb.Shard{
  7485  				{
  7486  					Keyspace: "testkeyspace",
  7487  					Name:     "-",
  7488  				},
  7489  			},
  7490  			req: &vtctldatapb.RemoveShardCellRequest{
  7491  				Keyspace:  "testkeyspace",
  7492  				ShardName: "-",
  7493  				Cell:      "fourthzone",
  7494  			},
  7495  			expected:  nil,
  7496  			shouldErr: true,
  7497  		},
  7498  		{
  7499  			name: "cell not in serving list",
  7500  			shards: []*vtctldatapb.Shard{
  7501  				{
  7502  					Keyspace: "testkeyspace",
  7503  					Name:     "-",
  7504  				},
  7505  			},
  7506  			servingCells:      []string{"zone1"},
  7507  			replicationGraphs: nil,
  7508  			req: &vtctldatapb.RemoveShardCellRequest{
  7509  				Keyspace:  "testkeyspace",
  7510  				ShardName: "-",
  7511  				Cell:      "zone2",
  7512  			},
  7513  			expected:  nil,
  7514  			shouldErr: true,
  7515  		},
  7516  		{
  7517  			name: "tablets/non-recursive",
  7518  			shards: []*vtctldatapb.Shard{
  7519  				{
  7520  					Keyspace: "testkeyspace",
  7521  					Name:     "-",
  7522  				},
  7523  			},
  7524  			replicationGraphs: []*topo.ShardReplicationInfo{
  7525  				topo.NewShardReplicationInfo(&topodatapb.ShardReplication{
  7526  					Nodes: []*topodatapb.ShardReplication_Node{
  7527  						{
  7528  							TabletAlias: &topodatapb.TabletAlias{
  7529  								Cell: "zone1",
  7530  								Uid:  100,
  7531  							},
  7532  						},
  7533  					},
  7534  				}, "zone1", "testkeyspace", "-"),
  7535  				topo.NewShardReplicationInfo(&topodatapb.ShardReplication{
  7536  					Nodes: []*topodatapb.ShardReplication_Node{
  7537  						{
  7538  							TabletAlias: &topodatapb.TabletAlias{
  7539  								Cell: "zone2",
  7540  								Uid:  200,
  7541  							},
  7542  						},
  7543  					},
  7544  				}, "zone2", "testkeyspace", "-"),
  7545  				topo.NewShardReplicationInfo(&topodatapb.ShardReplication{
  7546  					Nodes: []*topodatapb.ShardReplication_Node{
  7547  						{
  7548  							TabletAlias: &topodatapb.TabletAlias{
  7549  								Cell: "zone3",
  7550  								Uid:  300,
  7551  							},
  7552  						},
  7553  					},
  7554  				}, "zone3", "testkeyspace", "-"),
  7555  			},
  7556  			req: &vtctldatapb.RemoveShardCellRequest{
  7557  				Keyspace:  "testkeyspace",
  7558  				ShardName: "-",
  7559  				Cell:      "zone2",
  7560  				Recursive: false, // non-recursive + replication graph = failure
  7561  			},
  7562  			expected:  nil,
  7563  			shouldErr: true,
  7564  		},
  7565  		{
  7566  			name: "topo server down",
  7567  			shards: []*vtctldatapb.Shard{
  7568  				{
  7569  					Keyspace: "testkeyspace",
  7570  					Name:     "-",
  7571  				},
  7572  			},
  7573  			replicationGraphs: nil,
  7574  			req: &vtctldatapb.RemoveShardCellRequest{
  7575  				Keyspace:  "testkeyspace",
  7576  				ShardName: "-",
  7577  				Cell:      "zone2",
  7578  			},
  7579  			topoError:    assert.AnError,
  7580  			topoIsLocked: false,
  7581  			expected:     nil,
  7582  			shouldErr:    true,
  7583  		},
  7584  		// Not sure how to set up this test case.
  7585  		// {
  7586  		// 	name: "topo server down for replication check/no force",
  7587  		// },
  7588  		// Not sure how to set up this test case.
  7589  		// {
  7590  		// 	name: "topo server down for replication check/force",
  7591  		// },
  7592  		{
  7593  			name: "cannot lock keyspace",
  7594  			shards: []*vtctldatapb.Shard{
  7595  				{
  7596  					Keyspace: "testkeyspace",
  7597  					Name:     "-",
  7598  				},
  7599  			},
  7600  			replicationGraphs: nil,
  7601  			req: &vtctldatapb.RemoveShardCellRequest{
  7602  				Keyspace:  "testkeyspace",
  7603  				ShardName: "-",
  7604  				Cell:      "zone2",
  7605  			},
  7606  			topoError:    nil,
  7607  			topoIsLocked: true,
  7608  			expected:     nil,
  7609  			shouldErr:    true,
  7610  		},
  7611  		// Not sure how to set up this test case.
  7612  		// {
  7613  		// 	name: "cannot delete srvkeyspace partition",
  7614  		// },
  7615  	}
  7616  
  7617  	for _, tt := range tests {
  7618  		tt := tt
  7619  
  7620  		t.Run(tt.name, func(t *testing.T) {
  7621  			t.Parallel()
  7622  
  7623  			cells := []string{"zone1", "zone2", "zone3"}
  7624  
  7625  			ctx := context.Background()
  7626  			ts, topofactory := memorytopo.NewServerAndFactory(cells...)
  7627  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  7628  				return NewVtctldServer(ts)
  7629  			})
  7630  
  7631  			// Setup shard topos and replication graphs.
  7632  			testutil.AddShards(ctx, t, ts, tt.shards...)
  7633  			testutil.SetupReplicationGraphs(ctx, t, ts, tt.replicationGraphs...)
  7634  
  7635  			// Set up srvkeyspace partitions; a little gross.
  7636  			servingCells := tt.servingCells
  7637  			if servingCells == nil { // we expect an explicit empty list to have a shard with no serving cells
  7638  				servingCells = cells
  7639  			}
  7640  
  7641  			for _, shard := range tt.shards {
  7642  				lctx, unlock, lerr := ts.LockKeyspace(ctx, shard.Keyspace, "initializing serving graph for test")
  7643  				require.NoError(t, lerr, "cannot lock keyspace %s to initialize serving graph", shard.Keyspace)
  7644  
  7645  				for _, cell := range servingCells {
  7646  
  7647  					err := ts.UpdateSrvKeyspace(lctx, cell, shard.Keyspace, &topodatapb.SrvKeyspace{
  7648  						Partitions: []*topodatapb.SrvKeyspace_KeyspacePartition{
  7649  							{
  7650  								ServedType: topodatapb.TabletType_REPLICA,
  7651  								ShardReferences: []*topodatapb.ShardReference{
  7652  									{
  7653  										Name: shard.Name,
  7654  									},
  7655  								},
  7656  							},
  7657  						},
  7658  					})
  7659  					require.NoError(t, err, "cannot update srvkeyspace for %s/%s in cell %v", shard.Keyspace, shard.Name, cell)
  7660  				}
  7661  
  7662  				unlock(&lerr)
  7663  			}
  7664  
  7665  			// Set errors and locks.
  7666  			if tt.topoError != nil {
  7667  				topofactory.SetError(tt.topoError)
  7668  			}
  7669  
  7670  			if tt.topoIsLocked {
  7671  				lctx, unlock, err := ts.LockKeyspace(ctx, tt.req.Keyspace, "testing locked keyspace")
  7672  				require.NoError(t, err, "cannot lock keyspace %s", tt.req.Keyspace)
  7673  				defer unlock(&err)
  7674  
  7675  				// Need to use the lock ctx in the RPC call so we fail when
  7676  				// attempting to lock the keyspace rather than waiting forever
  7677  				// for the lock. Explicitly setting a deadline would be another
  7678  				// way to achieve this.
  7679  				ctx = lctx
  7680  			}
  7681  
  7682  			// Make the RPC and assert things about it.
  7683  			resp, err := vtctld.RemoveShardCell(ctx, tt.req)
  7684  			if tt.shouldErr {
  7685  				assert.Error(t, err)
  7686  				return
  7687  			}
  7688  
  7689  			assert.NoError(t, err)
  7690  			utils.MustMatch(t, tt.expected, resp)
  7691  		})
  7692  	}
  7693  }
  7694  
  7695  func TestReparentTablet(t *testing.T) {
  7696  	t.Parallel()
  7697  
  7698  	tests := []struct {
  7699  		name      string
  7700  		tmc       tmclient.TabletManagerClient
  7701  		tablets   []*topodatapb.Tablet
  7702  		shards    []*vtctldatapb.Shard
  7703  		topoErr   error
  7704  		req       *vtctldatapb.ReparentTabletRequest
  7705  		expected  *vtctldatapb.ReparentTabletResponse
  7706  		shouldErr bool
  7707  	}{
  7708  		{
  7709  			name: "success",
  7710  			tmc: &testutil.TabletManagerClient{
  7711  				SetReplicationSourceResults: map[string]error{
  7712  					"zone1-0000000100": nil,
  7713  				},
  7714  			},
  7715  			tablets: []*topodatapb.Tablet{
  7716  				{
  7717  					Alias: &topodatapb.TabletAlias{
  7718  						Cell: "zone1",
  7719  						Uid:  100,
  7720  					},
  7721  					Type:     topodatapb.TabletType_REPLICA,
  7722  					Keyspace: "testkeyspace",
  7723  					Shard:    "-",
  7724  				},
  7725  				{
  7726  					Alias: &topodatapb.TabletAlias{
  7727  						Cell: "zone2",
  7728  						Uid:  200,
  7729  					},
  7730  					Type:     topodatapb.TabletType_PRIMARY,
  7731  					Keyspace: "testkeyspace",
  7732  					Shard:    "-",
  7733  					PrimaryTermStartTime: &vttime.Time{
  7734  						Seconds: 1000,
  7735  					},
  7736  				},
  7737  			},
  7738  			shards: []*vtctldatapb.Shard{
  7739  				{
  7740  					Keyspace: "testkeyspace",
  7741  					Name:     "-",
  7742  					Shard: &topodatapb.Shard{
  7743  						PrimaryAlias: &topodatapb.TabletAlias{
  7744  							Cell: "zone2",
  7745  							Uid:  200,
  7746  						},
  7747  						PrimaryTermStartTime: &vttime.Time{
  7748  							Seconds: 1000,
  7749  						},
  7750  						IsPrimaryServing: true,
  7751  					},
  7752  				},
  7753  			},
  7754  			req: &vtctldatapb.ReparentTabletRequest{
  7755  				Tablet: &topodatapb.TabletAlias{
  7756  					Cell: "zone1",
  7757  					Uid:  100,
  7758  				},
  7759  			},
  7760  			expected: &vtctldatapb.ReparentTabletResponse{
  7761  				Keyspace: "testkeyspace",
  7762  				Shard:    "-",
  7763  				Primary: &topodatapb.TabletAlias{
  7764  					Cell: "zone2",
  7765  					Uid:  200,
  7766  				},
  7767  			},
  7768  			shouldErr: false,
  7769  		},
  7770  		{
  7771  			name: "tablet is nil",
  7772  			tablets: []*topodatapb.Tablet{
  7773  				{
  7774  					Alias: &topodatapb.TabletAlias{
  7775  						Cell: "zone1",
  7776  						Uid:  100,
  7777  					},
  7778  					Type:     topodatapb.TabletType_REPLICA,
  7779  					Keyspace: "testkeyspace",
  7780  					Shard:    "-",
  7781  				},
  7782  				{
  7783  					Alias: &topodatapb.TabletAlias{
  7784  						Cell: "zone2",
  7785  						Uid:  200,
  7786  					},
  7787  					Type:     topodatapb.TabletType_PRIMARY,
  7788  					Keyspace: "testkeyspace",
  7789  					Shard:    "-",
  7790  					PrimaryTermStartTime: &vttime.Time{
  7791  						Seconds: 1000,
  7792  					},
  7793  				},
  7794  			},
  7795  			shards: []*vtctldatapb.Shard{
  7796  				{
  7797  					Keyspace: "testkeyspace",
  7798  					Name:     "-",
  7799  					Shard: &topodatapb.Shard{
  7800  						PrimaryAlias: &topodatapb.TabletAlias{
  7801  							Cell: "zone2",
  7802  							Uid:  200,
  7803  						},
  7804  						PrimaryTermStartTime: &vttime.Time{
  7805  							Seconds: 1000,
  7806  						},
  7807  						IsPrimaryServing: true,
  7808  					},
  7809  				},
  7810  			},
  7811  			req: &vtctldatapb.ReparentTabletRequest{
  7812  				Tablet: nil,
  7813  			},
  7814  			expected:  nil,
  7815  			shouldErr: true,
  7816  		},
  7817  		{
  7818  			name: "tablet not in topo",
  7819  			tablets: []*topodatapb.Tablet{
  7820  				{
  7821  					Alias: &topodatapb.TabletAlias{
  7822  						Cell: "zone2",
  7823  						Uid:  200,
  7824  					},
  7825  					Type:     topodatapb.TabletType_PRIMARY,
  7826  					Keyspace: "testkeyspace",
  7827  					Shard:    "-",
  7828  					PrimaryTermStartTime: &vttime.Time{
  7829  						Seconds: 1000,
  7830  					},
  7831  				},
  7832  			},
  7833  			shards: []*vtctldatapb.Shard{
  7834  				{
  7835  					Keyspace: "testkeyspace",
  7836  					Name:     "-",
  7837  					Shard: &topodatapb.Shard{
  7838  						PrimaryAlias: &topodatapb.TabletAlias{
  7839  							Cell: "zone2",
  7840  							Uid:  200,
  7841  						},
  7842  						PrimaryTermStartTime: &vttime.Time{
  7843  							Seconds: 1000,
  7844  						},
  7845  						IsPrimaryServing: true,
  7846  					},
  7847  				},
  7848  			},
  7849  			req: &vtctldatapb.ReparentTabletRequest{
  7850  				Tablet: &topodatapb.TabletAlias{
  7851  					Cell: "zone1",
  7852  					Uid:  100,
  7853  				},
  7854  			},
  7855  			expected:  nil,
  7856  			shouldErr: true,
  7857  		},
  7858  		{
  7859  			name: "shard not in topo",
  7860  			tablets: []*topodatapb.Tablet{
  7861  				{
  7862  					Alias: &topodatapb.TabletAlias{
  7863  						Cell: "zone1",
  7864  						Uid:  100,
  7865  					},
  7866  					Type:     topodatapb.TabletType_REPLICA,
  7867  					Keyspace: "testkeyspace",
  7868  					Shard:    "-",
  7869  				},
  7870  				{
  7871  					Alias: &topodatapb.TabletAlias{
  7872  						Cell: "zone2",
  7873  						Uid:  200,
  7874  					},
  7875  					Type:     topodatapb.TabletType_PRIMARY,
  7876  					Keyspace: "testkeyspace",
  7877  					Shard:    "-",
  7878  					PrimaryTermStartTime: &vttime.Time{
  7879  						Seconds: 1000,
  7880  					},
  7881  				},
  7882  			},
  7883  			shards: nil,
  7884  			req: &vtctldatapb.ReparentTabletRequest{
  7885  				Tablet: &topodatapb.TabletAlias{
  7886  					Cell: "zone1",
  7887  					Uid:  100,
  7888  				},
  7889  			},
  7890  			expected:  nil,
  7891  			shouldErr: true,
  7892  		},
  7893  		{
  7894  			name: "shard has no primary",
  7895  			tablets: []*topodatapb.Tablet{
  7896  				{
  7897  					Alias: &topodatapb.TabletAlias{
  7898  						Cell: "zone1",
  7899  						Uid:  100,
  7900  					},
  7901  					Type:     topodatapb.TabletType_REPLICA,
  7902  					Keyspace: "testkeyspace",
  7903  					Shard:    "-",
  7904  				},
  7905  			},
  7906  			shards: []*vtctldatapb.Shard{
  7907  				{
  7908  					Keyspace: "testkeyspace",
  7909  					Name:     "-",
  7910  					Shard: &topodatapb.Shard{
  7911  						IsPrimaryServing: false,
  7912  					},
  7913  				},
  7914  			},
  7915  			req: &vtctldatapb.ReparentTabletRequest{
  7916  				Tablet: &topodatapb.TabletAlias{
  7917  					Cell: "zone1",
  7918  					Uid:  100,
  7919  				},
  7920  			},
  7921  			expected:  nil,
  7922  			shouldErr: true,
  7923  		},
  7924  		{
  7925  			name: "shard primary not in topo",
  7926  			tablets: []*topodatapb.Tablet{
  7927  				{
  7928  					Alias: &topodatapb.TabletAlias{
  7929  						Cell: "zone1",
  7930  						Uid:  100,
  7931  					},
  7932  					Type:     topodatapb.TabletType_REPLICA,
  7933  					Keyspace: "testkeyspace",
  7934  					Shard:    "-",
  7935  				},
  7936  				{
  7937  					Alias: &topodatapb.TabletAlias{
  7938  						Cell: "zone2",
  7939  						Uid:  200,
  7940  					},
  7941  					Type:     topodatapb.TabletType_PRIMARY,
  7942  					Keyspace: "testkeyspace",
  7943  					Shard:    "-",
  7944  					PrimaryTermStartTime: &vttime.Time{
  7945  						Seconds: 1000,
  7946  					},
  7947  				},
  7948  			},
  7949  			shards: []*vtctldatapb.Shard{
  7950  				{
  7951  					Keyspace: "testkeyspace",
  7952  					Name:     "-",
  7953  					Shard: &topodatapb.Shard{
  7954  						PrimaryAlias: &topodatapb.TabletAlias{
  7955  							Cell: "zone3",
  7956  							Uid:  300,
  7957  						},
  7958  						PrimaryTermStartTime: &vttime.Time{
  7959  							Seconds: 1010,
  7960  						},
  7961  						IsPrimaryServing: true,
  7962  					},
  7963  				},
  7964  			},
  7965  			req: &vtctldatapb.ReparentTabletRequest{
  7966  				Tablet: &topodatapb.TabletAlias{
  7967  					Cell: "zone1",
  7968  					Uid:  100,
  7969  				},
  7970  			},
  7971  			expected:  nil,
  7972  			shouldErr: true,
  7973  		},
  7974  		{
  7975  			name: "shard primary is not type PRIMARY",
  7976  			tablets: []*topodatapb.Tablet{
  7977  				{
  7978  					Alias: &topodatapb.TabletAlias{
  7979  						Cell: "zone1",
  7980  						Uid:  100,
  7981  					},
  7982  					Type:     topodatapb.TabletType_REPLICA,
  7983  					Keyspace: "testkeyspace",
  7984  					Shard:    "-",
  7985  				},
  7986  				{
  7987  					Alias: &topodatapb.TabletAlias{
  7988  						Cell: "zone2",
  7989  						Uid:  200,
  7990  					},
  7991  					Type:     topodatapb.TabletType_REPLICA,
  7992  					Keyspace: "testkeyspace",
  7993  					Shard:    "-",
  7994  				},
  7995  			},
  7996  			shards: []*vtctldatapb.Shard{
  7997  				{
  7998  					Keyspace: "testkeyspace",
  7999  					Name:     "-",
  8000  					Shard: &topodatapb.Shard{
  8001  						PrimaryAlias: &topodatapb.TabletAlias{
  8002  							Cell: "zone2",
  8003  							Uid:  200,
  8004  						},
  8005  						PrimaryTermStartTime: &vttime.Time{
  8006  							Seconds: 1010,
  8007  						},
  8008  						IsPrimaryServing: true,
  8009  					},
  8010  				},
  8011  			},
  8012  			req: &vtctldatapb.ReparentTabletRequest{
  8013  				Tablet: &topodatapb.TabletAlias{
  8014  					Cell: "zone1",
  8015  					Uid:  100,
  8016  				},
  8017  			},
  8018  			expected:  nil,
  8019  			shouldErr: true,
  8020  		},
  8021  		{
  8022  			name: "shard primary is not actually in shard",
  8023  			tablets: []*topodatapb.Tablet{
  8024  				{
  8025  					Alias: &topodatapb.TabletAlias{
  8026  						Cell: "zone1",
  8027  						Uid:  100,
  8028  					},
  8029  					Type:     topodatapb.TabletType_REPLICA,
  8030  					Keyspace: "testkeyspace",
  8031  					Shard:    "-",
  8032  				},
  8033  				{
  8034  					Alias: &topodatapb.TabletAlias{
  8035  						Cell: "zone2",
  8036  						Uid:  200,
  8037  					},
  8038  					Type:     topodatapb.TabletType_PRIMARY,
  8039  					Keyspace: "otherkeyspace",
  8040  					Shard:    "-",
  8041  					PrimaryTermStartTime: &vttime.Time{
  8042  						Seconds: 1000,
  8043  					},
  8044  				},
  8045  			},
  8046  			shards: []*vtctldatapb.Shard{
  8047  				{
  8048  					Keyspace: "testkeyspace",
  8049  					Name:     "-",
  8050  					Shard: &topodatapb.Shard{
  8051  						PrimaryAlias: &topodatapb.TabletAlias{
  8052  							Cell: "zone2",
  8053  							Uid:  200,
  8054  						},
  8055  						PrimaryTermStartTime: &vttime.Time{
  8056  							Seconds: 1010,
  8057  						},
  8058  						IsPrimaryServing: true,
  8059  					},
  8060  				},
  8061  			},
  8062  			req: &vtctldatapb.ReparentTabletRequest{
  8063  				Tablet: &topodatapb.TabletAlias{
  8064  					Cell: "zone1",
  8065  					Uid:  100,
  8066  				},
  8067  			},
  8068  			expected:  nil,
  8069  			shouldErr: true,
  8070  		},
  8071  		{
  8072  			name: "requested tablet is shard primary",
  8073  			tablets: []*topodatapb.Tablet{
  8074  				{
  8075  					Alias: &topodatapb.TabletAlias{
  8076  						Cell: "zone1",
  8077  						Uid:  100,
  8078  					},
  8079  					Type:     topodatapb.TabletType_PRIMARY,
  8080  					Keyspace: "testkeyspace",
  8081  					Shard:    "-",
  8082  				},
  8083  			},
  8084  			shards: []*vtctldatapb.Shard{
  8085  				{
  8086  					Keyspace: "testkeyspace",
  8087  					Name:     "-",
  8088  					Shard: &topodatapb.Shard{
  8089  						PrimaryAlias: &topodatapb.TabletAlias{
  8090  							Cell: "zone1",
  8091  							Uid:  100,
  8092  						},
  8093  						PrimaryTermStartTime: &vttime.Time{
  8094  							Seconds: 1010,
  8095  						},
  8096  						IsPrimaryServing: true,
  8097  					},
  8098  				},
  8099  			},
  8100  			req: &vtctldatapb.ReparentTabletRequest{
  8101  				Tablet: &topodatapb.TabletAlias{
  8102  					Cell: "zone1",
  8103  					Uid:  100,
  8104  				},
  8105  			},
  8106  			expected:  nil,
  8107  			shouldErr: true,
  8108  		},
  8109  		{
  8110  			name: "tmc.SetReplicationSource failure",
  8111  			tmc: &testutil.TabletManagerClient{
  8112  				SetReplicationSourceResults: map[string]error{
  8113  					"zone1-0000000100": assert.AnError,
  8114  				},
  8115  			},
  8116  			tablets: []*topodatapb.Tablet{
  8117  				{
  8118  					Alias: &topodatapb.TabletAlias{
  8119  						Cell: "zone1",
  8120  						Uid:  100,
  8121  					},
  8122  					Type:     topodatapb.TabletType_REPLICA,
  8123  					Keyspace: "testkeyspace",
  8124  					Shard:    "-",
  8125  				},
  8126  				{
  8127  					Alias: &topodatapb.TabletAlias{
  8128  						Cell: "zone2",
  8129  						Uid:  200,
  8130  					},
  8131  					Type:     topodatapb.TabletType_PRIMARY,
  8132  					Keyspace: "testkeyspace",
  8133  					Shard:    "-",
  8134  					PrimaryTermStartTime: &vttime.Time{
  8135  						Seconds: 1000,
  8136  					},
  8137  				},
  8138  			},
  8139  			shards: []*vtctldatapb.Shard{
  8140  				{
  8141  					Keyspace: "testkeyspace",
  8142  					Name:     "-",
  8143  					Shard: &topodatapb.Shard{
  8144  						PrimaryAlias: &topodatapb.TabletAlias{
  8145  							Cell: "zone2",
  8146  							Uid:  200,
  8147  						},
  8148  						PrimaryTermStartTime: &vttime.Time{
  8149  							Seconds: 1000,
  8150  						},
  8151  						IsPrimaryServing: true,
  8152  					},
  8153  				},
  8154  			},
  8155  			req: &vtctldatapb.ReparentTabletRequest{
  8156  				Tablet: &topodatapb.TabletAlias{
  8157  					Cell: "zone1",
  8158  					Uid:  100,
  8159  				},
  8160  			},
  8161  			expected:  nil,
  8162  			shouldErr: true,
  8163  		},
  8164  		{
  8165  			name: "topo is down",
  8166  			tmc: &testutil.TabletManagerClient{
  8167  				SetReplicationSourceResults: map[string]error{
  8168  					"zone1-0000000100": nil,
  8169  				},
  8170  			},
  8171  			tablets: []*topodatapb.Tablet{
  8172  				{
  8173  					Alias: &topodatapb.TabletAlias{
  8174  						Cell: "zone1",
  8175  						Uid:  100,
  8176  					},
  8177  					Type:     topodatapb.TabletType_REPLICA,
  8178  					Keyspace: "testkeyspace",
  8179  					Shard:    "-",
  8180  				},
  8181  				{
  8182  					Alias: &topodatapb.TabletAlias{
  8183  						Cell: "zone2",
  8184  						Uid:  200,
  8185  					},
  8186  					Type:     topodatapb.TabletType_PRIMARY,
  8187  					Keyspace: "testkeyspace",
  8188  					Shard:    "-",
  8189  					PrimaryTermStartTime: &vttime.Time{
  8190  						Seconds: 1000,
  8191  					},
  8192  				},
  8193  			},
  8194  			shards: []*vtctldatapb.Shard{
  8195  				{
  8196  					Keyspace: "testkeyspace",
  8197  					Name:     "-",
  8198  					Shard: &topodatapb.Shard{
  8199  						PrimaryAlias: &topodatapb.TabletAlias{
  8200  							Cell: "zone2",
  8201  							Uid:  200,
  8202  						},
  8203  						PrimaryTermStartTime: &vttime.Time{
  8204  							Seconds: 1000,
  8205  						},
  8206  						IsPrimaryServing: true,
  8207  					},
  8208  				},
  8209  			},
  8210  			topoErr: assert.AnError,
  8211  			req: &vtctldatapb.ReparentTabletRequest{
  8212  				Tablet: &topodatapb.TabletAlias{
  8213  					Cell: "zone1",
  8214  					Uid:  100,
  8215  				},
  8216  			},
  8217  			expected:  nil,
  8218  			shouldErr: true,
  8219  		},
  8220  	}
  8221  
  8222  	for _, tt := range tests {
  8223  		tt := tt
  8224  
  8225  		t.Run(tt.name, func(t *testing.T) {
  8226  			t.Parallel()
  8227  
  8228  			if tt.req == nil {
  8229  				t.Skip("focused on other test cases right now")
  8230  			}
  8231  
  8232  			cells := []string{"zone1", "zone2", "zone3"}
  8233  
  8234  			ctx := context.Background()
  8235  			ts, topofactory := memorytopo.NewServerAndFactory(cells...)
  8236  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, tt.tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  8237  				return NewVtctldServer(ts)
  8238  			})
  8239  
  8240  			testutil.AddTablets(ctx, t, ts, &testutil.AddTabletOptions{
  8241  				SkipShardCreation: true,
  8242  			}, tt.tablets...)
  8243  			testutil.AddShards(ctx, t, ts, tt.shards...)
  8244  
  8245  			if tt.topoErr != nil {
  8246  				topofactory.SetError(tt.topoErr)
  8247  			}
  8248  
  8249  			resp, err := vtctld.ReparentTablet(ctx, tt.req)
  8250  			if tt.shouldErr {
  8251  				assert.Error(t, err)
  8252  				return
  8253  			}
  8254  
  8255  			assert.NoError(t, err)
  8256  			utils.MustMatch(t, tt.expected, resp)
  8257  		})
  8258  	}
  8259  }
  8260  
  8261  func TestRestoreFromBackup(t *testing.T) {
  8262  	ctx := context.Background()
  8263  	tests := []struct {
  8264  		name      string
  8265  		ts        *topo.Server
  8266  		tmc       tmclient.TabletManagerClient
  8267  		tablets   []*topodatapb.Tablet
  8268  		req       *vtctldatapb.RestoreFromBackupRequest
  8269  		shouldErr bool
  8270  		assertion func(t *testing.T, responses []*vtctldatapb.RestoreFromBackupResponse, err error)
  8271  	}{
  8272  		{
  8273  			name: "ok",
  8274  			ts:   memorytopo.NewServer("zone1"),
  8275  			tmc: &testutil.TabletManagerClient{
  8276  				RestoreFromBackupResults: map[string]struct {
  8277  					Events        []*logutilpb.Event
  8278  					EventInterval time.Duration
  8279  					EventJitter   time.Duration
  8280  					ErrorAfter    time.Duration
  8281  				}{
  8282  					"zone1-0000000100": {
  8283  						Events: []*logutilpb.Event{{}, {}, {}},
  8284  					},
  8285  				},
  8286  				SetReplicationSourceResults: map[string]error{
  8287  					"zone1-0000000100": nil,
  8288  				},
  8289  			},
  8290  			tablets: []*topodatapb.Tablet{
  8291  				{
  8292  					Alias: &topodatapb.TabletAlias{
  8293  						Cell: "zone1",
  8294  						Uid:  100,
  8295  					},
  8296  					Keyspace: "ks",
  8297  					Shard:    "-",
  8298  					Type:     topodatapb.TabletType_REPLICA,
  8299  				},
  8300  				{
  8301  					Alias: &topodatapb.TabletAlias{
  8302  						Cell: "zone1",
  8303  						Uid:  200,
  8304  					},
  8305  					Keyspace: "ks",
  8306  					Shard:    "-",
  8307  					Type:     topodatapb.TabletType_PRIMARY,
  8308  				},
  8309  			},
  8310  			req: &vtctldatapb.RestoreFromBackupRequest{
  8311  				TabletAlias: &topodatapb.TabletAlias{
  8312  					Cell: "zone1",
  8313  					Uid:  100,
  8314  				},
  8315  			},
  8316  			assertion: func(t *testing.T, responses []*vtctldatapb.RestoreFromBackupResponse, err error) {
  8317  				assert.ErrorIs(t, err, io.EOF, "expected Recv loop to end with io.EOF")
  8318  				assert.Equal(t, 3, len(responses), "expected 3 messages from restorefrombackupclient stream")
  8319  			},
  8320  		},
  8321  		{
  8322  			name: "no such tablet",
  8323  			ts:   memorytopo.NewServer("zone1"),
  8324  			tmc: &testutil.TabletManagerClient{
  8325  				Backups: map[string]struct {
  8326  					Events        []*logutilpb.Event
  8327  					EventInterval time.Duration
  8328  					EventJitter   time.Duration
  8329  					ErrorAfter    time.Duration
  8330  				}{
  8331  					"zone1-0000000100": {
  8332  						Events: []*logutilpb.Event{{}, {}, {}},
  8333  					},
  8334  				},
  8335  			},
  8336  			tablets: []*topodatapb.Tablet{
  8337  				{
  8338  					Alias: &topodatapb.TabletAlias{
  8339  						Cell: "zone1",
  8340  						Uid:  100,
  8341  					},
  8342  					Type:     topodatapb.TabletType_REPLICA,
  8343  					Keyspace: "ks",
  8344  					Shard:    "-",
  8345  				},
  8346  			},
  8347  			req: &vtctldatapb.RestoreFromBackupRequest{
  8348  				TabletAlias: &topodatapb.TabletAlias{
  8349  					Cell: "zone404",
  8350  					Uid:  404,
  8351  				},
  8352  			},
  8353  			assertion: func(t *testing.T, responses []*vtctldatapb.RestoreFromBackupResponse, err error) {
  8354  				assert.NotErrorIs(t, err, io.EOF, "expected restorefrombackupclient stream to close with non-EOF")
  8355  				assert.Zero(t, len(responses), "expected no restorefrombackupclient messages")
  8356  			},
  8357  		},
  8358  	}
  8359  
  8360  	for _, tt := range tests {
  8361  		tt := tt
  8362  		t.Run(tt.name, func(t *testing.T) {
  8363  			testutil.AddTablets(ctx, t, tt.ts,
  8364  				&testutil.AddTabletOptions{
  8365  					AlsoSetShardPrimary: true,
  8366  				}, tt.tablets...,
  8367  			)
  8368  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, tt.ts, tt.tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  8369  				return NewVtctldServer(ts)
  8370  			})
  8371  			client := localvtctldclient.New(vtctld)
  8372  			stream, err := client.RestoreFromBackup(ctx, tt.req)
  8373  			if tt.shouldErr {
  8374  				assert.Error(t, err)
  8375  			} else {
  8376  				require.NoError(t, err)
  8377  			}
  8378  
  8379  			responses, err := func() (responses []*vtctldatapb.RestoreFromBackupResponse, err error) {
  8380  				for {
  8381  					resp, err := stream.Recv()
  8382  					if err != nil {
  8383  						return responses, err
  8384  					}
  8385  
  8386  					responses = append(responses, resp)
  8387  				}
  8388  			}()
  8389  
  8390  			if tt.assertion != nil {
  8391  				func() {
  8392  					t.Helper()
  8393  					tt.assertion(t, responses, err)
  8394  				}()
  8395  			}
  8396  		})
  8397  	}
  8398  }
  8399  
  8400  func TestRunHealthCheck(t *testing.T) {
  8401  	t.Parallel()
  8402  
  8403  	ctx := context.Background()
  8404  	tests := []struct {
  8405  		name      string
  8406  		tablets   []*topodatapb.Tablet
  8407  		tmc       testutil.TabletManagerClient
  8408  		req       *vtctldatapb.RunHealthCheckRequest
  8409  		shouldErr bool
  8410  	}{
  8411  		{
  8412  			name: "ok",
  8413  			tablets: []*topodatapb.Tablet{
  8414  				{
  8415  					Alias: &topodatapb.TabletAlias{
  8416  						Cell: "zone1",
  8417  						Uid:  100,
  8418  					},
  8419  				},
  8420  			},
  8421  			tmc: testutil.TabletManagerClient{
  8422  				RunHealthCheckResults: map[string]error{
  8423  					"zone1-0000000100": nil,
  8424  				},
  8425  			},
  8426  			req: &vtctldatapb.RunHealthCheckRequest{
  8427  				TabletAlias: &topodatapb.TabletAlias{
  8428  					Cell: "zone1",
  8429  					Uid:  100,
  8430  				},
  8431  			},
  8432  		},
  8433  		{
  8434  			name: "no tablet",
  8435  			tablets: []*topodatapb.Tablet{
  8436  				{
  8437  					Alias: &topodatapb.TabletAlias{
  8438  						Cell: "zone1",
  8439  						Uid:  404,
  8440  					},
  8441  				},
  8442  			},
  8443  			tmc: testutil.TabletManagerClient{
  8444  				RunHealthCheckResults: map[string]error{
  8445  					"zone1-0000000100": nil,
  8446  				},
  8447  			},
  8448  			req: &vtctldatapb.RunHealthCheckRequest{
  8449  				TabletAlias: &topodatapb.TabletAlias{
  8450  					Cell: "zone1",
  8451  					Uid:  100,
  8452  				},
  8453  			},
  8454  			shouldErr: true,
  8455  		},
  8456  		{
  8457  			name: "tmc call failed",
  8458  			tablets: []*topodatapb.Tablet{
  8459  				{
  8460  					Alias: &topodatapb.TabletAlias{
  8461  						Cell: "zone1",
  8462  						Uid:  100,
  8463  					},
  8464  				},
  8465  			},
  8466  			tmc: testutil.TabletManagerClient{
  8467  				RunHealthCheckResults: map[string]error{
  8468  					"zone1-0000000100": assert.AnError,
  8469  				},
  8470  			},
  8471  			req: &vtctldatapb.RunHealthCheckRequest{
  8472  				TabletAlias: &topodatapb.TabletAlias{
  8473  					Cell: "zone1",
  8474  					Uid:  100,
  8475  				},
  8476  			},
  8477  			shouldErr: true,
  8478  		},
  8479  	}
  8480  
  8481  	for _, tt := range tests {
  8482  		tt := tt
  8483  		t.Run(tt.name, func(t *testing.T) {
  8484  			t.Parallel()
  8485  
  8486  			ts := memorytopo.NewServer("zone1")
  8487  			testutil.AddTablets(ctx, t, ts, nil, tt.tablets...)
  8488  
  8489  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, &tt.tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  8490  				return NewVtctldServer(ts)
  8491  			})
  8492  			_, err := vtctld.RunHealthCheck(ctx, tt.req)
  8493  			if tt.shouldErr {
  8494  				assert.Error(t, err)
  8495  				return
  8496  			}
  8497  
  8498  			require.NoError(t, err)
  8499  		})
  8500  	}
  8501  }
  8502  
  8503  func TestSetKeyspaceDurabilityPolicy(t *testing.T) {
  8504  	t.Parallel()
  8505  
  8506  	tests := []struct {
  8507  		name        string
  8508  		keyspaces   []*vtctldatapb.Keyspace
  8509  		req         *vtctldatapb.SetKeyspaceDurabilityPolicyRequest
  8510  		expected    *vtctldatapb.SetKeyspaceDurabilityPolicyResponse
  8511  		expectedErr string
  8512  	}{
  8513  		{
  8514  			name: "ok",
  8515  			keyspaces: []*vtctldatapb.Keyspace{
  8516  				{
  8517  					Name:     "ks1",
  8518  					Keyspace: &topodatapb.Keyspace{},
  8519  				},
  8520  				{
  8521  					Name:     "ks2",
  8522  					Keyspace: &topodatapb.Keyspace{},
  8523  				},
  8524  			},
  8525  			req: &vtctldatapb.SetKeyspaceDurabilityPolicyRequest{
  8526  				Keyspace:         "ks1",
  8527  				DurabilityPolicy: "none",
  8528  			},
  8529  			expected: &vtctldatapb.SetKeyspaceDurabilityPolicyResponse{
  8530  				Keyspace: &topodatapb.Keyspace{
  8531  					DurabilityPolicy: "none",
  8532  				},
  8533  			},
  8534  		},
  8535  		{
  8536  			name: "keyspace not found",
  8537  			req: &vtctldatapb.SetKeyspaceDurabilityPolicyRequest{
  8538  				Keyspace: "ks1",
  8539  			},
  8540  			expectedErr: "node doesn't exist: keyspaces/ks1",
  8541  		},
  8542  		{
  8543  			name: "fail to update durability policy",
  8544  			keyspaces: []*vtctldatapb.Keyspace{
  8545  				{
  8546  					Name:     "ks1",
  8547  					Keyspace: &topodatapb.Keyspace{},
  8548  				},
  8549  			},
  8550  			req: &vtctldatapb.SetKeyspaceDurabilityPolicyRequest{
  8551  				Keyspace:         "ks1",
  8552  				DurabilityPolicy: "non-existent",
  8553  			},
  8554  			expectedErr: "durability policy <non-existent> is not a valid policy. Please register it as a policy first",
  8555  		},
  8556  	}
  8557  
  8558  	ctx := context.Background()
  8559  
  8560  	for _, tt := range tests {
  8561  		tt := tt
  8562  		t.Run(tt.name, func(t *testing.T) {
  8563  			t.Parallel()
  8564  
  8565  			ts := memorytopo.NewServer("zone1")
  8566  			testutil.AddKeyspaces(ctx, t, ts, tt.keyspaces...)
  8567  
  8568  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  8569  				return NewVtctldServer(ts)
  8570  			})
  8571  			resp, err := vtctld.SetKeyspaceDurabilityPolicy(ctx, tt.req)
  8572  			if tt.expectedErr != "" {
  8573  				assert.EqualError(t, err, tt.expectedErr)
  8574  				return
  8575  			}
  8576  
  8577  			require.NoError(t, err)
  8578  			utils.MustMatch(t, tt.expected, resp)
  8579  		})
  8580  	}
  8581  }
  8582  
  8583  func TestSetShardIsPrimaryServing(t *testing.T) {
  8584  	t.Parallel()
  8585  
  8586  	type testcase struct {
  8587  		name      string
  8588  		ctx       context.Context
  8589  		ts        *topo.Server
  8590  		setup     func(*testing.T, *testcase)
  8591  		teardown  func(*testing.T, *testcase)
  8592  		req       *vtctldatapb.SetShardIsPrimaryServingRequest
  8593  		expected  *vtctldatapb.SetShardIsPrimaryServingResponse
  8594  		shouldErr bool
  8595  	}
  8596  
  8597  	tests := []*testcase{
  8598  		{
  8599  			name: "ok",
  8600  			setup: func(t *testing.T, tt *testcase) {
  8601  				tt.ctx = context.Background()
  8602  				tt.ts = memorytopo.NewServer("zone1")
  8603  				testutil.AddShards(tt.ctx, t, tt.ts, &vtctldatapb.Shard{
  8604  					Keyspace: "testkeyspace",
  8605  					Name:     "-",
  8606  					Shard:    &topodatapb.Shard{},
  8607  				})
  8608  			},
  8609  			req: &vtctldatapb.SetShardIsPrimaryServingRequest{
  8610  				Keyspace:  "testkeyspace",
  8611  				Shard:     "-",
  8612  				IsServing: true,
  8613  			},
  8614  			expected: &vtctldatapb.SetShardIsPrimaryServingResponse{
  8615  				Shard: &topodatapb.Shard{
  8616  					IsPrimaryServing: true,
  8617  				},
  8618  			},
  8619  		},
  8620  		{
  8621  			name: "lock error",
  8622  			setup: func(t *testing.T, tt *testcase) {
  8623  				var cancel func()
  8624  				tt.ctx, cancel = context.WithTimeout(context.Background(), time.Millisecond*50)
  8625  				tt.ts = memorytopo.NewServer("zone1")
  8626  				testutil.AddShards(tt.ctx, t, tt.ts, &vtctldatapb.Shard{
  8627  					Keyspace: "testkeyspace",
  8628  					Name:     "-",
  8629  					Shard:    &topodatapb.Shard{},
  8630  				})
  8631  
  8632  				_, unlock, err := tt.ts.LockKeyspace(tt.ctx, "testkeyspace", "test lock")
  8633  				require.NoError(t, err)
  8634  				tt.teardown = func(t *testing.T, tt *testcase) {
  8635  					var err error
  8636  					unlock(&err)
  8637  					assert.NoError(t, err)
  8638  					cancel()
  8639  				}
  8640  			},
  8641  			req: &vtctldatapb.SetShardIsPrimaryServingRequest{
  8642  				Keyspace:  "testkeyspace",
  8643  				Shard:     "-",
  8644  				IsServing: true,
  8645  			},
  8646  			expected:  nil,
  8647  			shouldErr: true,
  8648  		},
  8649  	}
  8650  
  8651  	for _, tt := range tests {
  8652  		tt := tt
  8653  
  8654  		t.Run(tt.name, func(t *testing.T) {
  8655  			t.Parallel()
  8656  
  8657  			if tt.setup != nil {
  8658  				tt.setup(t, tt)
  8659  			}
  8660  			if tt.teardown != nil {
  8661  				defer tt.teardown(t, tt)
  8662  			}
  8663  
  8664  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, tt.ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  8665  				return NewVtctldServer(ts)
  8666  			})
  8667  			resp, err := vtctld.SetShardIsPrimaryServing(tt.ctx, tt.req)
  8668  			if tt.shouldErr {
  8669  				assert.Error(t, err)
  8670  				return
  8671  			}
  8672  
  8673  			assert.NoError(t, err)
  8674  			utils.MustMatch(t, tt.expected, resp)
  8675  		})
  8676  	}
  8677  }
  8678  
  8679  func TestSetShardTabletControl(t *testing.T) {
  8680  	t.Parallel()
  8681  
  8682  	type testcase struct {
  8683  		name      string
  8684  		ctx       context.Context
  8685  		ts        *topo.Server
  8686  		setup     func(*testing.T, *testcase)
  8687  		teardown  func(*testing.T, *testcase)
  8688  		req       *vtctldatapb.SetShardTabletControlRequest
  8689  		expected  *vtctldatapb.SetShardTabletControlResponse
  8690  		shouldErr bool
  8691  	}
  8692  
  8693  	tests := []*testcase{
  8694  		{
  8695  			name: "ok",
  8696  			setup: func(t *testing.T, tt *testcase) {
  8697  				tt.ctx = context.Background()
  8698  				tt.ts = memorytopo.NewServer("zone1", "zone2", "zone3")
  8699  
  8700  				testutil.AddShards(tt.ctx, t, tt.ts, &vtctldatapb.Shard{
  8701  					Keyspace: "testkeyspace",
  8702  					Name:     "-",
  8703  					Shard: &topodatapb.Shard{
  8704  						TabletControls: []*topodatapb.Shard_TabletControl{
  8705  							{
  8706  								TabletType:   topodatapb.TabletType_REPLICA,
  8707  								Cells:        []string{"zone1"},
  8708  								DeniedTables: []string{"t1"},
  8709  							},
  8710  							{
  8711  								TabletType:   topodatapb.TabletType_REPLICA,
  8712  								Cells:        []string{"zone2", "zone3"},
  8713  								DeniedTables: []string{"t2"},
  8714  							},
  8715  						},
  8716  					},
  8717  				})
  8718  			},
  8719  			req: &vtctldatapb.SetShardTabletControlRequest{
  8720  				Keyspace:     "testkeyspace",
  8721  				Shard:        "-",
  8722  				DeniedTables: []string{"t1"},
  8723  				Cells:        []string{"zone2", "zone3"},
  8724  				TabletType:   topodatapb.TabletType_REPLICA,
  8725  			},
  8726  			expected: &vtctldatapb.SetShardTabletControlResponse{
  8727  				Shard: &topodatapb.Shard{
  8728  					TabletControls: []*topodatapb.Shard_TabletControl{
  8729  						{
  8730  							TabletType:   topodatapb.TabletType_REPLICA,
  8731  							Cells:        []string{"zone1", "zone2", "zone3"},
  8732  							DeniedTables: []string{"t1"},
  8733  						},
  8734  						{
  8735  							TabletType:   topodatapb.TabletType_REPLICA,
  8736  							Cells:        []string{"zone2", "zone3"},
  8737  							DeniedTables: []string{"t2"},
  8738  						},
  8739  					},
  8740  				},
  8741  			},
  8742  		},
  8743  		{
  8744  			name: "remove tabletcontrols",
  8745  			setup: func(t *testing.T, tt *testcase) {
  8746  				tt.ctx = context.Background()
  8747  				tt.ts = memorytopo.NewServer("zone1", "zone2", "zone3")
  8748  
  8749  				testutil.AddShards(tt.ctx, t, tt.ts, &vtctldatapb.Shard{
  8750  					Keyspace: "testkeyspace",
  8751  					Name:     "-",
  8752  					Shard: &topodatapb.Shard{
  8753  						TabletControls: []*topodatapb.Shard_TabletControl{
  8754  							{
  8755  								TabletType:   topodatapb.TabletType_REPLICA,
  8756  								Cells:        []string{"zone1"},
  8757  								DeniedTables: []string{"t1"},
  8758  							},
  8759  							{
  8760  								TabletType:   topodatapb.TabletType_REPLICA,
  8761  								Cells:        []string{"zone2", "zone3"},
  8762  								DeniedTables: []string{"t2"},
  8763  							},
  8764  						},
  8765  					},
  8766  				})
  8767  			},
  8768  			req: &vtctldatapb.SetShardTabletControlRequest{
  8769  				Keyspace:   "testkeyspace",
  8770  				Shard:      "-",
  8771  				TabletType: topodatapb.TabletType_REPLICA,
  8772  				Remove:     true,
  8773  			},
  8774  			expected: &vtctldatapb.SetShardTabletControlResponse{
  8775  				Shard: &topodatapb.Shard{},
  8776  			},
  8777  		},
  8778  		{
  8779  			name: "disable queryservice",
  8780  			setup: func(t *testing.T, tt *testcase) {
  8781  				tt.ctx = context.Background()
  8782  				tt.ts = memorytopo.NewServer("zone1", "zone2", "zone3")
  8783  
  8784  				testutil.AddShards(tt.ctx, t, tt.ts, &vtctldatapb.Shard{
  8785  					Keyspace: "testkeyspace",
  8786  					Name:     "-",
  8787  				})
  8788  
  8789  				lctx, unlock, lerr := tt.ts.LockKeyspace(tt.ctx, "testkeyspace", "locking to create partitions for test")
  8790  				require.NoError(t, lerr, "could not lock keyspace to setup test partitions")
  8791  				var err error
  8792  				defer unlock(&err)
  8793  				defer func() { require.NoError(t, err) }()
  8794  
  8795  				err = tt.ts.UpdateSrvKeyspace(lctx, "zone1", "testkeyspace", &topodatapb.SrvKeyspace{
  8796  					Partitions: []*topodatapb.SrvKeyspace_KeyspacePartition{
  8797  						{
  8798  							ServedType: topodatapb.TabletType_REPLICA,
  8799  							ShardTabletControls: []*topodatapb.ShardTabletControl{
  8800  								{
  8801  									Name:                 "-",
  8802  									QueryServiceDisabled: false,
  8803  								},
  8804  							},
  8805  						},
  8806  					},
  8807  				})
  8808  				require.NoError(t, err)
  8809  				err = tt.ts.UpdateSrvKeyspace(lctx, "zone2", "testkeyspace", &topodatapb.SrvKeyspace{
  8810  					Partitions: []*topodatapb.SrvKeyspace_KeyspacePartition{
  8811  						{
  8812  							ServedType: topodatapb.TabletType_REPLICA,
  8813  							ShardTabletControls: []*topodatapb.ShardTabletControl{
  8814  								{
  8815  									Name:                 "-",
  8816  									QueryServiceDisabled: true,
  8817  								},
  8818  							},
  8819  						},
  8820  					},
  8821  				})
  8822  				require.NoError(t, err)
  8823  			},
  8824  			teardown: func(t *testing.T, tt *testcase) {
  8825  				expected := map[string][]*topodatapb.ShardTabletControl{
  8826  					"zone1": {
  8827  						{
  8828  							Name:                 "-",
  8829  							QueryServiceDisabled: true,
  8830  						},
  8831  					},
  8832  					"zone2": {
  8833  						{
  8834  							Name:                 "-",
  8835  							QueryServiceDisabled: true,
  8836  						},
  8837  					},
  8838  				}
  8839  				for cell, expectedControls := range expected {
  8840  					partitions, err := tt.ts.GetSrvKeyspace(tt.ctx, cell, "testkeyspace")
  8841  					require.NoError(t, err, "could not get srvkeyspace for testkeyspace/%s", cell)
  8842  
  8843  					for _, partition := range partitions.Partitions {
  8844  						if partition.ServedType != topodatapb.TabletType_REPLICA {
  8845  							continue
  8846  						}
  8847  
  8848  						utils.MustMatch(t, expectedControls, partition.ShardTabletControls)
  8849  					}
  8850  				}
  8851  			},
  8852  			req: &vtctldatapb.SetShardTabletControlRequest{
  8853  				Keyspace:            "testkeyspace",
  8854  				Shard:               "-",
  8855  				Cells:               []string{"zone1", "zone2"},
  8856  				TabletType:          topodatapb.TabletType_REPLICA,
  8857  				DisableQueryService: true,
  8858  			},
  8859  			expected: &vtctldatapb.SetShardTabletControlResponse{
  8860  				Shard: &topodatapb.Shard{
  8861  					TabletControls: []*topodatapb.Shard_TabletControl{
  8862  						{
  8863  							TabletType: topodatapb.TabletType_REPLICA,
  8864  							Cells:      []string{"zone1", "zone2"},
  8865  						},
  8866  					},
  8867  					IsPrimaryServing: true,
  8868  					KeyRange:         &topodatapb.KeyRange{},
  8869  				},
  8870  			},
  8871  		},
  8872  		{
  8873  			name: "keyspace lock error",
  8874  			setup: func(t *testing.T, tt *testcase) {
  8875  				var cancel func()
  8876  				tt.ctx, cancel = context.WithTimeout(context.Background(), time.Millisecond*50)
  8877  				tt.ts = memorytopo.NewServer("zone1")
  8878  				testutil.AddShards(tt.ctx, t, tt.ts, &vtctldatapb.Shard{
  8879  					Keyspace: "testkeyspace",
  8880  					Name:     "-",
  8881  					Shard:    &topodatapb.Shard{},
  8882  				})
  8883  
  8884  				_, unlock, err := tt.ts.LockKeyspace(tt.ctx, "testkeyspace", "test lock")
  8885  				require.NoError(t, err)
  8886  				tt.teardown = func(t *testing.T, tt *testcase) {
  8887  					var err error
  8888  					unlock(&err)
  8889  					assert.NoError(t, err)
  8890  					cancel()
  8891  				}
  8892  			},
  8893  			req: &vtctldatapb.SetShardTabletControlRequest{
  8894  				Keyspace:     "testkeyspace",
  8895  				Shard:        "-",
  8896  				DeniedTables: []string{"t1"},
  8897  				TabletType:   topodatapb.TabletType_REPLICA,
  8898  			},
  8899  			shouldErr: true,
  8900  		},
  8901  	}
  8902  	for _, tt := range tests {
  8903  		tt := tt
  8904  		t.Run(tt.name, func(t *testing.T) {
  8905  			t.Parallel()
  8906  
  8907  			if tt.setup != nil {
  8908  				tt.setup(t, tt)
  8909  			}
  8910  			if tt.teardown != nil {
  8911  				defer tt.teardown(t, tt)
  8912  			}
  8913  
  8914  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, tt.ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  8915  				return NewVtctldServer(ts)
  8916  			})
  8917  			resp, err := vtctld.SetShardTabletControl(tt.ctx, tt.req)
  8918  			if tt.shouldErr {
  8919  				assert.Error(t, err)
  8920  				return
  8921  			}
  8922  
  8923  			assert.NoError(t, err)
  8924  			utils.MustMatch(t, tt.expected, resp)
  8925  		})
  8926  	}
  8927  }
  8928  
  8929  func TestSetWritable(t *testing.T) {
  8930  	t.Parallel()
  8931  
  8932  	tests := []struct {
  8933  		name      string
  8934  		cells     []string
  8935  		tablets   []*topodatapb.Tablet
  8936  		tmc       testutil.TabletManagerClient
  8937  		req       *vtctldatapb.SetWritableRequest
  8938  		shouldErr bool
  8939  	}{
  8940  		{
  8941  			name:  "writable ok",
  8942  			cells: []string{"zone1"},
  8943  			tablets: []*topodatapb.Tablet{
  8944  				{
  8945  					Alias: &topodatapb.TabletAlias{
  8946  						Cell: "zone1",
  8947  						Uid:  100,
  8948  					},
  8949  					Keyspace: "testkeyspace",
  8950  					Shard:    "-",
  8951  					Type:     topodatapb.TabletType_REPLICA,
  8952  				},
  8953  			},
  8954  			tmc: testutil.TabletManagerClient{
  8955  				SetReadOnlyResults: map[string]error{
  8956  					"zone1-0000000100": assert.AnError,
  8957  				},
  8958  				SetReadWriteResults: map[string]error{
  8959  					"zone1-0000000100": nil,
  8960  				},
  8961  			},
  8962  			req: &vtctldatapb.SetWritableRequest{
  8963  				TabletAlias: &topodatapb.TabletAlias{
  8964  					Cell: "zone1",
  8965  					Uid:  100,
  8966  				},
  8967  				Writable: true,
  8968  			},
  8969  			shouldErr: false,
  8970  		},
  8971  		{
  8972  			name:  "writable fail",
  8973  			cells: []string{"zone1"},
  8974  			tablets: []*topodatapb.Tablet{
  8975  				{
  8976  					Alias: &topodatapb.TabletAlias{
  8977  						Cell: "zone1",
  8978  						Uid:  100,
  8979  					},
  8980  					Keyspace: "testkeyspace",
  8981  					Shard:    "-",
  8982  					Type:     topodatapb.TabletType_RDONLY,
  8983  				},
  8984  			},
  8985  			tmc: testutil.TabletManagerClient{
  8986  				SetReadOnlyResults: map[string]error{
  8987  					"zone1-0000000100": nil,
  8988  				},
  8989  				SetReadWriteResults: map[string]error{
  8990  					"zone1-0000000100": assert.AnError,
  8991  				},
  8992  			},
  8993  			req: &vtctldatapb.SetWritableRequest{
  8994  				TabletAlias: &topodatapb.TabletAlias{
  8995  					Cell: "zone1",
  8996  					Uid:  100,
  8997  				},
  8998  				Writable: true,
  8999  			},
  9000  			shouldErr: true,
  9001  		},
  9002  		{
  9003  			name:  "read only ok",
  9004  			cells: []string{"zone1"},
  9005  			tablets: []*topodatapb.Tablet{
  9006  				{
  9007  					Alias: &topodatapb.TabletAlias{
  9008  						Cell: "zone1",
  9009  						Uid:  100,
  9010  					},
  9011  					Keyspace: "testkeyspace",
  9012  					Shard:    "-",
  9013  					Type:     topodatapb.TabletType_REPLICA,
  9014  				},
  9015  			},
  9016  			tmc: testutil.TabletManagerClient{
  9017  				SetReadOnlyResults: map[string]error{
  9018  					"zone1-0000000100": nil,
  9019  				},
  9020  				SetReadWriteResults: map[string]error{
  9021  					"zone1-0000000100": assert.AnError,
  9022  				},
  9023  			},
  9024  			req: &vtctldatapb.SetWritableRequest{
  9025  				TabletAlias: &topodatapb.TabletAlias{
  9026  					Cell: "zone1",
  9027  					Uid:  100,
  9028  				},
  9029  				Writable: false,
  9030  			},
  9031  			shouldErr: false,
  9032  		},
  9033  		{
  9034  			name:  "read only fail",
  9035  			cells: []string{"zone1"},
  9036  			tablets: []*topodatapb.Tablet{
  9037  				{
  9038  					Alias: &topodatapb.TabletAlias{
  9039  						Cell: "zone1",
  9040  						Uid:  100,
  9041  					},
  9042  					Keyspace: "testkeyspace",
  9043  					Shard:    "-",
  9044  					Type:     topodatapb.TabletType_PRIMARY,
  9045  				},
  9046  			},
  9047  			tmc: testutil.TabletManagerClient{
  9048  				SetReadOnlyResults: map[string]error{
  9049  					"zone1-0000000100": assert.AnError,
  9050  				},
  9051  				SetReadWriteResults: map[string]error{
  9052  					"zone1-0000000100": nil,
  9053  				},
  9054  			},
  9055  			req: &vtctldatapb.SetWritableRequest{
  9056  				TabletAlias: &topodatapb.TabletAlias{
  9057  					Cell: "zone1",
  9058  					Uid:  100,
  9059  				},
  9060  				Writable: false,
  9061  			},
  9062  			shouldErr: true,
  9063  		},
  9064  		{
  9065  			name:  "no such tablet",
  9066  			cells: []string{"zone1"},
  9067  			tablets: []*topodatapb.Tablet{
  9068  				{
  9069  					Alias: &topodatapb.TabletAlias{
  9070  						Cell: "zone1",
  9071  						Uid:  100,
  9072  					},
  9073  					Keyspace: "testkeyspace",
  9074  					Shard:    "-",
  9075  					Type:     topodatapb.TabletType_PRIMARY,
  9076  				},
  9077  			},
  9078  			req: &vtctldatapb.SetWritableRequest{
  9079  				TabletAlias: &topodatapb.TabletAlias{
  9080  					Cell: "zone2",
  9081  					Uid:  200,
  9082  				},
  9083  				Writable: false,
  9084  			},
  9085  			shouldErr: true,
  9086  		},
  9087  		{
  9088  			name:  "bad request",
  9089  			cells: []string{"zone1"},
  9090  			tablets: []*topodatapb.Tablet{
  9091  				{
  9092  					Alias: &topodatapb.TabletAlias{
  9093  						Cell: "zone1",
  9094  						Uid:  100,
  9095  					},
  9096  					Keyspace: "testkeyspace",
  9097  					Shard:    "-",
  9098  					Type:     topodatapb.TabletType_PRIMARY,
  9099  				},
  9100  			},
  9101  			req:       &vtctldatapb.SetWritableRequest{},
  9102  			shouldErr: true,
  9103  		},
  9104  	}
  9105  
  9106  	ctx := context.Background()
  9107  	for _, tt := range tests {
  9108  		tt := tt
  9109  		t.Run(tt.name, func(t *testing.T) {
  9110  			t.Parallel()
  9111  
  9112  			ts := memorytopo.NewServer(tt.cells...)
  9113  			defer ts.Close()
  9114  
  9115  			testutil.AddTablets(ctx, t, ts, nil, tt.tablets...)
  9116  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, &tt.tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  9117  				return NewVtctldServer(ts)
  9118  			})
  9119  
  9120  			_, err := vtctld.SetWritable(ctx, tt.req)
  9121  			if tt.shouldErr {
  9122  				assert.Error(t, err)
  9123  				return
  9124  			}
  9125  
  9126  			assert.NoError(t, err)
  9127  		})
  9128  	}
  9129  }
  9130  
  9131  func TestShardReplicationAdd(t *testing.T) {
  9132  	t.Parallel()
  9133  
  9134  	ctx := context.Background()
  9135  	ts := memorytopo.NewServer("zone1")
  9136  	vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  9137  		return NewVtctldServer(ts)
  9138  	})
  9139  
  9140  	tablets := []*topodatapb.Tablet{
  9141  		{
  9142  			Alias: &topodatapb.TabletAlias{
  9143  				Cell: "zone1",
  9144  				Uid:  100,
  9145  			},
  9146  			Keyspace: "ks",
  9147  			Shard:    "-",
  9148  		},
  9149  		{
  9150  			Alias: &topodatapb.TabletAlias{
  9151  				Cell: "zone1",
  9152  				Uid:  101,
  9153  			},
  9154  			Keyspace: "ks",
  9155  			Shard:    "-",
  9156  		},
  9157  	}
  9158  	testutil.AddTablets(ctx, t, ts, nil, tablets...)
  9159  
  9160  	_, err := vtctld.ShardReplicationAdd(ctx, &vtctldatapb.ShardReplicationAddRequest{
  9161  		Keyspace: "ks",
  9162  		Shard:    "-",
  9163  		TabletAlias: &topodatapb.TabletAlias{
  9164  			Cell: "zone1",
  9165  			Uid:  404,
  9166  		},
  9167  	})
  9168  	assert.NoError(t, err)
  9169  
  9170  	resp, err := vtctld.ShardReplicationFix(ctx, &vtctldatapb.ShardReplicationFixRequest{
  9171  		Keyspace: "ks",
  9172  		Shard:    "-",
  9173  		Cell:     "zone1",
  9174  	})
  9175  	require.NoError(t, err, "ShardReplicationFix failed")
  9176  	utils.MustMatch(t, &topodatapb.ShardReplicationError{
  9177  		TabletAlias: &topodatapb.TabletAlias{
  9178  			Cell: "zone1",
  9179  			Uid:  404,
  9180  		},
  9181  		Type: topodatapb.ShardReplicationError_NOT_FOUND,
  9182  	}, resp.Error)
  9183  }
  9184  
  9185  func TestShardReplicationPositions(t *testing.T) {
  9186  	t.Parallel()
  9187  
  9188  	tests := []struct {
  9189  		name       string
  9190  		ts         *topo.Server
  9191  		tablets    []*topodatapb.Tablet
  9192  		tmc        tmclient.TabletManagerClient
  9193  		ctxTimeout time.Duration
  9194  		req        *vtctldatapb.ShardReplicationPositionsRequest
  9195  		expected   *vtctldatapb.ShardReplicationPositionsResponse
  9196  		shouldErr  bool
  9197  	}{
  9198  		{
  9199  			name: "success",
  9200  			ts:   memorytopo.NewServer("zone1"),
  9201  			tablets: []*topodatapb.Tablet{
  9202  				{
  9203  					Alias: &topodatapb.TabletAlias{
  9204  						Cell: "zone1",
  9205  						Uid:  100,
  9206  					},
  9207  					Keyspace: "testkeyspace",
  9208  					Shard:    "-",
  9209  					Type:     topodatapb.TabletType_PRIMARY,
  9210  				},
  9211  				{
  9212  					Alias: &topodatapb.TabletAlias{
  9213  						Cell: "zone1",
  9214  						Uid:  101,
  9215  					},
  9216  					Keyspace: "testkeyspace",
  9217  					Shard:    "-",
  9218  					Type:     topodatapb.TabletType_REPLICA,
  9219  				},
  9220  			},
  9221  			tmc: &testutil.TabletManagerClient{
  9222  				PrimaryPositionResults: map[string]struct {
  9223  					Position string
  9224  					Error    error
  9225  				}{
  9226  					"zone1-0000000100": {
  9227  						Position: "primary_tablet_position",
  9228  					},
  9229  				},
  9230  				ReplicationStatusResults: map[string]struct {
  9231  					Position *replicationdatapb.Status
  9232  					Error    error
  9233  				}{
  9234  					"zone1-0000000101": {
  9235  						Position: &replicationdatapb.Status{
  9236  							Position: "replica_tablet_position",
  9237  						},
  9238  					},
  9239  				},
  9240  			},
  9241  			req: &vtctldatapb.ShardReplicationPositionsRequest{
  9242  				Keyspace: "testkeyspace",
  9243  				Shard:    "-",
  9244  			},
  9245  			expected: &vtctldatapb.ShardReplicationPositionsResponse{
  9246  				ReplicationStatuses: map[string]*replicationdatapb.Status{
  9247  					"zone1-0000000100": {
  9248  						Position: "primary_tablet_position",
  9249  					},
  9250  					"zone1-0000000101": {
  9251  						Position: "replica_tablet_position",
  9252  					},
  9253  				},
  9254  				TabletMap: map[string]*topodatapb.Tablet{
  9255  					"zone1-0000000100": {
  9256  						Alias: &topodatapb.TabletAlias{
  9257  							Cell: "zone1",
  9258  							Uid:  100,
  9259  						},
  9260  						Keyspace: "testkeyspace",
  9261  						Shard:    "-",
  9262  						Type:     topodatapb.TabletType_PRIMARY,
  9263  					},
  9264  					"zone1-0000000101": {
  9265  						Alias: &topodatapb.TabletAlias{
  9266  							Cell: "zone1",
  9267  							Uid:  101,
  9268  						},
  9269  						Keyspace: "testkeyspace",
  9270  						Shard:    "-",
  9271  						Type:     topodatapb.TabletType_REPLICA,
  9272  					},
  9273  				},
  9274  			},
  9275  			shouldErr: false,
  9276  		},
  9277  		{
  9278  			name: "timeouts are nonfatal",
  9279  			ts:   memorytopo.NewServer("zone1"),
  9280  			tablets: []*topodatapb.Tablet{
  9281  				{
  9282  					Alias: &topodatapb.TabletAlias{
  9283  						Cell: "zone1",
  9284  						Uid:  100,
  9285  					},
  9286  					Keyspace: "testkeyspace",
  9287  					Shard:    "-",
  9288  					Type:     topodatapb.TabletType_PRIMARY,
  9289  				},
  9290  				{
  9291  					Alias: &topodatapb.TabletAlias{
  9292  						Cell: "zone1",
  9293  						Uid:  101,
  9294  					},
  9295  					Keyspace: "testkeyspace",
  9296  					Shard:    "-",
  9297  					Type:     topodatapb.TabletType_REPLICA,
  9298  				},
  9299  			},
  9300  			tmc: &testutil.TabletManagerClient{
  9301  				PrimaryPositionDelays: map[string]time.Duration{
  9302  					"zone1-0000000100": time.Millisecond * 100,
  9303  				},
  9304  				PrimaryPositionResults: map[string]struct {
  9305  					Position string
  9306  					Error    error
  9307  				}{
  9308  					"zone1-0000000100": {
  9309  						Position: "primary_tablet_position",
  9310  					},
  9311  				},
  9312  				ReplicationStatusDelays: map[string]time.Duration{
  9313  					"zone1-0000000101": time.Millisecond * 100,
  9314  				},
  9315  				ReplicationStatusResults: map[string]struct {
  9316  					Position *replicationdatapb.Status
  9317  					Error    error
  9318  				}{
  9319  					"zone1-0000000101": {
  9320  						Position: &replicationdatapb.Status{
  9321  							Position: "replica_tablet_position",
  9322  						},
  9323  					},
  9324  				},
  9325  			},
  9326  			ctxTimeout: time.Millisecond * 10,
  9327  			req: &vtctldatapb.ShardReplicationPositionsRequest{
  9328  				Keyspace: "testkeyspace",
  9329  				Shard:    "-",
  9330  			},
  9331  			expected: &vtctldatapb.ShardReplicationPositionsResponse{
  9332  				ReplicationStatuses: map[string]*replicationdatapb.Status{
  9333  					"zone1-0000000100": nil,
  9334  					"zone1-0000000101": nil,
  9335  				},
  9336  				TabletMap: map[string]*topodatapb.Tablet{
  9337  					"zone1-0000000100": {
  9338  						Alias: &topodatapb.TabletAlias{
  9339  							Cell: "zone1",
  9340  							Uid:  100,
  9341  						},
  9342  						Keyspace: "testkeyspace",
  9343  						Shard:    "-",
  9344  						Type:     topodatapb.TabletType_PRIMARY,
  9345  					},
  9346  					"zone1-0000000101": {
  9347  						Alias: &topodatapb.TabletAlias{
  9348  							Cell: "zone1",
  9349  							Uid:  101,
  9350  						},
  9351  						Keyspace: "testkeyspace",
  9352  						Shard:    "-",
  9353  						Type:     topodatapb.TabletType_REPLICA,
  9354  					},
  9355  				},
  9356  			},
  9357  			shouldErr: false,
  9358  		},
  9359  		{
  9360  			name: "other rpc errors are fatal",
  9361  			ts:   memorytopo.NewServer("zone1"),
  9362  			tablets: []*topodatapb.Tablet{
  9363  				{
  9364  					Alias: &topodatapb.TabletAlias{
  9365  						Cell: "zone1",
  9366  						Uid:  100,
  9367  					},
  9368  					Keyspace: "testkeyspace",
  9369  					Shard:    "-",
  9370  					Type:     topodatapb.TabletType_PRIMARY,
  9371  				},
  9372  				{
  9373  					Alias: &topodatapb.TabletAlias{
  9374  						Cell: "zone1",
  9375  						Uid:  101,
  9376  					},
  9377  					Keyspace: "testkeyspace",
  9378  					Shard:    "-",
  9379  					Type:     topodatapb.TabletType_REPLICA,
  9380  				},
  9381  			},
  9382  			tmc: &testutil.TabletManagerClient{
  9383  				PrimaryPositionResults: map[string]struct {
  9384  					Position string
  9385  					Error    error
  9386  				}{
  9387  					"zone1-0000000100": {
  9388  						Error: assert.AnError,
  9389  					},
  9390  				},
  9391  				ReplicationStatusResults: map[string]struct {
  9392  					Position *replicationdatapb.Status
  9393  					Error    error
  9394  				}{
  9395  					"zone1-0000000101": {
  9396  						Position: &replicationdatapb.Status{
  9397  							Position: "replica_tablet_position",
  9398  						},
  9399  					},
  9400  				},
  9401  			},
  9402  			req: &vtctldatapb.ShardReplicationPositionsRequest{
  9403  				Keyspace: "testkeyspace",
  9404  				Shard:    "-",
  9405  			},
  9406  			expected:  nil,
  9407  			shouldErr: true,
  9408  		},
  9409  		{
  9410  			name: "nonexistent shard",
  9411  			ts:   memorytopo.NewServer("zone1"),
  9412  			req: &vtctldatapb.ShardReplicationPositionsRequest{
  9413  				Keyspace: "testkeyspace",
  9414  				Shard:    "-",
  9415  			},
  9416  			expected:  nil,
  9417  			shouldErr: true,
  9418  		},
  9419  	}
  9420  
  9421  	for _, tt := range tests {
  9422  		tt := tt
  9423  
  9424  		t.Run(tt.name, func(t *testing.T) {
  9425  			t.Parallel()
  9426  
  9427  			ctx := context.Background()
  9428  
  9429  			testutil.AddTablets(ctx, t, tt.ts, &testutil.AddTabletOptions{
  9430  				AlsoSetShardPrimary: true,
  9431  				SkipShardCreation:   false,
  9432  			}, tt.tablets...)
  9433  
  9434  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, tt.ts, tt.tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  9435  				return NewVtctldServer(ts)
  9436  			})
  9437  
  9438  			if tt.ctxTimeout > 0 {
  9439  				_ctx, cancel := context.WithTimeout(ctx, tt.ctxTimeout)
  9440  				defer cancel()
  9441  
  9442  				ctx = _ctx
  9443  			}
  9444  
  9445  			resp, err := vtctld.ShardReplicationPositions(ctx, tt.req)
  9446  			if tt.shouldErr {
  9447  				assert.Error(t, err)
  9448  
  9449  				return
  9450  			}
  9451  
  9452  			assert.NoError(t, err)
  9453  			utils.MustMatch(t, tt.expected, resp)
  9454  		})
  9455  	}
  9456  }
  9457  
  9458  func TestShardReplicationRemove(t *testing.T) {
  9459  	t.Parallel()
  9460  
  9461  	ctx := context.Background()
  9462  	ts := memorytopo.NewServer("zone1")
  9463  	vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  9464  		return NewVtctldServer(ts)
  9465  	})
  9466  
  9467  	tablets := []*topodatapb.Tablet{
  9468  		{
  9469  			Alias: &topodatapb.TabletAlias{
  9470  				Cell: "zone1",
  9471  				Uid:  100,
  9472  			},
  9473  			Keyspace: "ks",
  9474  			Shard:    "-",
  9475  		},
  9476  		{
  9477  			Alias: &topodatapb.TabletAlias{
  9478  				Cell: "zone1",
  9479  				Uid:  101,
  9480  			},
  9481  			Keyspace: "ks",
  9482  			Shard:    "-",
  9483  		},
  9484  	}
  9485  	testutil.AddTablets(ctx, t, ts, nil, tablets...)
  9486  
  9487  	_, err := vtctld.ShardReplicationRemove(ctx, &vtctldatapb.ShardReplicationRemoveRequest{
  9488  		Keyspace: "ks",
  9489  		Shard:    "-",
  9490  		TabletAlias: &topodatapb.TabletAlias{
  9491  			Cell: "zone1",
  9492  			Uid:  101,
  9493  		},
  9494  	})
  9495  	assert.NoError(t, err)
  9496  
  9497  	sri, err := ts.GetShardReplication(ctx, "zone1", "ks", "-")
  9498  	require.NoError(t, err, "GetShardReplication failed")
  9499  
  9500  	utils.MustMatch(t, sri.Nodes, []*topodatapb.ShardReplication_Node{{
  9501  		TabletAlias: &topodatapb.TabletAlias{
  9502  			Cell: "zone1",
  9503  			Uid:  100,
  9504  		},
  9505  	}})
  9506  }
  9507  
  9508  func TestSourceShardAdd(t *testing.T) {
  9509  	t.Parallel()
  9510  
  9511  	tests := []struct {
  9512  		name         string
  9513  		shards       []*vtctldatapb.Shard
  9514  		topoIsLocked bool
  9515  		req          *vtctldatapb.SourceShardAddRequest
  9516  		expected     *vtctldatapb.SourceShardAddResponse
  9517  		shouldErr    bool
  9518  		assertion    func(ctx context.Context, t *testing.T, ts *topo.Server)
  9519  	}{
  9520  		{
  9521  			name: "ok",
  9522  			shards: []*vtctldatapb.Shard{
  9523  				{
  9524  					Keyspace: "ks",
  9525  					Name:     "-",
  9526  				},
  9527  			},
  9528  			req: &vtctldatapb.SourceShardAddRequest{
  9529  				Keyspace:       "ks",
  9530  				Shard:          "-",
  9531  				Uid:            1,
  9532  				SourceKeyspace: "otherks",
  9533  				SourceShard:    "-80",
  9534  			},
  9535  			expected: &vtctldatapb.SourceShardAddResponse{
  9536  				Shard: &topodatapb.Shard{
  9537  					IsPrimaryServing: true,
  9538  					KeyRange:         &topodatapb.KeyRange{},
  9539  					SourceShards: []*topodatapb.Shard_SourceShard{
  9540  						{
  9541  							Uid:      1,
  9542  							Keyspace: "otherks",
  9543  							Shard:    "-80",
  9544  						},
  9545  					},
  9546  				},
  9547  			},
  9548  		},
  9549  		{
  9550  			name: "uid already used",
  9551  			shards: []*vtctldatapb.Shard{
  9552  				{
  9553  					Keyspace: "ks",
  9554  					Name:     "-",
  9555  					Shard: &topodatapb.Shard{
  9556  						SourceShards: []*topodatapb.Shard_SourceShard{
  9557  							{
  9558  								Uid:      1,
  9559  								Keyspace: "otherks",
  9560  								Shard:    "-80",
  9561  							},
  9562  						},
  9563  					},
  9564  				},
  9565  			},
  9566  			req: &vtctldatapb.SourceShardAddRequest{
  9567  				Keyspace:       "ks",
  9568  				Shard:          "-",
  9569  				Uid:            1,
  9570  				SourceKeyspace: "otherks",
  9571  				SourceShard:    "80-",
  9572  			},
  9573  			expected: &vtctldatapb.SourceShardAddResponse{},
  9574  			assertion: func(ctx context.Context, t *testing.T, ts *topo.Server) {
  9575  				si, err := ts.GetShard(ctx, "ks", "-")
  9576  				require.NoError(t, err, "failed to get shard ks/-")
  9577  				utils.MustMatch(t, []*topodatapb.Shard_SourceShard{
  9578  					{
  9579  						Uid:      1,
  9580  						Keyspace: "otherks",
  9581  						Shard:    "-80",
  9582  					},
  9583  				}, si.SourceShards, "SourceShards should not have changed")
  9584  			},
  9585  		},
  9586  		{
  9587  			name: "cannot lock keyspace",
  9588  			shards: []*vtctldatapb.Shard{
  9589  				{
  9590  					Keyspace: "ks",
  9591  					Name:     "-",
  9592  					Shard: &topodatapb.Shard{
  9593  						SourceShards: []*topodatapb.Shard_SourceShard{
  9594  							{
  9595  								Uid:      1,
  9596  								Keyspace: "otherks",
  9597  								Shard:    "-80",
  9598  							},
  9599  						},
  9600  					},
  9601  				},
  9602  			},
  9603  			topoIsLocked: true,
  9604  			req: &vtctldatapb.SourceShardAddRequest{
  9605  				Keyspace:       "ks",
  9606  				Shard:          "-",
  9607  				Uid:            1,
  9608  				SourceKeyspace: "otherks",
  9609  				SourceShard:    "80-",
  9610  			},
  9611  			shouldErr: true,
  9612  		},
  9613  	}
  9614  
  9615  	for _, tt := range tests {
  9616  		tt := tt
  9617  		t.Run(tt.name, func(t *testing.T) {
  9618  			t.Parallel()
  9619  
  9620  			ctx := context.Background()
  9621  			ts := memorytopo.NewServer("zone1")
  9622  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  9623  				return NewVtctldServer(ts)
  9624  			})
  9625  
  9626  			testutil.AddShards(ctx, t, ts, tt.shards...)
  9627  			if tt.topoIsLocked {
  9628  				lctx, unlock, lerr := ts.LockKeyspace(ctx, tt.req.Keyspace, "test lock")
  9629  				require.NoError(t, lerr, "failed to lock %s for test setup", tt.req.Keyspace)
  9630  
  9631  				defer func() {
  9632  					var err error
  9633  					unlock(&err)
  9634  					assert.NoError(t, err, "failed to unlock %s after test", tt.req.Keyspace)
  9635  				}()
  9636  
  9637  				ctx = lctx
  9638  			}
  9639  
  9640  			resp, err := vtctld.SourceShardAdd(ctx, tt.req)
  9641  			if tt.shouldErr {
  9642  				assert.Error(t, err)
  9643  				return
  9644  			}
  9645  
  9646  			require.NoError(t, err)
  9647  			utils.MustMatch(t, tt.expected, resp)
  9648  			if tt.assertion != nil {
  9649  				func() {
  9650  					t.Helper()
  9651  					tt.assertion(ctx, t, ts)
  9652  				}()
  9653  			}
  9654  		})
  9655  	}
  9656  }
  9657  
  9658  func TestSourceShardDelete(t *testing.T) {
  9659  	t.Parallel()
  9660  
  9661  	tests := []struct {
  9662  		name         string
  9663  		shards       []*vtctldatapb.Shard
  9664  		topoIsLocked bool
  9665  		req          *vtctldatapb.SourceShardDeleteRequest
  9666  		expected     *vtctldatapb.SourceShardDeleteResponse
  9667  		shouldErr    bool
  9668  		assertion    func(ctx context.Context, t *testing.T, ts *topo.Server)
  9669  	}{
  9670  		{
  9671  			name: "ok",
  9672  			shards: []*vtctldatapb.Shard{
  9673  				{
  9674  					Keyspace: "ks",
  9675  					Name:     "-",
  9676  					Shard: &topodatapb.Shard{
  9677  						SourceShards: []*topodatapb.Shard_SourceShard{
  9678  							{
  9679  								Uid:      1,
  9680  								Keyspace: "otherks",
  9681  								Shard:    "-80",
  9682  							},
  9683  						},
  9684  					},
  9685  				},
  9686  			},
  9687  			req: &vtctldatapb.SourceShardDeleteRequest{
  9688  				Keyspace: "ks",
  9689  				Shard:    "-",
  9690  				Uid:      1,
  9691  			},
  9692  			expected: &vtctldatapb.SourceShardDeleteResponse{
  9693  				Shard: &topodatapb.Shard{},
  9694  			},
  9695  		},
  9696  		{
  9697  			name: "no SourceShard with uid",
  9698  			shards: []*vtctldatapb.Shard{
  9699  				{
  9700  					Keyspace: "ks",
  9701  					Name:     "-",
  9702  					Shard: &topodatapb.Shard{
  9703  						SourceShards: []*topodatapb.Shard_SourceShard{
  9704  							{
  9705  								Uid:      1,
  9706  								Keyspace: "otherks",
  9707  								Shard:    "-80",
  9708  							},
  9709  						},
  9710  					},
  9711  				},
  9712  			},
  9713  			req: &vtctldatapb.SourceShardDeleteRequest{
  9714  				Keyspace: "ks",
  9715  				Shard:    "-",
  9716  				Uid:      2,
  9717  			},
  9718  			expected: &vtctldatapb.SourceShardDeleteResponse{},
  9719  			assertion: func(ctx context.Context, t *testing.T, ts *topo.Server) {
  9720  				si, err := ts.GetShard(ctx, "ks", "-")
  9721  				require.NoError(t, err, "failed to get shard ks/-")
  9722  				utils.MustMatch(t, []*topodatapb.Shard_SourceShard{
  9723  					{
  9724  						Uid:      1,
  9725  						Keyspace: "otherks",
  9726  						Shard:    "-80",
  9727  					},
  9728  				}, si.SourceShards, "SourceShards should not have changed")
  9729  			},
  9730  		},
  9731  		{
  9732  			name: "cannot lock keyspace",
  9733  			shards: []*vtctldatapb.Shard{
  9734  				{
  9735  					Keyspace: "ks",
  9736  					Name:     "-",
  9737  				},
  9738  			},
  9739  			topoIsLocked: true,
  9740  			req: &vtctldatapb.SourceShardDeleteRequest{
  9741  				Keyspace: "ks",
  9742  				Shard:    "-",
  9743  				Uid:      1,
  9744  			},
  9745  			shouldErr: true,
  9746  		},
  9747  	}
  9748  
  9749  	for _, tt := range tests {
  9750  		tt := tt
  9751  		t.Run(tt.name, func(t *testing.T) {
  9752  			t.Parallel()
  9753  
  9754  			ctx := context.Background()
  9755  			ts := memorytopo.NewServer("zone1")
  9756  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  9757  				return NewVtctldServer(ts)
  9758  			})
  9759  
  9760  			testutil.AddShards(ctx, t, ts, tt.shards...)
  9761  			if tt.topoIsLocked {
  9762  				lctx, unlock, lerr := ts.LockKeyspace(ctx, tt.req.Keyspace, "test lock")
  9763  				require.NoError(t, lerr, "failed to lock %s for test setup", tt.req.Keyspace)
  9764  
  9765  				defer func() {
  9766  					var err error
  9767  					unlock(&err)
  9768  					assert.NoError(t, err, "failed to unlock %s after test", tt.req.Keyspace)
  9769  				}()
  9770  
  9771  				ctx = lctx
  9772  			}
  9773  
  9774  			resp, err := vtctld.SourceShardDelete(ctx, tt.req)
  9775  			if tt.shouldErr {
  9776  				assert.Error(t, err)
  9777  				return
  9778  			}
  9779  
  9780  			require.NoError(t, err)
  9781  			utils.MustMatch(t, tt.expected, resp)
  9782  			if tt.assertion != nil {
  9783  				func() {
  9784  					t.Helper()
  9785  					tt.assertion(ctx, t, ts)
  9786  				}()
  9787  			}
  9788  		})
  9789  	}
  9790  }
  9791  
  9792  func TestStartReplication(t *testing.T) {
  9793  	t.Parallel()
  9794  
  9795  	tests := []struct {
  9796  		name      string
  9797  		cells     []string
  9798  		tablets   []*topodatapb.Tablet
  9799  		tmc       testutil.TabletManagerClient
  9800  		req       *vtctldatapb.StartReplicationRequest
  9801  		shouldErr bool
  9802  	}{
  9803  		{
  9804  			name:  "ok",
  9805  			cells: []string{"zone1"},
  9806  			tablets: []*topodatapb.Tablet{
  9807  				{
  9808  					Alias: &topodatapb.TabletAlias{
  9809  						Cell: "zone1",
  9810  						Uid:  100,
  9811  					},
  9812  					Keyspace: "testkeyspace",
  9813  					Shard:    "-",
  9814  					Type:     topodatapb.TabletType_REPLICA,
  9815  				}, {
  9816  					Alias: &topodatapb.TabletAlias{
  9817  						Cell: "zone1",
  9818  						Uid:  101,
  9819  					},
  9820  					Keyspace: "testkeyspace",
  9821  					Shard:    "-",
  9822  					Type:     topodatapb.TabletType_PRIMARY,
  9823  				},
  9824  			},
  9825  			tmc: testutil.TabletManagerClient{
  9826  				StartReplicationResults: map[string]error{
  9827  					"zone1-0000000100": nil,
  9828  				},
  9829  			},
  9830  			req: &vtctldatapb.StartReplicationRequest{
  9831  				TabletAlias: &topodatapb.TabletAlias{
  9832  					Cell: "zone1",
  9833  					Uid:  100,
  9834  				},
  9835  			},
  9836  		},
  9837  		{
  9838  			name:  "fail",
  9839  			cells: []string{"zone1"},
  9840  			tablets: []*topodatapb.Tablet{
  9841  				{
  9842  					Alias: &topodatapb.TabletAlias{
  9843  						Cell: "zone1",
  9844  						Uid:  100,
  9845  					},
  9846  					Keyspace: "testkeyspace",
  9847  					Shard:    "-",
  9848  					Type:     topodatapb.TabletType_REPLICA,
  9849  				}, {
  9850  					Alias: &topodatapb.TabletAlias{
  9851  						Cell: "zone1",
  9852  						Uid:  101,
  9853  					},
  9854  					Keyspace: "testkeyspace",
  9855  					Shard:    "-",
  9856  					Type:     topodatapb.TabletType_PRIMARY,
  9857  				},
  9858  			},
  9859  			tmc: testutil.TabletManagerClient{
  9860  				StartReplicationResults: map[string]error{
  9861  					"zone1-0000000100": assert.AnError,
  9862  				},
  9863  			},
  9864  			req: &vtctldatapb.StartReplicationRequest{
  9865  				TabletAlias: &topodatapb.TabletAlias{
  9866  					Cell: "zone1",
  9867  					Uid:  100,
  9868  				},
  9869  			},
  9870  			shouldErr: true,
  9871  		},
  9872  		{
  9873  			name:  "no such tablet",
  9874  			cells: []string{"zone1"},
  9875  			tablets: []*topodatapb.Tablet{
  9876  				{
  9877  					Alias: &topodatapb.TabletAlias{
  9878  						Cell: "zone1",
  9879  						Uid:  100,
  9880  					},
  9881  					Keyspace: "testkeyspace",
  9882  					Shard:    "-",
  9883  					Type:     topodatapb.TabletType_REPLICA,
  9884  				}, {
  9885  					Alias: &topodatapb.TabletAlias{
  9886  						Cell: "zone1",
  9887  						Uid:  101,
  9888  					},
  9889  					Keyspace: "testkeyspace",
  9890  					Shard:    "-",
  9891  					Type:     topodatapb.TabletType_PRIMARY,
  9892  				},
  9893  			},
  9894  			tmc: testutil.TabletManagerClient{
  9895  				StartReplicationResults: map[string]error{
  9896  					"zone1-0000000100": nil,
  9897  				},
  9898  			},
  9899  			req: &vtctldatapb.StartReplicationRequest{
  9900  				TabletAlias: &topodatapb.TabletAlias{
  9901  					Cell: "zone2",
  9902  					Uid:  200,
  9903  				},
  9904  			},
  9905  			shouldErr: true,
  9906  		},
  9907  		{
  9908  			name:  "bad request",
  9909  			cells: []string{"zone1"},
  9910  			tablets: []*topodatapb.Tablet{
  9911  				{
  9912  					Alias: &topodatapb.TabletAlias{
  9913  						Cell: "zone1",
  9914  						Uid:  100,
  9915  					},
  9916  					Keyspace: "testkeyspace",
  9917  					Shard:    "-",
  9918  					Type:     topodatapb.TabletType_REPLICA,
  9919  				}, {
  9920  					Alias: &topodatapb.TabletAlias{
  9921  						Cell: "zone1",
  9922  						Uid:  101,
  9923  					},
  9924  					Keyspace: "testkeyspace",
  9925  					Shard:    "-",
  9926  					Type:     topodatapb.TabletType_PRIMARY,
  9927  				},
  9928  			},
  9929  			req:       &vtctldatapb.StartReplicationRequest{},
  9930  			shouldErr: true,
  9931  		},
  9932  	}
  9933  
  9934  	ctx := context.Background()
  9935  	for _, tt := range tests {
  9936  		tt := tt
  9937  		t.Run(tt.name, func(t *testing.T) {
  9938  			t.Parallel()
  9939  
  9940  			ts := memorytopo.NewServer(tt.cells...)
  9941  			defer ts.Close()
  9942  
  9943  			testutil.AddTablets(ctx, t, ts, &testutil.AddTabletOptions{
  9944  				AlsoSetShardPrimary: true,
  9945  			}, tt.tablets...)
  9946  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, &tt.tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
  9947  				return NewVtctldServer(ts)
  9948  			})
  9949  
  9950  			_, err := vtctld.StartReplication(ctx, tt.req)
  9951  			if tt.shouldErr {
  9952  				assert.Error(t, err)
  9953  				return
  9954  			}
  9955  
  9956  			assert.NoError(t, err)
  9957  		})
  9958  	}
  9959  }
  9960  
  9961  func TestStopReplication(t *testing.T) {
  9962  	t.Parallel()
  9963  
  9964  	tests := []struct {
  9965  		name      string
  9966  		cells     []string
  9967  		tablets   []*topodatapb.Tablet
  9968  		tmc       testutil.TabletManagerClient
  9969  		req       *vtctldatapb.StopReplicationRequest
  9970  		shouldErr bool
  9971  	}{
  9972  		{
  9973  			name:  "ok",
  9974  			cells: []string{"zone1"},
  9975  			tablets: []*topodatapb.Tablet{
  9976  				{
  9977  					Alias: &topodatapb.TabletAlias{
  9978  						Cell: "zone1",
  9979  						Uid:  100,
  9980  					},
  9981  					Keyspace: "testkeyspace",
  9982  					Shard:    "-",
  9983  					Type:     topodatapb.TabletType_REPLICA,
  9984  				},
  9985  			},
  9986  			tmc: testutil.TabletManagerClient{
  9987  				StopReplicationResults: map[string]error{
  9988  					"zone1-0000000100": nil,
  9989  				},
  9990  			},
  9991  			req: &vtctldatapb.StopReplicationRequest{
  9992  				TabletAlias: &topodatapb.TabletAlias{
  9993  					Cell: "zone1",
  9994  					Uid:  100,
  9995  				},
  9996  			},
  9997  		},
  9998  		{
  9999  			name:  "fail",
 10000  			cells: []string{"zone1"},
 10001  			tablets: []*topodatapb.Tablet{
 10002  				{
 10003  					Alias: &topodatapb.TabletAlias{
 10004  						Cell: "zone1",
 10005  						Uid:  100,
 10006  					},
 10007  					Keyspace: "testkeyspace",
 10008  					Shard:    "-",
 10009  					Type:     topodatapb.TabletType_REPLICA,
 10010  				},
 10011  			},
 10012  			tmc: testutil.TabletManagerClient{
 10013  				StopReplicationResults: map[string]error{
 10014  					"zone1-0000000100": assert.AnError,
 10015  				},
 10016  			},
 10017  			req: &vtctldatapb.StopReplicationRequest{
 10018  				TabletAlias: &topodatapb.TabletAlias{
 10019  					Cell: "zone1",
 10020  					Uid:  100,
 10021  				},
 10022  			},
 10023  			shouldErr: true,
 10024  		},
 10025  		{
 10026  			name:  "no such tablet",
 10027  			cells: []string{"zone1"},
 10028  			tablets: []*topodatapb.Tablet{
 10029  				{
 10030  					Alias: &topodatapb.TabletAlias{
 10031  						Cell: "zone1",
 10032  						Uid:  100,
 10033  					},
 10034  					Keyspace: "testkeyspace",
 10035  					Shard:    "-",
 10036  					Type:     topodatapb.TabletType_REPLICA,
 10037  				},
 10038  			},
 10039  			tmc: testutil.TabletManagerClient{
 10040  				StopReplicationResults: map[string]error{
 10041  					"zone1-0000000100": nil,
 10042  				},
 10043  			},
 10044  			req: &vtctldatapb.StopReplicationRequest{
 10045  				TabletAlias: &topodatapb.TabletAlias{
 10046  					Cell: "zone2",
 10047  					Uid:  200,
 10048  				},
 10049  			},
 10050  			shouldErr: true,
 10051  		},
 10052  		{
 10053  			name:  "bad request",
 10054  			cells: []string{"zone1"},
 10055  			tablets: []*topodatapb.Tablet{
 10056  				{
 10057  					Alias: &topodatapb.TabletAlias{
 10058  						Cell: "zone1",
 10059  						Uid:  100,
 10060  					},
 10061  					Keyspace: "testkeyspace",
 10062  					Shard:    "-",
 10063  					Type:     topodatapb.TabletType_REPLICA,
 10064  				},
 10065  			},
 10066  			req:       &vtctldatapb.StopReplicationRequest{},
 10067  			shouldErr: true,
 10068  		},
 10069  	}
 10070  
 10071  	ctx := context.Background()
 10072  	for _, tt := range tests {
 10073  		tt := tt
 10074  		t.Run(tt.name, func(t *testing.T) {
 10075  			t.Parallel()
 10076  
 10077  			ts := memorytopo.NewServer(tt.cells...)
 10078  			defer ts.Close()
 10079  
 10080  			testutil.AddTablets(ctx, t, ts, nil, tt.tablets...)
 10081  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, &tt.tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
 10082  				return NewVtctldServer(ts)
 10083  			})
 10084  
 10085  			_, err := vtctld.StopReplication(ctx, tt.req)
 10086  			if tt.shouldErr {
 10087  				assert.Error(t, err)
 10088  				return
 10089  			}
 10090  
 10091  			assert.NoError(t, err)
 10092  		})
 10093  	}
 10094  }
 10095  
 10096  func TestTabletExternallyReparented(t *testing.T) {
 10097  	t.Parallel()
 10098  
 10099  	tests := []struct {
 10100  		name         string
 10101  		topo         []*topodatapb.Tablet
 10102  		topoErr      error
 10103  		tmcHasNoTopo bool
 10104  		req          *vtctldatapb.TabletExternallyReparentedRequest
 10105  		expected     *vtctldatapb.TabletExternallyReparentedResponse
 10106  		shouldErr    bool
 10107  		expectedTopo []*topodatapb.Tablet
 10108  	}{
 10109  		{
 10110  			name: "success",
 10111  			topo: []*topodatapb.Tablet{
 10112  				{
 10113  					Alias: &topodatapb.TabletAlias{
 10114  						Cell: "zone1",
 10115  						Uid:  100,
 10116  					},
 10117  					Type:     topodatapb.TabletType_PRIMARY,
 10118  					Keyspace: "testkeyspace",
 10119  					Shard:    "-",
 10120  					PrimaryTermStartTime: &vttime.Time{
 10121  						Seconds: 1000,
 10122  					},
 10123  				},
 10124  				{
 10125  					Alias: &topodatapb.TabletAlias{
 10126  						Cell: "zone2",
 10127  						Uid:  200,
 10128  					},
 10129  					Type:     topodatapb.TabletType_REPLICA,
 10130  					Keyspace: "testkeyspace",
 10131  					Shard:    "-",
 10132  				},
 10133  				{
 10134  					Alias: &topodatapb.TabletAlias{
 10135  						Cell: "zone3",
 10136  						Uid:  300,
 10137  					},
 10138  					Type:     topodatapb.TabletType_REPLICA,
 10139  					Keyspace: "testkeyspace",
 10140  					Shard:    "-",
 10141  				},
 10142  			},
 10143  			topoErr: nil,
 10144  			req: &vtctldatapb.TabletExternallyReparentedRequest{
 10145  				Tablet: &topodatapb.TabletAlias{
 10146  					Cell: "zone2",
 10147  					Uid:  200,
 10148  				},
 10149  			},
 10150  			expected: &vtctldatapb.TabletExternallyReparentedResponse{
 10151  				Keyspace: "testkeyspace",
 10152  				Shard:    "-",
 10153  				NewPrimary: &topodatapb.TabletAlias{
 10154  					Cell: "zone2",
 10155  					Uid:  200,
 10156  				},
 10157  				OldPrimary: &topodatapb.TabletAlias{
 10158  					Cell: "zone1",
 10159  					Uid:  100,
 10160  				},
 10161  			},
 10162  			shouldErr: false,
 10163  			// NOTE: this seems weird, right? Why is the old primary still a
 10164  			// PRIMARY, and why is the new primary's term start 0,0? Well, our
 10165  			// test client implementation is a little incomplete. See
 10166  			// ./testutil/test_tmclient.go for reference.
 10167  			expectedTopo: []*topodatapb.Tablet{
 10168  				{
 10169  					Alias: &topodatapb.TabletAlias{
 10170  						Cell: "zone1",
 10171  						Uid:  100,
 10172  					},
 10173  					Type:     topodatapb.TabletType_PRIMARY,
 10174  					Keyspace: "testkeyspace",
 10175  					Shard:    "-",
 10176  					PrimaryTermStartTime: &vttime.Time{
 10177  						Seconds: 1000,
 10178  					},
 10179  				},
 10180  				{
 10181  					Alias: &topodatapb.TabletAlias{
 10182  						Cell: "zone2",
 10183  						Uid:  200,
 10184  					},
 10185  					Type:                 topodatapb.TabletType_UNKNOWN,
 10186  					Keyspace:             "testkeyspace",
 10187  					Shard:                "-",
 10188  					PrimaryTermStartTime: &vttime.Time{},
 10189  				},
 10190  				{
 10191  					Alias: &topodatapb.TabletAlias{
 10192  						Cell: "zone3",
 10193  						Uid:  300,
 10194  					},
 10195  					Type:     topodatapb.TabletType_REPLICA,
 10196  					Keyspace: "testkeyspace",
 10197  					Shard:    "-",
 10198  				},
 10199  			},
 10200  		},
 10201  		{
 10202  			name:    "tablet is nil",
 10203  			topo:    nil,
 10204  			topoErr: nil,
 10205  			req: &vtctldatapb.TabletExternallyReparentedRequest{
 10206  				Tablet: nil,
 10207  			},
 10208  			expected:     nil,
 10209  			shouldErr:    true,
 10210  			expectedTopo: nil,
 10211  		},
 10212  		{
 10213  			name: "topo is down",
 10214  			topo: []*topodatapb.Tablet{
 10215  				{
 10216  					Alias: &topodatapb.TabletAlias{
 10217  						Cell: "zone1",
 10218  						Uid:  100,
 10219  					},
 10220  					Type:     topodatapb.TabletType_PRIMARY,
 10221  					Keyspace: "testkeyspace",
 10222  					Shard:    "-",
 10223  					PrimaryTermStartTime: &vttime.Time{
 10224  						Seconds: 1000,
 10225  					},
 10226  				},
 10227  				{
 10228  					Alias: &topodatapb.TabletAlias{
 10229  						Cell: "zone2",
 10230  						Uid:  200,
 10231  					},
 10232  					Type:     topodatapb.TabletType_REPLICA,
 10233  					Keyspace: "testkeyspace",
 10234  					Shard:    "-",
 10235  				},
 10236  				{
 10237  					Alias: &topodatapb.TabletAlias{
 10238  						Cell: "zone3",
 10239  						Uid:  300,
 10240  					},
 10241  					Type:     topodatapb.TabletType_REPLICA,
 10242  					Keyspace: "testkeyspace",
 10243  					Shard:    "-",
 10244  				},
 10245  			},
 10246  			topoErr: assert.AnError,
 10247  			req: &vtctldatapb.TabletExternallyReparentedRequest{
 10248  				Tablet: &topodatapb.TabletAlias{
 10249  					Cell: "zone2",
 10250  					Uid:  200,
 10251  				},
 10252  			},
 10253  			expected:  nil,
 10254  			shouldErr: true,
 10255  			expectedTopo: []*topodatapb.Tablet{
 10256  				{
 10257  					Alias: &topodatapb.TabletAlias{
 10258  						Cell: "zone1",
 10259  						Uid:  100,
 10260  					},
 10261  					Type:     topodatapb.TabletType_PRIMARY,
 10262  					Keyspace: "testkeyspace",
 10263  					Shard:    "-",
 10264  					PrimaryTermStartTime: &vttime.Time{
 10265  						Seconds: 1000,
 10266  					},
 10267  				},
 10268  				{
 10269  					Alias: &topodatapb.TabletAlias{
 10270  						Cell: "zone2",
 10271  						Uid:  200,
 10272  					},
 10273  					Type:     topodatapb.TabletType_REPLICA,
 10274  					Keyspace: "testkeyspace",
 10275  					Shard:    "-",
 10276  				},
 10277  				{
 10278  					Alias: &topodatapb.TabletAlias{
 10279  						Cell: "zone3",
 10280  						Uid:  300,
 10281  					},
 10282  					Type:     topodatapb.TabletType_REPLICA,
 10283  					Keyspace: "testkeyspace",
 10284  					Shard:    "-",
 10285  				},
 10286  			},
 10287  		},
 10288  		{
 10289  			name: "tablet is already primary",
 10290  			topo: []*topodatapb.Tablet{
 10291  				{
 10292  					Alias: &topodatapb.TabletAlias{
 10293  						Cell: "zone1",
 10294  						Uid:  100,
 10295  					},
 10296  					Type:     topodatapb.TabletType_PRIMARY,
 10297  					Keyspace: "testkeyspace",
 10298  					Shard:    "-",
 10299  					PrimaryTermStartTime: &vttime.Time{
 10300  						Seconds: 1000,
 10301  					},
 10302  				},
 10303  				{
 10304  					Alias: &topodatapb.TabletAlias{
 10305  						Cell: "zone2",
 10306  						Uid:  200,
 10307  					},
 10308  					Type:     topodatapb.TabletType_REPLICA,
 10309  					Keyspace: "testkeyspace",
 10310  					Shard:    "-",
 10311  				},
 10312  				{
 10313  					Alias: &topodatapb.TabletAlias{
 10314  						Cell: "zone3",
 10315  						Uid:  300,
 10316  					},
 10317  					Type:     topodatapb.TabletType_REPLICA,
 10318  					Keyspace: "testkeyspace",
 10319  					Shard:    "-",
 10320  				},
 10321  			},
 10322  			topoErr: nil,
 10323  			req: &vtctldatapb.TabletExternallyReparentedRequest{
 10324  				Tablet: &topodatapb.TabletAlias{
 10325  					Cell: "zone1",
 10326  					Uid:  100,
 10327  				},
 10328  			},
 10329  			expected: &vtctldatapb.TabletExternallyReparentedResponse{
 10330  				Keyspace: "testkeyspace",
 10331  				Shard:    "-",
 10332  				NewPrimary: &topodatapb.TabletAlias{
 10333  					Cell: "zone1",
 10334  					Uid:  100,
 10335  				},
 10336  				OldPrimary: &topodatapb.TabletAlias{
 10337  					Cell: "zone1",
 10338  					Uid:  100,
 10339  				},
 10340  			},
 10341  			shouldErr: false,
 10342  			expectedTopo: []*topodatapb.Tablet{
 10343  				{
 10344  					Alias: &topodatapb.TabletAlias{
 10345  						Cell: "zone1",
 10346  						Uid:  100,
 10347  					},
 10348  					Type:     topodatapb.TabletType_PRIMARY,
 10349  					Keyspace: "testkeyspace",
 10350  					Shard:    "-",
 10351  					PrimaryTermStartTime: &vttime.Time{
 10352  						Seconds: 1000,
 10353  					},
 10354  				},
 10355  				{
 10356  					Alias: &topodatapb.TabletAlias{
 10357  						Cell: "zone2",
 10358  						Uid:  200,
 10359  					},
 10360  					Type:     topodatapb.TabletType_REPLICA,
 10361  					Keyspace: "testkeyspace",
 10362  					Shard:    "-",
 10363  				},
 10364  				{
 10365  					Alias: &topodatapb.TabletAlias{
 10366  						Cell: "zone3",
 10367  						Uid:  300,
 10368  					},
 10369  					Type:     topodatapb.TabletType_REPLICA,
 10370  					Keyspace: "testkeyspace",
 10371  					Shard:    "-",
 10372  				},
 10373  			},
 10374  		},
 10375  		{
 10376  			name: "cannot change tablet type",
 10377  			topo: []*topodatapb.Tablet{
 10378  				{
 10379  					Alias: &topodatapb.TabletAlias{
 10380  						Cell: "zone1",
 10381  						Uid:  100,
 10382  					},
 10383  					Type:     topodatapb.TabletType_PRIMARY,
 10384  					Keyspace: "testkeyspace",
 10385  					Shard:    "-",
 10386  					PrimaryTermStartTime: &vttime.Time{
 10387  						Seconds: 1000,
 10388  					},
 10389  				},
 10390  				{
 10391  					Alias: &topodatapb.TabletAlias{
 10392  						Cell: "zone2",
 10393  						Uid:  200,
 10394  					},
 10395  					Type:     topodatapb.TabletType_REPLICA,
 10396  					Keyspace: "testkeyspace",
 10397  					Shard:    "-",
 10398  				},
 10399  				{
 10400  					Alias: &topodatapb.TabletAlias{
 10401  						Cell: "zone3",
 10402  						Uid:  300,
 10403  					},
 10404  					Type:     topodatapb.TabletType_REPLICA,
 10405  					Keyspace: "testkeyspace",
 10406  					Shard:    "-",
 10407  				},
 10408  			},
 10409  			topoErr:      nil,
 10410  			tmcHasNoTopo: true,
 10411  			req: &vtctldatapb.TabletExternallyReparentedRequest{
 10412  				Tablet: &topodatapb.TabletAlias{
 10413  					Cell: "zone2",
 10414  					Uid:  200,
 10415  				},
 10416  			},
 10417  			expected:  nil,
 10418  			shouldErr: true,
 10419  			expectedTopo: []*topodatapb.Tablet{
 10420  				{
 10421  					Alias: &topodatapb.TabletAlias{
 10422  						Cell: "zone1",
 10423  						Uid:  100,
 10424  					},
 10425  					Type:     topodatapb.TabletType_PRIMARY,
 10426  					Keyspace: "testkeyspace",
 10427  					Shard:    "-",
 10428  					PrimaryTermStartTime: &vttime.Time{
 10429  						Seconds: 1000,
 10430  					},
 10431  				},
 10432  				{
 10433  					Alias: &topodatapb.TabletAlias{
 10434  						Cell: "zone2",
 10435  						Uid:  200,
 10436  					},
 10437  					Type:     topodatapb.TabletType_REPLICA,
 10438  					Keyspace: "testkeyspace",
 10439  					Shard:    "-",
 10440  				},
 10441  				{
 10442  					Alias: &topodatapb.TabletAlias{
 10443  						Cell: "zone3",
 10444  						Uid:  300,
 10445  					},
 10446  					Type:     topodatapb.TabletType_REPLICA,
 10447  					Keyspace: "testkeyspace",
 10448  					Shard:    "-",
 10449  				},
 10450  			},
 10451  		},
 10452  	}
 10453  
 10454  	for _, tt := range tests {
 10455  		tt := tt
 10456  
 10457  		t.Run(tt.name, func(t *testing.T) {
 10458  			t.Parallel()
 10459  
 10460  			cells := []string{"zone1", "zone2", "zone3"}
 10461  
 10462  			ctx := context.Background()
 10463  			ts, topofactory := memorytopo.NewServerAndFactory(cells...)
 10464  			tmc := testutil.TabletManagerClient{
 10465  				TopoServer: ts,
 10466  			}
 10467  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, &tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
 10468  				return NewVtctldServer(ts)
 10469  			})
 10470  
 10471  			if tt.tmcHasNoTopo {
 10472  				// For certain test cases, we want specifically just the
 10473  				// ChangeType call to fail, which is why we rely on a separate
 10474  				// bool rather than using tt.topoErr.
 10475  				tmc.TopoServer = nil
 10476  			}
 10477  
 10478  			testutil.AddTablets(ctx, t, ts, &testutil.AddTabletOptions{
 10479  				AlsoSetShardPrimary: true,
 10480  			}, tt.topo...)
 10481  
 10482  			if tt.topoErr != nil {
 10483  				topofactory.SetError(tt.topoErr)
 10484  			}
 10485  
 10486  			if tt.expectedTopo != nil {
 10487  				// assert on expectedTopo state when we've fininished the rest
 10488  				// of the test.
 10489  				defer func() {
 10490  					topofactory.SetError(nil)
 10491  
 10492  					ctx, cancel := context.WithTimeout(ctx, time.Millisecond*10)
 10493  					defer cancel()
 10494  
 10495  					resp, err := vtctld.GetTablets(ctx, &vtctldatapb.GetTabletsRequest{})
 10496  					require.NoError(t, err, "cannot get all tablets in the topo")
 10497  					testutil.AssertSameTablets(t, tt.expectedTopo, resp.Tablets)
 10498  				}()
 10499  			}
 10500  
 10501  			resp, err := vtctld.TabletExternallyReparented(ctx, tt.req)
 10502  			if tt.shouldErr {
 10503  				assert.Error(t, err)
 10504  				return
 10505  			}
 10506  
 10507  			assert.NoError(t, err)
 10508  			utils.MustMatch(t, tt.expected, resp)
 10509  		})
 10510  	}
 10511  }
 10512  
 10513  func TestUpdateCellInfo(t *testing.T) {
 10514  	t.Parallel()
 10515  
 10516  	ctx := context.Background()
 10517  	tests := []struct {
 10518  		name           string
 10519  		cells          map[string]*topodatapb.CellInfo
 10520  		forceTopoError bool
 10521  		req            *vtctldatapb.UpdateCellInfoRequest
 10522  		expected       *vtctldatapb.UpdateCellInfoResponse
 10523  		shouldErr      bool
 10524  	}{
 10525  		{
 10526  			name: "update",
 10527  			cells: map[string]*topodatapb.CellInfo{
 10528  				"zone1": {
 10529  					ServerAddress: ":1111",
 10530  					Root:          "/zone1",
 10531  				},
 10532  			},
 10533  			req: &vtctldatapb.UpdateCellInfoRequest{
 10534  				Name: "zone1",
 10535  				CellInfo: &topodatapb.CellInfo{
 10536  					ServerAddress: ":0101",
 10537  					Root:          "/zones/zone1",
 10538  				},
 10539  			},
 10540  			expected: &vtctldatapb.UpdateCellInfoResponse{
 10541  				Name: "zone1",
 10542  				CellInfo: &topodatapb.CellInfo{
 10543  					ServerAddress: ":0101",
 10544  					Root:          "/zones/zone1",
 10545  				},
 10546  			},
 10547  		},
 10548  		{
 10549  			name: "partial update",
 10550  			cells: map[string]*topodatapb.CellInfo{
 10551  				"zone1": {
 10552  					ServerAddress: ":1111",
 10553  					Root:          "/zone1",
 10554  				},
 10555  			},
 10556  			req: &vtctldatapb.UpdateCellInfoRequest{
 10557  				Name: "zone1",
 10558  				CellInfo: &topodatapb.CellInfo{
 10559  					Root: "/zones/zone1",
 10560  				},
 10561  			},
 10562  			expected: &vtctldatapb.UpdateCellInfoResponse{
 10563  				Name: "zone1",
 10564  				CellInfo: &topodatapb.CellInfo{
 10565  					ServerAddress: ":1111",
 10566  					Root:          "/zones/zone1",
 10567  				},
 10568  			},
 10569  		},
 10570  		{
 10571  			name: "no update",
 10572  			cells: map[string]*topodatapb.CellInfo{
 10573  				"zone1": {
 10574  					ServerAddress: ":1111",
 10575  					Root:          "/zone1",
 10576  				},
 10577  			},
 10578  			req: &vtctldatapb.UpdateCellInfoRequest{
 10579  				Name: "zone1",
 10580  				CellInfo: &topodatapb.CellInfo{
 10581  					Root: "/zone1",
 10582  				},
 10583  			},
 10584  			expected: &vtctldatapb.UpdateCellInfoResponse{
 10585  				Name: "zone1",
 10586  				CellInfo: &topodatapb.CellInfo{
 10587  					ServerAddress: ":1111",
 10588  					Root:          "/zone1",
 10589  				},
 10590  			},
 10591  		},
 10592  		{
 10593  			name: "cell not found",
 10594  			cells: map[string]*topodatapb.CellInfo{
 10595  				"zone1": {
 10596  					ServerAddress: ":1111",
 10597  					Root:          "/zone1",
 10598  				},
 10599  			},
 10600  			req: &vtctldatapb.UpdateCellInfoRequest{
 10601  				Name: "zone404",
 10602  				CellInfo: &topodatapb.CellInfo{
 10603  					ServerAddress: ":4040",
 10604  					Root:          "/zone404",
 10605  				},
 10606  			},
 10607  			expected: &vtctldatapb.UpdateCellInfoResponse{
 10608  				Name: "zone404",
 10609  				CellInfo: &topodatapb.CellInfo{
 10610  					ServerAddress: ":4040",
 10611  					Root:          "/zone404",
 10612  				},
 10613  			},
 10614  		},
 10615  		{
 10616  			name: "cannot update",
 10617  			cells: map[string]*topodatapb.CellInfo{
 10618  				"zone1": {
 10619  					ServerAddress: ":1111",
 10620  					Root:          "/zone1",
 10621  				},
 10622  			},
 10623  			forceTopoError: true,
 10624  			req: &vtctldatapb.UpdateCellInfoRequest{
 10625  				Name: "zone1",
 10626  				CellInfo: &topodatapb.CellInfo{
 10627  					Root: "/zone1",
 10628  				},
 10629  			},
 10630  			expected:  nil,
 10631  			shouldErr: true,
 10632  		},
 10633  	}
 10634  
 10635  	for _, tt := range tests {
 10636  		tt := tt
 10637  		t.Run(tt.name, func(t *testing.T) {
 10638  			t.Parallel()
 10639  
 10640  			ts, factory := memorytopo.NewServerAndFactory()
 10641  			for name, cell := range tt.cells {
 10642  				err := ts.CreateCellInfo(ctx, name, cell)
 10643  				require.NoError(t, err, "failed to create cell %s: %+v for test", name, cell)
 10644  			}
 10645  
 10646  			if tt.forceTopoError {
 10647  				factory.SetError(fmt.Errorf("%w: topo down for testing", assert.AnError))
 10648  			}
 10649  
 10650  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
 10651  				return NewVtctldServer(ts)
 10652  			})
 10653  			resp, err := vtctld.UpdateCellInfo(ctx, tt.req)
 10654  			if tt.shouldErr {
 10655  				assert.Error(t, err)
 10656  				return
 10657  			}
 10658  
 10659  			require.NoError(t, err)
 10660  			utils.MustMatch(t, tt.expected, resp)
 10661  		})
 10662  	}
 10663  }
 10664  
 10665  func TestUpdateCellsAlias(t *testing.T) {
 10666  	t.Parallel()
 10667  
 10668  	ctx := context.Background()
 10669  	tests := []struct {
 10670  		name      string
 10671  		cells     []string
 10672  		aliases   map[string][]string
 10673  		req       *vtctldatapb.UpdateCellsAliasRequest
 10674  		expected  *vtctldatapb.UpdateCellsAliasResponse
 10675  		shouldErr bool
 10676  	}{
 10677  		{
 10678  			name: "remove one cell",
 10679  			aliases: map[string][]string{
 10680  				"zone": {
 10681  					"zone1",
 10682  					"zone2",
 10683  					"zone3",
 10684  				},
 10685  			},
 10686  			req: &vtctldatapb.UpdateCellsAliasRequest{
 10687  				Name: "zone",
 10688  				CellsAlias: &topodatapb.CellsAlias{
 10689  					Cells: []string{"zone1", "zone2"},
 10690  				},
 10691  			},
 10692  			expected: &vtctldatapb.UpdateCellsAliasResponse{
 10693  				Name: "zone",
 10694  				CellsAlias: &topodatapb.CellsAlias{
 10695  					Cells: []string{"zone1", "zone2"},
 10696  				},
 10697  			},
 10698  		},
 10699  		{
 10700  			name:  "add one cell",
 10701  			cells: []string{"zone4"}, // all other cells get created via the aliases map
 10702  			aliases: map[string][]string{
 10703  				"zone": {
 10704  					"zone1",
 10705  					"zone2",
 10706  					"zone3",
 10707  				},
 10708  			},
 10709  			req: &vtctldatapb.UpdateCellsAliasRequest{
 10710  				Name: "zone",
 10711  				CellsAlias: &topodatapb.CellsAlias{
 10712  					Cells: []string{
 10713  						"zone1",
 10714  						"zone2",
 10715  						"zone3",
 10716  						"zone4",
 10717  					},
 10718  				},
 10719  			},
 10720  			expected: &vtctldatapb.UpdateCellsAliasResponse{
 10721  				Name: "zone",
 10722  				CellsAlias: &topodatapb.CellsAlias{
 10723  					Cells: []string{
 10724  						"zone1",
 10725  						"zone2",
 10726  						"zone3",
 10727  						"zone4",
 10728  					},
 10729  				},
 10730  			},
 10731  		},
 10732  		{
 10733  			name:  "alias does not exist",
 10734  			cells: []string{"zone1", "zone2"},
 10735  			req: &vtctldatapb.UpdateCellsAliasRequest{
 10736  				Name: "zone",
 10737  				CellsAlias: &topodatapb.CellsAlias{
 10738  					Cells: []string{"zone1", "zone2"},
 10739  				},
 10740  			},
 10741  			expected: &vtctldatapb.UpdateCellsAliasResponse{
 10742  				Name: "zone",
 10743  				CellsAlias: &topodatapb.CellsAlias{
 10744  					Cells: []string{"zone1", "zone2"},
 10745  				},
 10746  			},
 10747  		},
 10748  		{
 10749  			name: "invalid alias list",
 10750  			aliases: map[string][]string{
 10751  				"zone_a": {
 10752  					"zone1",
 10753  					"zone2",
 10754  				},
 10755  				"zone_b": {
 10756  					"zone3",
 10757  					"zone4",
 10758  				},
 10759  			},
 10760  			req: &vtctldatapb.UpdateCellsAliasRequest{
 10761  				Name: "zone_a",
 10762  				CellsAlias: &topodatapb.CellsAlias{
 10763  					Cells: []string{
 10764  						"zone1",
 10765  						"zone2",
 10766  						"zone3", // this is invalid because it belongs to alias zone_b
 10767  					},
 10768  				},
 10769  			},
 10770  			shouldErr: true,
 10771  		},
 10772  	}
 10773  
 10774  	for _, tt := range tests {
 10775  		tt := tt
 10776  		t.Run(tt.name, func(t *testing.T) {
 10777  			t.Parallel()
 10778  
 10779  			ts := memorytopo.NewServer(tt.cells...)
 10780  			for name, cells := range tt.aliases {
 10781  				for _, cell := range cells {
 10782  					// We use UpdateCellInfoFields rather than CreateCellInfo
 10783  					// for the update-or-create behavior.
 10784  					err := ts.UpdateCellInfoFields(ctx, cell, func(ci *topodatapb.CellInfo) error {
 10785  						ci.Root = "/" + cell
 10786  						ci.ServerAddress = cell + ":8080"
 10787  						return nil
 10788  					})
 10789  					require.NoError(t, err, "failed to create cell %v", cell)
 10790  				}
 10791  
 10792  				err := ts.CreateCellsAlias(ctx, name, &topodatapb.CellsAlias{
 10793  					Cells: cells,
 10794  				})
 10795  				require.NoError(t, err, "failed to create cell alias %v (cells = %v)", name, cells)
 10796  			}
 10797  
 10798  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
 10799  				return NewVtctldServer(ts)
 10800  			})
 10801  			resp, err := vtctld.UpdateCellsAlias(ctx, tt.req)
 10802  			if tt.shouldErr {
 10803  				assert.Error(t, err)
 10804  				return
 10805  			}
 10806  
 10807  			require.NoError(t, err)
 10808  			utils.MustMatch(t, tt.expected, resp)
 10809  		})
 10810  	}
 10811  }
 10812  
 10813  func TestValidate(t *testing.T) {
 10814  	t.Parallel()
 10815  
 10816  	ctx := context.Background()
 10817  	ts := memorytopo.NewServer("zone1", "zone2", "zone3")
 10818  	tablets := []*topodatapb.Tablet{
 10819  		{
 10820  			Keyspace: "ks1",
 10821  			Shard:    "-",
 10822  			Type:     topodatapb.TabletType_PRIMARY,
 10823  			Alias: &topodatapb.TabletAlias{
 10824  				Cell: "zone1",
 10825  				Uid:  100,
 10826  			},
 10827  			Hostname: "ks1-00-00-primary",
 10828  		},
 10829  		{
 10830  			Keyspace: "ks1",
 10831  			Shard:    "-",
 10832  			Type:     topodatapb.TabletType_REPLICA,
 10833  			Alias: &topodatapb.TabletAlias{
 10834  				Cell: "zone1",
 10835  				Uid:  101,
 10836  			},
 10837  			Hostname: "ks1-00-00-replica",
 10838  		},
 10839  		{
 10840  			Keyspace: "ks2",
 10841  			Shard:    "-80",
 10842  			Type:     topodatapb.TabletType_PRIMARY,
 10843  			Alias: &topodatapb.TabletAlias{
 10844  				Cell: "zone1",
 10845  				Uid:  102,
 10846  			},
 10847  			Hostname: "ks2-00-80-primary",
 10848  		},
 10849  		{
 10850  			Keyspace: "ks2",
 10851  			Shard:    "-80",
 10852  			Type:     topodatapb.TabletType_REPLICA,
 10853  			Alias: &topodatapb.TabletAlias{
 10854  				Cell: "zone2",
 10855  				Uid:  200,
 10856  			},
 10857  			Hostname: "ks2-00-80-replica1",
 10858  		},
 10859  		{
 10860  			Keyspace: "ks2",
 10861  			Shard:    "-80",
 10862  			Type:     topodatapb.TabletType_REPLICA,
 10863  			Alias: &topodatapb.TabletAlias{
 10864  				Cell: "zone3",
 10865  				Uid:  300,
 10866  			},
 10867  			Hostname: "ks2-00-80-replica2",
 10868  		},
 10869  		{
 10870  			Keyspace: "ks2",
 10871  			Shard:    "80-",
 10872  			Type:     topodatapb.TabletType_PRIMARY,
 10873  			Alias: &topodatapb.TabletAlias{
 10874  				Cell: "zone3",
 10875  				Uid:  301,
 10876  			},
 10877  			Hostname: "ks2-80-00-primary",
 10878  		},
 10879  		{
 10880  			Keyspace: "ks2",
 10881  			Shard:    "80-",
 10882  			Type:     topodatapb.TabletType_REPLICA,
 10883  			Alias: &topodatapb.TabletAlias{
 10884  				Cell: "zone2",
 10885  				Uid:  201,
 10886  			},
 10887  			Hostname: "ks2-80-00-replica1",
 10888  		},
 10889  		{
 10890  			Keyspace: "ks2",
 10891  			Shard:    "80-",
 10892  			Type:     topodatapb.TabletType_RDONLY,
 10893  			Alias: &topodatapb.TabletAlias{
 10894  				Cell: "zone1",
 10895  				Uid:  103,
 10896  			},
 10897  			Hostname: "ks2-80-00-rdonly1",
 10898  		},
 10899  	}
 10900  	testutil.AddTablets(ctx, t, ts, &testutil.AddTabletOptions{
 10901  		AlsoSetShardPrimary:  true,
 10902  		ForceSetShardPrimary: true,
 10903  		SkipShardCreation:    false,
 10904  	}, tablets...)
 10905  	vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, nil, func(ts *topo.Server) vtctlservicepb.VtctldServer {
 10906  		return NewVtctldServer(ts)
 10907  	})
 10908  
 10909  	resp, err := vtctld.Validate(ctx, &vtctldatapb.ValidateRequest{
 10910  		PingTablets: false,
 10911  	})
 10912  	require.NoError(t, err)
 10913  	assert.Equal(t, &vtctldatapb.ValidateResponse{
 10914  		ResultsByKeyspace: map[string]*vtctldatapb.ValidateKeyspaceResponse{
 10915  			"ks1": {
 10916  				ResultsByShard: map[string]*vtctldatapb.ValidateShardResponse{
 10917  					"-": {},
 10918  				},
 10919  			},
 10920  			"ks2": {
 10921  				ResultsByShard: map[string]*vtctldatapb.ValidateShardResponse{
 10922  					"-80": {},
 10923  					"80-": {},
 10924  				},
 10925  			},
 10926  		},
 10927  	}, resp)
 10928  }
 10929  
 10930  func TestValidateSchemaKeyspace(t *testing.T) {
 10931  	ctx := context.Background()
 10932  	ts := memorytopo.NewServer("zone1", "zone2", "zone3")
 10933  	tmc := testutil.TabletManagerClient{
 10934  		GetSchemaResults: map[string]struct {
 10935  			Schema *tabletmanagerdatapb.SchemaDefinition
 10936  			Error  error
 10937  		}{},
 10938  	}
 10939  	testutil.AddKeyspace(ctx, t, ts, &vtctldatapb.Keyspace{
 10940  		Name: "ks1",
 10941  		Keyspace: &topodatapb.Keyspace{
 10942  			KeyspaceType: topodatapb.KeyspaceType_NORMAL,
 10943  		},
 10944  	})
 10945  	testutil.AddKeyspace(ctx, t, ts, &vtctldatapb.Keyspace{
 10946  		Name: "ks2",
 10947  		Keyspace: &topodatapb.Keyspace{
 10948  			KeyspaceType: topodatapb.KeyspaceType_NORMAL,
 10949  		},
 10950  	})
 10951  	tablets := []*topodatapb.Tablet{
 10952  		{
 10953  			Keyspace: "ks1",
 10954  			Shard:    "-",
 10955  			Type:     topodatapb.TabletType_PRIMARY,
 10956  			Alias: &topodatapb.TabletAlias{
 10957  				Cell: "zone1",
 10958  				Uid:  100,
 10959  			},
 10960  			Hostname: "ks1-00-00-primary",
 10961  		},
 10962  		{
 10963  			Keyspace: "ks1",
 10964  			Shard:    "-",
 10965  			Type:     topodatapb.TabletType_REPLICA,
 10966  			Alias: &topodatapb.TabletAlias{
 10967  				Cell: "zone1",
 10968  				Uid:  101,
 10969  			},
 10970  			Hostname: "ks1-00-00-replica",
 10971  		},
 10972  		// ks2 shard -80 has no Primary intentionally for testing
 10973  		{
 10974  			Keyspace: "ks2",
 10975  			Shard:    "-80",
 10976  			Type:     topodatapb.TabletType_REPLICA,
 10977  			Alias: &topodatapb.TabletAlias{
 10978  				Cell: "zone1",
 10979  				Uid:  102,
 10980  			},
 10981  			Hostname: "ks2-00-80-replica0",
 10982  		},
 10983  		{
 10984  			Keyspace: "ks2",
 10985  			Shard:    "-80",
 10986  			Type:     topodatapb.TabletType_REPLICA,
 10987  			Alias: &topodatapb.TabletAlias{
 10988  				Cell: "zone2",
 10989  				Uid:  200,
 10990  			},
 10991  			Hostname: "ks2-00-80-replica1",
 10992  		},
 10993  		//
 10994  		{
 10995  			Keyspace: "ks2",
 10996  			Shard:    "80-",
 10997  			Type:     topodatapb.TabletType_PRIMARY,
 10998  			Alias: &topodatapb.TabletAlias{
 10999  				Cell: "zone1",
 11000  				Uid:  103,
 11001  			},
 11002  			Hostname: "ks2-80-00-primary1",
 11003  		},
 11004  		{
 11005  			Keyspace: "ks2",
 11006  			Shard:    "80-",
 11007  			Type:     topodatapb.TabletType_REPLICA,
 11008  			Alias: &topodatapb.TabletAlias{
 11009  				Cell: "zone2",
 11010  				Uid:  201,
 11011  			},
 11012  			Hostname: "ks2-80-00-replica1",
 11013  		},
 11014  	}
 11015  	testutil.AddTablets(ctx, t, ts, &testutil.AddTabletOptions{
 11016  		AlsoSetShardPrimary:  true,
 11017  		ForceSetShardPrimary: true,
 11018  		SkipShardCreation:    false,
 11019  	}, tablets...)
 11020  
 11021  	vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, &tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
 11022  		return NewVtctldServer(ts)
 11023  	})
 11024  
 11025  	schema1 := &tabletmanagerdatapb.SchemaDefinition{
 11026  		TableDefinitions: []*tabletmanagerdatapb.TableDefinition{{
 11027  			Name:              "not_in_vschema",
 11028  			Columns:           []string{"c1", "c2"},
 11029  			PrimaryKeyColumns: []string{"c1"},
 11030  			Fields:            sqltypes.MakeTestFields("c1|c2", "int64|int64"),
 11031  		}},
 11032  	}
 11033  
 11034  	schema2 := &tabletmanagerdatapb.SchemaDefinition{
 11035  		TableDefinitions: []*tabletmanagerdatapb.TableDefinition{
 11036  			{
 11037  				Name:    "t1",
 11038  				Columns: []string{"c1"},
 11039  			},
 11040  			{
 11041  				Name:    "t2",
 11042  				Columns: []string{"c1"},
 11043  			},
 11044  			{
 11045  				Name:    "t3",
 11046  				Columns: []string{"c1"},
 11047  			},
 11048  		},
 11049  	}
 11050  
 11051  	// we need to run this on each test case or they will pollute each other
 11052  	setupSchema := func(tablet *topodatapb.TabletAlias, schema *tabletmanagerdatapb.SchemaDefinition) {
 11053  		tmc.GetSchemaResults[topoproto.TabletAliasString(tablet)] = struct {
 11054  			Schema *tabletmanagerdatapb.SchemaDefinition
 11055  			Error  error
 11056  		}{
 11057  			Schema: schema,
 11058  			Error:  nil,
 11059  		}
 11060  	}
 11061  
 11062  	tests := []*struct {
 11063  		name      string
 11064  		req       *vtctldatapb.ValidateSchemaKeyspaceRequest
 11065  		expected  *vtctldatapb.ValidateSchemaKeyspaceResponse
 11066  		setup     func()
 11067  		shouldErr bool
 11068  	}{
 11069  		{
 11070  			name: "valid schemas",
 11071  			req: &vtctldatapb.ValidateSchemaKeyspaceRequest{
 11072  				Keyspace: "ks1",
 11073  			},
 11074  			expected: &vtctldatapb.ValidateSchemaKeyspaceResponse{
 11075  				Results: []string{},
 11076  				ResultsByShard: map[string]*vtctldatapb.ValidateShardResponse{
 11077  					"-": {Results: []string{}},
 11078  				},
 11079  			},
 11080  			setup: func() {
 11081  				setupSchema(&topodatapb.TabletAlias{
 11082  					Cell: "zone1",
 11083  					Uid:  100,
 11084  				}, schema1)
 11085  				setupSchema(&topodatapb.TabletAlias{
 11086  					Cell: "zone1",
 11087  					Uid:  101,
 11088  				}, schema1)
 11089  			},
 11090  			shouldErr: false,
 11091  		},
 11092  		{
 11093  			name: "different schemas",
 11094  			req: &vtctldatapb.ValidateSchemaKeyspaceRequest{
 11095  				Keyspace: "ks1",
 11096  			},
 11097  			expected: &vtctldatapb.ValidateSchemaKeyspaceResponse{
 11098  				Results: []string{"zone1-0000000100 has an extra table named not_in_vschema"},
 11099  				ResultsByShard: map[string]*vtctldatapb.ValidateShardResponse{
 11100  					"-": {Results: []string{"zone1-0000000100 has an extra table named not_in_vschema"}},
 11101  				},
 11102  			},
 11103  			setup: func() {
 11104  				setupSchema(&topodatapb.TabletAlias{
 11105  					Cell: "zone1",
 11106  					Uid:  100,
 11107  				}, schema1)
 11108  				setupSchema(&topodatapb.TabletAlias{
 11109  					Cell: "zone1",
 11110  					Uid:  101,
 11111  				}, schema2)
 11112  			},
 11113  			shouldErr: false,
 11114  		},
 11115  		{
 11116  			name: "skip-no-primary: no primary",
 11117  			req: &vtctldatapb.ValidateSchemaKeyspaceRequest{
 11118  				Keyspace:      "ks2",
 11119  				SkipNoPrimary: false,
 11120  			},
 11121  			expected: &vtctldatapb.ValidateSchemaKeyspaceResponse{
 11122  				Results: []string{"no primary in shard ks2/-80"},
 11123  				ResultsByShard: map[string]*vtctldatapb.ValidateShardResponse{
 11124  					"-80": {Results: []string{"no primary in shard ks2/-80"}},
 11125  					"80-": {Results: []string{}},
 11126  				},
 11127  			},
 11128  			setup: func() {
 11129  				setupSchema(&topodatapb.TabletAlias{
 11130  					Cell: "zone1",
 11131  					Uid:  103,
 11132  				}, schema1)
 11133  				setupSchema(&topodatapb.TabletAlias{
 11134  					Cell: "zone2",
 11135  					Uid:  201,
 11136  				}, schema1)
 11137  			},
 11138  			shouldErr: false,
 11139  		},
 11140  	}
 11141  
 11142  	for _, tt := range tests {
 11143  		t.Run(tt.name, func(t *testing.T) {
 11144  			tt.setup()
 11145  			resp, err := vtctld.ValidateSchemaKeyspace(ctx, tt.req)
 11146  			if tt.shouldErr {
 11147  				assert.Error(t, err)
 11148  				return
 11149  			}
 11150  
 11151  			assert.NoError(t, err)
 11152  			utils.MustMatch(t, tt.expected, resp)
 11153  		})
 11154  	}
 11155  }
 11156  
 11157  func TestValidateVersionKeyspace(t *testing.T) {
 11158  	ctx := context.Background()
 11159  	ts := memorytopo.NewServer("zone1", "zone2")
 11160  	tmc := testutil.TabletManagerClient{
 11161  		GetSchemaResults: map[string]struct {
 11162  			Schema *tabletmanagerdatapb.SchemaDefinition
 11163  			Error  error
 11164  		}{},
 11165  	}
 11166  	testutil.AddKeyspace(ctx, t, ts, &vtctldatapb.Keyspace{
 11167  		Name: "ks1",
 11168  		Keyspace: &topodatapb.Keyspace{
 11169  			KeyspaceType: topodatapb.KeyspaceType_NORMAL,
 11170  		},
 11171  	})
 11172  	testutil.AddKeyspace(ctx, t, ts, &vtctldatapb.Keyspace{
 11173  		Name: "ks2",
 11174  		Keyspace: &topodatapb.Keyspace{
 11175  			KeyspaceType: topodatapb.KeyspaceType_NORMAL,
 11176  		},
 11177  	})
 11178  	tablets := []*topodatapb.Tablet{
 11179  		{
 11180  			Keyspace: "ks1",
 11181  			Shard:    "-",
 11182  			Type:     topodatapb.TabletType_PRIMARY,
 11183  			Alias: &topodatapb.TabletAlias{
 11184  				Cell: "zone1",
 11185  				Uid:  100,
 11186  			},
 11187  			Hostname: "primary",
 11188  		},
 11189  		{
 11190  			Keyspace: "ks1",
 11191  			Shard:    "-",
 11192  			Type:     topodatapb.TabletType_REPLICA,
 11193  			Alias: &topodatapb.TabletAlias{
 11194  				Cell: "zone1",
 11195  				Uid:  101,
 11196  			},
 11197  			Hostname: "replica",
 11198  		},
 11199  	}
 11200  	testutil.AddTablets(ctx, t, ts, &testutil.AddTabletOptions{
 11201  		AlsoSetShardPrimary:  true,
 11202  		ForceSetShardPrimary: true,
 11203  		SkipShardCreation:    false,
 11204  	}, tablets...)
 11205  
 11206  	vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, &tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
 11207  		return NewVtctldServer(ts)
 11208  	})
 11209  
 11210  	tests := []*struct {
 11211  		name      string
 11212  		req       *vtctldatapb.ValidateVersionKeyspaceRequest
 11213  		expected  *vtctldatapb.ValidateVersionKeyspaceResponse
 11214  		setup     func()
 11215  		shouldErr bool
 11216  	}{
 11217  		{
 11218  			name: "valid versions",
 11219  			req: &vtctldatapb.ValidateVersionKeyspaceRequest{
 11220  				Keyspace: "ks1",
 11221  			},
 11222  			expected: &vtctldatapb.ValidateVersionKeyspaceResponse{
 11223  				Results: []string{},
 11224  				ResultsByShard: map[string]*vtctldatapb.ValidateShardResponse{
 11225  					"-": {Results: []string{}},
 11226  				},
 11227  			},
 11228  			setup: func() {
 11229  				addrVersionMap := map[string]string{
 11230  					"primary:0": "version1",
 11231  					"replica:0": "version1",
 11232  				}
 11233  				SetVersionFunc(testutil.MockGetVersionFromTablet(addrVersionMap))
 11234  			},
 11235  			shouldErr: false,
 11236  		},
 11237  		{
 11238  			name: "different versions",
 11239  			req: &vtctldatapb.ValidateVersionKeyspaceRequest{
 11240  				Keyspace: "ks1",
 11241  			},
 11242  			expected: &vtctldatapb.ValidateVersionKeyspaceResponse{
 11243  				Results: []string{"primary zone1-0000000100 version version:\"version1\" is different than replica zone1-0000000101 version version:\"version2\""},
 11244  				ResultsByShard: map[string]*vtctldatapb.ValidateShardResponse{
 11245  					"-": {Results: []string{"primary zone1-0000000100 version version:\"version1\" is different than replica zone1-0000000101 version version:\"version2\""}},
 11246  				},
 11247  			},
 11248  			setup: func() {
 11249  				addrVersionMap := map[string]string{
 11250  					"primary:0": "version1",
 11251  					"replica:0": "version2",
 11252  				}
 11253  				SetVersionFunc(testutil.MockGetVersionFromTablet(addrVersionMap))
 11254  			},
 11255  			shouldErr: false,
 11256  		},
 11257  	}
 11258  
 11259  	for _, tt := range tests {
 11260  		t.Run(tt.name, func(t *testing.T) {
 11261  			tt.setup()
 11262  			resp, err := vtctld.ValidateVersionKeyspace(ctx, tt.req)
 11263  			if tt.shouldErr {
 11264  				assert.Error(t, err)
 11265  				return
 11266  			}
 11267  
 11268  			assert.NoError(t, err)
 11269  			utils.MustMatch(t, tt.expected, resp)
 11270  		})
 11271  	}
 11272  }
 11273  
 11274  func TestValidateVersionShard(t *testing.T) {
 11275  	t.Parallel()
 11276  
 11277  	ctx := context.Background()
 11278  	ts := memorytopo.NewServer("zone1", "zone2")
 11279  	tmc := testutil.TabletManagerClient{
 11280  		GetSchemaResults: map[string]struct {
 11281  			Schema *tabletmanagerdatapb.SchemaDefinition
 11282  			Error  error
 11283  		}{},
 11284  	}
 11285  	testutil.AddKeyspace(ctx, t, ts, &vtctldatapb.Keyspace{
 11286  		Name: "ks",
 11287  		Keyspace: &topodatapb.Keyspace{
 11288  			KeyspaceType: topodatapb.KeyspaceType_NORMAL,
 11289  		},
 11290  	})
 11291  
 11292  	tablets := []*topodatapb.Tablet{
 11293  		{
 11294  			Keyspace: "ks",
 11295  			Shard:    "-",
 11296  			Type:     topodatapb.TabletType_PRIMARY,
 11297  			Alias: &topodatapb.TabletAlias{
 11298  				Cell: "zone1",
 11299  				Uid:  100,
 11300  			},
 11301  			Hostname: "primary",
 11302  		},
 11303  		{
 11304  			Keyspace: "ks",
 11305  			Shard:    "-",
 11306  			Type:     topodatapb.TabletType_REPLICA,
 11307  			Alias: &topodatapb.TabletAlias{
 11308  				Cell: "zone1",
 11309  				Uid:  101,
 11310  			},
 11311  			Hostname: "replica",
 11312  		},
 11313  	}
 11314  	testutil.AddTablets(ctx, t, ts, &testutil.AddTabletOptions{
 11315  		AlsoSetShardPrimary:  true,
 11316  		ForceSetShardPrimary: true,
 11317  		SkipShardCreation:    false,
 11318  	}, tablets...)
 11319  
 11320  	vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, ts, &tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
 11321  		return NewVtctldServer(ts)
 11322  	})
 11323  
 11324  	tests := []*struct {
 11325  		name      string
 11326  		req       *vtctldatapb.ValidateVersionShardRequest
 11327  		expected  *vtctldatapb.ValidateVersionShardResponse
 11328  		setup     func()
 11329  		shouldErr bool
 11330  	}{
 11331  		{
 11332  			name: "valid versions",
 11333  			req: &vtctldatapb.ValidateVersionShardRequest{
 11334  				Keyspace: "ks",
 11335  				Shard:    "-",
 11336  			},
 11337  			expected: &vtctldatapb.ValidateVersionShardResponse{
 11338  				Results: []string{},
 11339  			},
 11340  			setup: func() {
 11341  				addrVersionMap := map[string]string{
 11342  					"primary:0": "version1",
 11343  					"replica:0": "version1",
 11344  				}
 11345  				SetVersionFunc(testutil.MockGetVersionFromTablet(addrVersionMap))
 11346  			},
 11347  			shouldErr: false,
 11348  		},
 11349  		{
 11350  			name: "different versions",
 11351  			req: &vtctldatapb.ValidateVersionShardRequest{
 11352  				Keyspace: "ks",
 11353  				Shard:    "-",
 11354  			},
 11355  			expected: &vtctldatapb.ValidateVersionShardResponse{
 11356  				Results: []string{"primary zone1-0000000100 version version1 is different than replica zone1-0000000101 version version:\"version2\""},
 11357  			},
 11358  			setup: func() {
 11359  				addrVersionMap := map[string]string{
 11360  					"primary:0": "version1",
 11361  					"replica:0": "version2",
 11362  				}
 11363  				SetVersionFunc(testutil.MockGetVersionFromTablet(addrVersionMap))
 11364  			},
 11365  			shouldErr: false,
 11366  		},
 11367  	}
 11368  
 11369  	for _, tt := range tests {
 11370  		curT := tt
 11371  		t.Run(tt.name, func(t *testing.T) {
 11372  			t.Parallel()
 11373  
 11374  			curT.setup()
 11375  			resp, err := vtctld.ValidateVersionShard(ctx, curT.req)
 11376  			if curT.shouldErr {
 11377  				assert.Error(t, err)
 11378  				return
 11379  			}
 11380  
 11381  			assert.NoError(t, err)
 11382  			utils.MustMatch(t, curT.expected, resp)
 11383  		})
 11384  	}
 11385  }
 11386  
 11387  func TestValidateShard(t *testing.T) {
 11388  	t.Parallel()
 11389  
 11390  	type testcase struct {
 11391  		name      string
 11392  		ts        *topo.Server
 11393  		tmc       *testutil.TabletManagerClient
 11394  		setup     func(t *testing.T, tt *testcase)
 11395  		req       *vtctldatapb.ValidateShardRequest
 11396  		expected  *vtctldatapb.ValidateShardResponse
 11397  		shouldErr bool
 11398  	}
 11399  
 11400  	ctx := context.Background()
 11401  	tests := []*testcase{
 11402  		{
 11403  			name: "ok",
 11404  			ts:   memorytopo.NewServer("zone1"),
 11405  			tmc:  nil,
 11406  			setup: func(t *testing.T, tt *testcase) {
 11407  				tablets := []*topodatapb.Tablet{
 11408  					{
 11409  						Keyspace: "ks1",
 11410  						Shard:    "-",
 11411  						Alias: &topodatapb.TabletAlias{
 11412  							Cell: "zone1",
 11413  							Uid:  100,
 11414  						},
 11415  						Type:     topodatapb.TabletType_PRIMARY,
 11416  						Hostname: "ks1-primary",
 11417  					},
 11418  					{
 11419  						Keyspace: "ks1",
 11420  						Shard:    "-",
 11421  						Alias: &topodatapb.TabletAlias{
 11422  							Cell: "zone1",
 11423  							Uid:  101,
 11424  						},
 11425  						Type:     topodatapb.TabletType_REPLICA,
 11426  						Hostname: "ks1-replica",
 11427  					},
 11428  				}
 11429  				testutil.AddTablets(ctx, t, tt.ts, &testutil.AddTabletOptions{
 11430  					AlsoSetShardPrimary: true,
 11431  				}, tablets...)
 11432  			},
 11433  			req: &vtctldatapb.ValidateShardRequest{
 11434  				Keyspace: "ks1",
 11435  				Shard:    "-",
 11436  			},
 11437  			expected: &vtctldatapb.ValidateShardResponse{},
 11438  		},
 11439  		{
 11440  			name: "no shard",
 11441  			ts:   memorytopo.NewServer("zone1"),
 11442  			tmc:  nil,
 11443  			setup: func(t *testing.T, tt *testcase) {
 11444  				tablets := []*topodatapb.Tablet{
 11445  					{
 11446  						Keyspace: "ks1",
 11447  						Shard:    "-",
 11448  						Alias: &topodatapb.TabletAlias{
 11449  							Cell: "zone1",
 11450  							Uid:  100,
 11451  						},
 11452  						Type:     topodatapb.TabletType_PRIMARY,
 11453  						Hostname: "ks1-primary",
 11454  					},
 11455  					{
 11456  						Keyspace: "ks1",
 11457  						Shard:    "-",
 11458  						Alias: &topodatapb.TabletAlias{
 11459  							Cell: "zone1",
 11460  							Uid:  101,
 11461  						},
 11462  						Type:     topodatapb.TabletType_REPLICA,
 11463  						Hostname: "ks1-replica",
 11464  					},
 11465  				}
 11466  				testutil.AddTablets(ctx, t, tt.ts, &testutil.AddTabletOptions{
 11467  					SkipShardCreation: true,
 11468  				}, tablets...)
 11469  			},
 11470  			req: &vtctldatapb.ValidateShardRequest{
 11471  				Keyspace: "ks1",
 11472  				Shard:    "-",
 11473  			},
 11474  			expected: &vtctldatapb.ValidateShardResponse{
 11475  				Results: []string{
 11476  					"TopologyServer.GetShard(ks1, -) failed: node doesn't exist: keyspaces/ks1/shards/-/Shard",
 11477  				},
 11478  			},
 11479  		},
 11480  		{
 11481  			name: "no primary in shard",
 11482  			ts:   memorytopo.NewServer("zone1"),
 11483  			tmc:  nil,
 11484  			setup: func(t *testing.T, tt *testcase) {
 11485  				tablets := []*topodatapb.Tablet{
 11486  					{
 11487  						Keyspace: "ks1",
 11488  						Shard:    "-",
 11489  						Alias: &topodatapb.TabletAlias{
 11490  							Cell: "zone1",
 11491  							Uid:  101,
 11492  						},
 11493  						Type:     topodatapb.TabletType_REPLICA,
 11494  						Hostname: "ks1-replica",
 11495  					},
 11496  				}
 11497  				testutil.AddTablets(ctx, t, tt.ts, nil, tablets...)
 11498  			},
 11499  			req: &vtctldatapb.ValidateShardRequest{
 11500  				Keyspace: "ks1",
 11501  				Shard:    "-",
 11502  			},
 11503  			expected: &vtctldatapb.ValidateShardResponse{
 11504  				Results: []string{"no primary for shard ks1/-"},
 11505  			},
 11506  		},
 11507  		{
 11508  			name: "two primaries in shard",
 11509  			ts:   memorytopo.NewServer("zone1"),
 11510  			tmc:  nil,
 11511  			setup: func(t *testing.T, tt *testcase) {
 11512  				tablets := []*topodatapb.Tablet{
 11513  					{
 11514  						Keyspace: "ks1",
 11515  						Shard:    "-",
 11516  						Alias: &topodatapb.TabletAlias{
 11517  							Cell: "zone1",
 11518  							Uid:  100,
 11519  						},
 11520  						Type:     topodatapb.TabletType_PRIMARY,
 11521  						Hostname: "ks1-primary",
 11522  					},
 11523  					{
 11524  						Keyspace: "ks1",
 11525  						Shard:    "-",
 11526  						Alias: &topodatapb.TabletAlias{
 11527  							Cell: "zone1",
 11528  							Uid:  101,
 11529  						},
 11530  						Type:     topodatapb.TabletType_PRIMARY,
 11531  						Hostname: "ks1-primary2",
 11532  					},
 11533  				}
 11534  				testutil.AddTablets(ctx, t, tt.ts, &testutil.AddTabletOptions{
 11535  					AlsoSetShardPrimary:  true,
 11536  					ForceSetShardPrimary: true,
 11537  				}, tablets...)
 11538  			},
 11539  			req: &vtctldatapb.ValidateShardRequest{
 11540  				Keyspace: "ks1",
 11541  				Shard:    "-",
 11542  			},
 11543  			expected: &vtctldatapb.ValidateShardResponse{
 11544  				Results: []string{
 11545  					"shard ks1/- already has primary zone1-0000000100 but found other primary zone1-0000000101",
 11546  					"primary mismatch for shard ks1/-: found zone1-0000000100, expected zone1-0000000101",
 11547  				},
 11548  			},
 11549  		},
 11550  		{
 11551  			name: "ping_tablets/ok",
 11552  			ts:   memorytopo.NewServer("zone1"),
 11553  			tmc: &testutil.TabletManagerClient{
 11554  				GetReplicasResults: map[string]struct {
 11555  					Replicas []string
 11556  					Error    error
 11557  				}{
 11558  					"zone1-0000000100": {
 11559  						Replicas: []string{"11.21.31.41", "12.22.32.42"},
 11560  					},
 11561  				},
 11562  				PingResults: map[string]error{
 11563  					"zone1-0000000100": nil,
 11564  					"zone1-0000000101": nil,
 11565  					"zone1-0000000102": nil,
 11566  				},
 11567  			},
 11568  			setup: func(t *testing.T, tt *testcase) {
 11569  				tablets := []*topodatapb.Tablet{
 11570  					{
 11571  						Keyspace: "ks1",
 11572  						Shard:    "-",
 11573  						Alias: &topodatapb.TabletAlias{
 11574  							Cell: "zone1",
 11575  							Uid:  100,
 11576  						},
 11577  						Type:     topodatapb.TabletType_PRIMARY,
 11578  						Hostname: "ks1-primary",
 11579  						// note: we don't actually use this IP, we just need to
 11580  						// resolve _something_ for the testcase. The IPs are
 11581  						// used by the validateReplication function to
 11582  						// disambiguate/deduplicate tablets.
 11583  						MysqlHostname: "10.20.30.40",
 11584  					},
 11585  					{
 11586  						Keyspace: "ks1",
 11587  						Shard:    "-",
 11588  						Alias: &topodatapb.TabletAlias{
 11589  							Cell: "zone1",
 11590  							Uid:  101,
 11591  						},
 11592  						Type:          topodatapb.TabletType_REPLICA,
 11593  						Hostname:      "ks1-replica",
 11594  						MysqlHostname: "11.21.31.41",
 11595  					},
 11596  					{
 11597  						Keyspace: "ks1",
 11598  						Shard:    "-",
 11599  						Alias: &topodatapb.TabletAlias{
 11600  							Cell: "zone1",
 11601  							Uid:  102,
 11602  						},
 11603  						Type:          topodatapb.TabletType_RDONLY,
 11604  						Hostname:      "ks1-rdonly",
 11605  						MysqlHostname: "12.22.32.42",
 11606  					},
 11607  				}
 11608  				testutil.AddTablets(ctx, t, tt.ts, &testutil.AddTabletOptions{
 11609  					AlsoSetShardPrimary: true,
 11610  				}, tablets...)
 11611  			},
 11612  			req: &vtctldatapb.ValidateShardRequest{
 11613  				Keyspace:    "ks1",
 11614  				Shard:       "-",
 11615  				PingTablets: true,
 11616  			},
 11617  			expected: &vtctldatapb.ValidateShardResponse{},
 11618  		},
 11619  		{
 11620  			name: "ping_tablets/GetReplicas failed",
 11621  			ts:   memorytopo.NewServer("zone1"),
 11622  			tmc: &testutil.TabletManagerClient{
 11623  				GetReplicasResults: map[string]struct {
 11624  					Replicas []string
 11625  					Error    error
 11626  				}{
 11627  					"zone1-0000000100": {
 11628  						Error: assert.AnError,
 11629  					},
 11630  				},
 11631  				PingResults: map[string]error{
 11632  					"zone1-0000000100": nil,
 11633  					"zone1-0000000101": nil,
 11634  					"zone1-0000000102": nil,
 11635  				},
 11636  			},
 11637  			setup: func(t *testing.T, tt *testcase) {
 11638  				tablets := []*topodatapb.Tablet{
 11639  					{
 11640  						Keyspace: "ks1",
 11641  						Shard:    "-",
 11642  						Alias: &topodatapb.TabletAlias{
 11643  							Cell: "zone1",
 11644  							Uid:  100,
 11645  						},
 11646  						Type:     topodatapb.TabletType_PRIMARY,
 11647  						Hostname: "ks1-primary",
 11648  						// note: we don't actually use this IP, we just need to
 11649  						// resolve _something_ for the testcase. The IPs are
 11650  						// used by the validateReplication function to
 11651  						// disambiguate/deduplicate tablets.
 11652  						MysqlHostname: "10.20.30.40",
 11653  					},
 11654  					{
 11655  						Keyspace: "ks1",
 11656  						Shard:    "-",
 11657  						Alias: &topodatapb.TabletAlias{
 11658  							Cell: "zone1",
 11659  							Uid:  101,
 11660  						},
 11661  						Type:          topodatapb.TabletType_REPLICA,
 11662  						Hostname:      "ks1-replica",
 11663  						MysqlHostname: "11.21.31.41",
 11664  					},
 11665  					{
 11666  						Keyspace: "ks1",
 11667  						Shard:    "-",
 11668  						Alias: &topodatapb.TabletAlias{
 11669  							Cell: "zone1",
 11670  							Uid:  102,
 11671  						},
 11672  						Type:          topodatapb.TabletType_RDONLY,
 11673  						Hostname:      "ks1-rdonly",
 11674  						MysqlHostname: "12.22.32.42",
 11675  					},
 11676  				}
 11677  				testutil.AddTablets(ctx, t, tt.ts, &testutil.AddTabletOptions{
 11678  					AlsoSetShardPrimary: true,
 11679  				}, tablets...)
 11680  			},
 11681  			req: &vtctldatapb.ValidateShardRequest{
 11682  				Keyspace:    "ks1",
 11683  				Shard:       "-",
 11684  				PingTablets: true,
 11685  			},
 11686  			expected: &vtctldatapb.ValidateShardResponse{
 11687  				Results: []string{"GetReplicas(Tablet{zone1-0000000100}) failed: assert.AnError general error for testing"},
 11688  			},
 11689  		},
 11690  		{
 11691  			name: "ping_tablets/no replicas",
 11692  			ts:   memorytopo.NewServer("zone1"),
 11693  			tmc: &testutil.TabletManagerClient{
 11694  				GetReplicasResults: map[string]struct {
 11695  					Replicas []string
 11696  					Error    error
 11697  				}{
 11698  					"zone1-0000000100": {
 11699  						Replicas: []string{},
 11700  					},
 11701  				},
 11702  				PingResults: map[string]error{
 11703  					"zone1-0000000100": nil,
 11704  					"zone1-0000000101": nil,
 11705  					"zone1-0000000102": nil,
 11706  				},
 11707  			},
 11708  			setup: func(t *testing.T, tt *testcase) {
 11709  				tablets := []*topodatapb.Tablet{
 11710  					{
 11711  						Keyspace: "ks1",
 11712  						Shard:    "-",
 11713  						Alias: &topodatapb.TabletAlias{
 11714  							Cell: "zone1",
 11715  							Uid:  100,
 11716  						},
 11717  						Type:     topodatapb.TabletType_PRIMARY,
 11718  						Hostname: "ks1-primary",
 11719  						// note: we don't actually use this IP, we just need to
 11720  						// resolve _something_ for the testcase. The IPs are
 11721  						// used by the validateReplication function to
 11722  						// disambiguate/deduplicate tablets.
 11723  						MysqlHostname: "10.20.30.40",
 11724  					},
 11725  					{
 11726  						Keyspace: "ks1",
 11727  						Shard:    "-",
 11728  						Alias: &topodatapb.TabletAlias{
 11729  							Cell: "zone1",
 11730  							Uid:  101,
 11731  						},
 11732  						Type:          topodatapb.TabletType_REPLICA,
 11733  						Hostname:      "ks1-replica",
 11734  						MysqlHostname: "11.21.31.41",
 11735  					},
 11736  					{
 11737  						Keyspace: "ks1",
 11738  						Shard:    "-",
 11739  						Alias: &topodatapb.TabletAlias{
 11740  							Cell: "zone1",
 11741  							Uid:  102,
 11742  						},
 11743  						Type:          topodatapb.TabletType_RDONLY,
 11744  						Hostname:      "ks1-rdonly",
 11745  						MysqlHostname: "12.22.32.42",
 11746  					},
 11747  				}
 11748  				testutil.AddTablets(ctx, t, tt.ts, &testutil.AddTabletOptions{
 11749  					AlsoSetShardPrimary: true,
 11750  				}, tablets...)
 11751  			},
 11752  			req: &vtctldatapb.ValidateShardRequest{
 11753  				Keyspace:    "ks1",
 11754  				Shard:       "-",
 11755  				PingTablets: true,
 11756  			},
 11757  			expected: &vtctldatapb.ValidateShardResponse{
 11758  				Results: []string{"no replicas of tablet zone1-0000000100 found"},
 11759  			},
 11760  		},
 11761  		{
 11762  			name: "ping_tablets/orphaned replica",
 11763  			ts:   memorytopo.NewServer("zone1"),
 11764  			tmc: &testutil.TabletManagerClient{
 11765  				GetReplicasResults: map[string]struct {
 11766  					Replicas []string
 11767  					Error    error
 11768  				}{
 11769  					"zone1-0000000100": {
 11770  						Replicas: []string{"11.21.31.41", "100.200.200.100" /* not in set of tablet addrs below */},
 11771  					},
 11772  				},
 11773  				PingResults: map[string]error{
 11774  					"zone1-0000000100": nil,
 11775  					"zone1-0000000101": nil,
 11776  					"zone1-0000000102": nil,
 11777  				},
 11778  			},
 11779  			setup: func(t *testing.T, tt *testcase) {
 11780  				tablets := []*topodatapb.Tablet{
 11781  					{
 11782  						Keyspace: "ks1",
 11783  						Shard:    "-",
 11784  						Alias: &topodatapb.TabletAlias{
 11785  							Cell: "zone1",
 11786  							Uid:  100,
 11787  						},
 11788  						Type:     topodatapb.TabletType_PRIMARY,
 11789  						Hostname: "ks1-primary",
 11790  						// note: we don't actually use this IP, we just need to
 11791  						// resolve _something_ for the testcase. The IPs are
 11792  						// used by the validateReplication function to
 11793  						// disambiguate/deduplicate tablets.
 11794  						MysqlHostname: "10.20.30.40",
 11795  					},
 11796  					{
 11797  						Keyspace: "ks1",
 11798  						Shard:    "-",
 11799  						Alias: &topodatapb.TabletAlias{
 11800  							Cell: "zone1",
 11801  							Uid:  101,
 11802  						},
 11803  						Type:          topodatapb.TabletType_REPLICA,
 11804  						Hostname:      "ks1-replica",
 11805  						MysqlHostname: "11.21.31.41",
 11806  					},
 11807  					{
 11808  						Keyspace: "ks1",
 11809  						Shard:    "-",
 11810  						Alias: &topodatapb.TabletAlias{
 11811  							Cell: "zone1",
 11812  							Uid:  102,
 11813  						},
 11814  						Type:          topodatapb.TabletType_RDONLY,
 11815  						Hostname:      "ks1-rdonly",
 11816  						MysqlHostname: "12.22.32.42",
 11817  					},
 11818  				}
 11819  				testutil.AddTablets(ctx, t, tt.ts, &testutil.AddTabletOptions{
 11820  					AlsoSetShardPrimary: true,
 11821  				}, tablets...)
 11822  			},
 11823  			req: &vtctldatapb.ValidateShardRequest{
 11824  				Keyspace:    "ks1",
 11825  				Shard:       "-",
 11826  				PingTablets: true,
 11827  			},
 11828  			expected: &vtctldatapb.ValidateShardResponse{
 11829  				Results: []string{
 11830  					"replica 100.200.200.100 not in replication graph for shard ks1/- (mysql instance without vttablet?)",
 11831  					"replica zone1-0000000102 not replicating: 12.22.32.42 replica list: [\"11.21.31.41\" \"100.200.200.100\"]",
 11832  				},
 11833  			},
 11834  		},
 11835  		{
 11836  			name: "ping_tablets/Ping failed",
 11837  			ts:   memorytopo.NewServer("zone1"),
 11838  			tmc: &testutil.TabletManagerClient{
 11839  				GetReplicasResults: map[string]struct {
 11840  					Replicas []string
 11841  					Error    error
 11842  				}{
 11843  					"zone1-0000000100": {
 11844  						Replicas: []string{"11.21.31.41", "12.22.32.42"},
 11845  					},
 11846  				},
 11847  				PingResults: map[string]error{
 11848  					"zone1-0000000100": nil,
 11849  					"zone1-0000000101": assert.AnError,
 11850  					"zone1-0000000102": nil,
 11851  				},
 11852  			},
 11853  			setup: func(t *testing.T, tt *testcase) {
 11854  				tablets := []*topodatapb.Tablet{
 11855  					{
 11856  						Keyspace: "ks1",
 11857  						Shard:    "-",
 11858  						Alias: &topodatapb.TabletAlias{
 11859  							Cell: "zone1",
 11860  							Uid:  100,
 11861  						},
 11862  						Type:     topodatapb.TabletType_PRIMARY,
 11863  						Hostname: "ks1-primary",
 11864  						// note: we don't actually use this IP, we just need to
 11865  						// resolve _something_ for the testcase. The IPs are
 11866  						// used by the validateReplication function to
 11867  						// disambiguate/deduplicate tablets.
 11868  						MysqlHostname: "10.20.30.40",
 11869  					},
 11870  					{
 11871  						Keyspace: "ks1",
 11872  						Shard:    "-",
 11873  						Alias: &topodatapb.TabletAlias{
 11874  							Cell: "zone1",
 11875  							Uid:  101,
 11876  						},
 11877  						Type:          topodatapb.TabletType_REPLICA,
 11878  						Hostname:      "ks1-replica",
 11879  						MysqlHostname: "11.21.31.41",
 11880  					},
 11881  					{
 11882  						Keyspace: "ks1",
 11883  						Shard:    "-",
 11884  						Alias: &topodatapb.TabletAlias{
 11885  							Cell: "zone1",
 11886  							Uid:  102,
 11887  						},
 11888  						Type:          topodatapb.TabletType_RDONLY,
 11889  						Hostname:      "ks1-rdonly",
 11890  						MysqlHostname: "12.22.32.42",
 11891  					},
 11892  				}
 11893  				testutil.AddTablets(ctx, t, tt.ts, &testutil.AddTabletOptions{
 11894  					AlsoSetShardPrimary: true,
 11895  				}, tablets...)
 11896  			},
 11897  			req: &vtctldatapb.ValidateShardRequest{
 11898  				Keyspace:    "ks1",
 11899  				Shard:       "-",
 11900  				PingTablets: true,
 11901  			},
 11902  			expected: &vtctldatapb.ValidateShardResponse{
 11903  				Results: []string{"Ping(zone1-0000000101) failed: assert.AnError general error for testing tablet hostname: ks1-replica"},
 11904  			},
 11905  		},
 11906  	}
 11907  
 11908  	for _, tt := range tests {
 11909  		tt := tt
 11910  		t.Run(tt.name, func(t *testing.T) {
 11911  			t.Parallel()
 11912  
 11913  			if tt.setup != nil {
 11914  				tt.setup(t, tt)
 11915  			}
 11916  
 11917  			vtctld := testutil.NewVtctldServerWithTabletManagerClient(t, tt.ts, tt.tmc, func(ts *topo.Server) vtctlservicepb.VtctldServer {
 11918  				return NewVtctldServer(ts)
 11919  			})
 11920  			resp, err := vtctld.ValidateShard(ctx, tt.req)
 11921  			if tt.shouldErr {
 11922  				assert.Error(t, err)
 11923  				return
 11924  			}
 11925  
 11926  			require.NoError(t, err)
 11927  			assert.Equal(t, tt.expected, resp)
 11928  		})
 11929  	}
 11930  }
 11931  func TestMain(m *testing.M) {
 11932  	_flag.ParseFlagsForTest()
 11933  	os.Exit(m.Run())
 11934  }