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

     1  /*
     2  Copyright 2021 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package cluster
    18  
    19  import (
    20  	"context"
    21  	"database/sql"
    22  	"fmt"
    23  	"sort"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/stretchr/testify/assert"
    28  	"github.com/stretchr/testify/require"
    29  	"k8s.io/apimachinery/pkg/util/sets"
    30  
    31  	"vitess.io/vitess/go/pools"
    32  	"vitess.io/vitess/go/test/utils"
    33  	"vitess.io/vitess/go/vt/topo"
    34  	"vitess.io/vitess/go/vt/topo/topoproto"
    35  	"vitess.io/vitess/go/vt/vitessdriver"
    36  	"vitess.io/vitess/go/vt/vtadmin/cluster/resolver"
    37  	"vitess.io/vitess/go/vt/vtadmin/vtctldclient/fakevtctldclient"
    38  	"vitess.io/vitess/go/vt/vtadmin/vtsql"
    39  	"vitess.io/vitess/go/vt/vtadmin/vtsql/fakevtsql"
    40  	"vitess.io/vitess/go/vt/vtctl/vtctldclient"
    41  
    42  	logutilpb "vitess.io/vitess/go/vt/proto/logutil"
    43  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    44  	vtadminpb "vitess.io/vitess/go/vt/proto/vtadmin"
    45  	vtctldatapb "vitess.io/vitess/go/vt/proto/vtctldata"
    46  )
    47  
    48  type vtctldProxy struct {
    49  	vtctldclient.VtctldClient
    50  	dialErr error
    51  }
    52  
    53  func (fake *vtctldProxy) Dial(ctx context.Context) error { return fake.dialErr }
    54  
    55  func TestDeleteTablets(t *testing.T) {
    56  	t.Parallel()
    57  
    58  	testClusterProto := &vtadminpb.Cluster{
    59  		Id:   "test",
    60  		Name: "test",
    61  	}
    62  
    63  	tests := []struct {
    64  		name      string
    65  		cluster   *Cluster
    66  		timeout   time.Duration
    67  		setup     func(t testing.TB, c *Cluster)
    68  		req       *vtctldatapb.DeleteTabletsRequest
    69  		expected  *vtctldatapb.DeleteTabletsResponse
    70  		shouldErr bool
    71  	}{
    72  		{
    73  			name: "ok",
    74  			cluster: &Cluster{
    75  				Vtctld: &fakevtctldclient.VtctldClient{
    76  					DeleteTabletsResults: map[string]error{
    77  						"zone1-0000000100,zone1-0000000101": nil,
    78  					},
    79  				},
    80  				topoRWPool: pools.NewRPCPool(1, time.Millisecond*100, nil),
    81  			},
    82  			req: &vtctldatapb.DeleteTabletsRequest{
    83  				TabletAliases: []*topodatapb.TabletAlias{
    84  					{
    85  						Cell: "zone1",
    86  						Uid:  100,
    87  					},
    88  					{
    89  						Cell: "zone1",
    90  						Uid:  101,
    91  					},
    92  				},
    93  			},
    94  			expected: &vtctldatapb.DeleteTabletsResponse{},
    95  		},
    96  		{
    97  			name: "error",
    98  			cluster: &Cluster{
    99  				Vtctld:     &fakevtctldclient.VtctldClient{},
   100  				topoRWPool: pools.NewRPCPool(1, time.Millisecond*100, nil),
   101  			},
   102  			req: &vtctldatapb.DeleteTabletsRequest{
   103  				TabletAliases: []*topodatapb.TabletAlias{
   104  					{
   105  						Cell: "zone1",
   106  						Uid:  100,
   107  					},
   108  					{
   109  						Cell: "zone1",
   110  						Uid:  101,
   111  					},
   112  				},
   113  			},
   114  			shouldErr: true,
   115  		},
   116  		{
   117  			name: "RPC pool full",
   118  			cluster: &Cluster{
   119  				Vtctld:     &fakevtctldclient.VtctldClient{},
   120  				topoRWPool: pools.NewRPCPool(1, time.Millisecond*10, nil),
   121  			},
   122  			timeout: time.Millisecond * 50,
   123  			setup: func(t testing.TB, c *Cluster) {
   124  				err := c.topoRWPool.Acquire(context.Background())
   125  				require.NoError(t, err, "failed to lock RPC pool")
   126  				t.Cleanup(c.topoRWPool.Release)
   127  			},
   128  			req: &vtctldatapb.DeleteTabletsRequest{
   129  				TabletAliases: []*topodatapb.TabletAlias{
   130  					{
   131  						Cell: "zone1",
   132  						Uid:  100,
   133  					},
   134  					{
   135  						Cell: "zone1",
   136  						Uid:  101,
   137  					},
   138  				},
   139  			},
   140  			shouldErr: true,
   141  		},
   142  	}
   143  
   144  	for _, tt := range tests {
   145  		tt := tt
   146  
   147  		t.Run(tt.name, func(t *testing.T) {
   148  			t.Parallel()
   149  
   150  			tt.cluster.ID = testClusterProto.Id
   151  			tt.cluster.Name = testClusterProto.Name
   152  
   153  			if tt.setup != nil {
   154  				tt.setup(t, tt.cluster)
   155  			}
   156  
   157  			var (
   158  				ctx    context.Context
   159  				cancel context.CancelFunc
   160  			)
   161  
   162  			switch tt.timeout {
   163  			case 0:
   164  				ctx, cancel = context.WithCancel(context.Background())
   165  			default:
   166  				ctx, cancel = context.WithTimeout(context.Background(), tt.timeout)
   167  			}
   168  			defer cancel()
   169  
   170  			resp, err := tt.cluster.DeleteTablets(ctx, tt.req)
   171  			if tt.shouldErr {
   172  				assert.Error(t, err, "expected error, got %+v", resp)
   173  				return
   174  			}
   175  
   176  			require.NoError(t, err)
   177  			utils.MustMatch(t, tt.expected, resp)
   178  		})
   179  	}
   180  }
   181  
   182  func TestEmergencyFailoverShard(t *testing.T) {
   183  	t.Parallel()
   184  
   185  	testClusterProto := &vtadminpb.Cluster{
   186  		Id:   "test",
   187  		Name: "test",
   188  	}
   189  
   190  	tests := []struct {
   191  		name      string
   192  		cluster   *Cluster
   193  		setup     func(t testing.TB, c *Cluster)
   194  		timeout   time.Duration
   195  		req       *vtctldatapb.EmergencyReparentShardRequest
   196  		expected  *vtadminpb.EmergencyFailoverShardResponse
   197  		shouldErr bool
   198  	}{
   199  		{
   200  			name: "ok",
   201  			cluster: &Cluster{
   202  				ID:   "test",
   203  				Name: "test",
   204  				Vtctld: &fakevtctldclient.VtctldClient{
   205  					EmergencyReparentShardResults: map[string]struct {
   206  						Response *vtctldatapb.EmergencyReparentShardResponse
   207  						Error    error
   208  					}{
   209  						"ks1/-": {
   210  							Response: &vtctldatapb.EmergencyReparentShardResponse{
   211  								Keyspace: "ks1",
   212  								Shard:    "-",
   213  								PromotedPrimary: &topodatapb.TabletAlias{
   214  									Cell: "zone1",
   215  									Uid:  100,
   216  								},
   217  								Events: []*logutilpb.Event{{}, {}, {}},
   218  							},
   219  						},
   220  						"ks2/-80": {
   221  							Error: fmt.Errorf("some error: %w", assert.AnError),
   222  						},
   223  					},
   224  				},
   225  				emergencyFailoverPool: pools.NewRPCPool(1, time.Second, nil),
   226  			},
   227  			req: &vtctldatapb.EmergencyReparentShardRequest{
   228  				Keyspace: "ks1",
   229  				Shard:    "-",
   230  				NewPrimary: &topodatapb.TabletAlias{
   231  					Cell: "zone1",
   232  					Uid:  100,
   233  				},
   234  			},
   235  			expected: &vtadminpb.EmergencyFailoverShardResponse{
   236  				Cluster:  testClusterProto,
   237  				Keyspace: "ks1",
   238  				Shard:    "-",
   239  				PromotedPrimary: &topodatapb.TabletAlias{
   240  					Cell: "zone1",
   241  					Uid:  100,
   242  				},
   243  				Events: []*logutilpb.Event{{}, {}, {}},
   244  			},
   245  		},
   246  		{
   247  			name: "error",
   248  			cluster: &Cluster{
   249  				ID:   "test",
   250  				Name: "test",
   251  				Vtctld: &fakevtctldclient.VtctldClient{
   252  					EmergencyReparentShardResults: map[string]struct {
   253  						Response *vtctldatapb.EmergencyReparentShardResponse
   254  						Error    error
   255  					}{
   256  						"ks1/-": {
   257  							Response: &vtctldatapb.EmergencyReparentShardResponse{
   258  								Keyspace: "ks1",
   259  								Shard:    "-",
   260  								PromotedPrimary: &topodatapb.TabletAlias{
   261  									Cell: "zone1",
   262  									Uid:  100,
   263  								},
   264  								Events: []*logutilpb.Event{{}, {}, {}},
   265  							},
   266  						},
   267  						"ks2/-80": {
   268  							Error: fmt.Errorf("some error: %w", assert.AnError),
   269  						},
   270  					},
   271  				},
   272  				emergencyFailoverPool: pools.NewRPCPool(1, time.Second, nil),
   273  			},
   274  			req: &vtctldatapb.EmergencyReparentShardRequest{
   275  				Keyspace: "ks2",
   276  				Shard:    "-80",
   277  				NewPrimary: &topodatapb.TabletAlias{
   278  					Cell: "zone1",
   279  					Uid:  200,
   280  				},
   281  			},
   282  			shouldErr: true,
   283  		},
   284  		{
   285  			name: "pool full",
   286  			cluster: &Cluster{
   287  				ID:   "test",
   288  				Name: "test",
   289  				Vtctld: &fakevtctldclient.VtctldClient{
   290  					EmergencyReparentShardResults: map[string]struct {
   291  						Response *vtctldatapb.EmergencyReparentShardResponse
   292  						Error    error
   293  					}{
   294  						"ks1/-": {
   295  							Response: &vtctldatapb.EmergencyReparentShardResponse{
   296  								Keyspace: "ks1",
   297  								Shard:    "-",
   298  								PromotedPrimary: &topodatapb.TabletAlias{
   299  									Cell: "zone1",
   300  									Uid:  100,
   301  								},
   302  								Events: []*logutilpb.Event{{}, {}, {}},
   303  							},
   304  						},
   305  						"ks2/-80": {
   306  							Error: fmt.Errorf("some error: %w", assert.AnError),
   307  						},
   308  					},
   309  				},
   310  				emergencyFailoverPool: pools.NewRPCPool(1, time.Millisecond*25, nil),
   311  			},
   312  			setup: func(t testing.TB, c *Cluster) {
   313  				ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
   314  				defer cancel()
   315  
   316  				err := c.emergencyFailoverPool.Acquire(ctx)
   317  				require.NoError(t, err, "could not block ers pool in setup")
   318  				t.Cleanup(c.emergencyFailoverPool.Release)
   319  			},
   320  			timeout: time.Millisecond * 50,
   321  			req: &vtctldatapb.EmergencyReparentShardRequest{
   322  				Keyspace: "ks1",
   323  				Shard:    "-",
   324  				NewPrimary: &topodatapb.TabletAlias{
   325  					Cell: "zone1",
   326  					Uid:  100,
   327  				},
   328  			},
   329  			shouldErr: true,
   330  		},
   331  	}
   332  
   333  	for _, tt := range tests {
   334  		tt := tt
   335  
   336  		t.Run(tt.name, func(t *testing.T) {
   337  			t.Parallel()
   338  
   339  			if tt.setup != nil {
   340  				func(t *testing.T) {
   341  					t.Helper()
   342  					tt.setup(t, tt.cluster)
   343  				}(t)
   344  			}
   345  
   346  			var (
   347  				ctx    context.Context
   348  				cancel context.CancelFunc
   349  			)
   350  			switch tt.timeout {
   351  			case 0:
   352  				ctx, cancel = context.WithCancel(context.Background())
   353  			default:
   354  				ctx, cancel = context.WithTimeout(context.Background(), tt.timeout)
   355  			}
   356  			defer cancel()
   357  
   358  			resp, err := tt.cluster.EmergencyFailoverShard(ctx, tt.req)
   359  			if tt.shouldErr {
   360  				assert.Error(t, err)
   361  				return
   362  			}
   363  
   364  			require.NoError(t, err)
   365  			utils.MustMatch(t, tt.expected, resp)
   366  		})
   367  	}
   368  }
   369  
   370  func Test_getShardSets(t *testing.T) {
   371  	t.Parallel()
   372  
   373  	c := &Cluster{
   374  		Vtctld: &vtctldProxy{
   375  			VtctldClient: &fakevtctldclient.VtctldClient{
   376  				GetKeyspaceResults: map[string]struct {
   377  					Response *vtctldatapb.GetKeyspaceResponse
   378  					Error    error
   379  				}{
   380  					"ks1": {
   381  						Response: &vtctldatapb.GetKeyspaceResponse{
   382  							Keyspace: &vtctldatapb.Keyspace{
   383  								Name:     "ks1",
   384  								Keyspace: &topodatapb.Keyspace{},
   385  							},
   386  						},
   387  					},
   388  					"ks2": {
   389  						Response: &vtctldatapb.GetKeyspaceResponse{
   390  							Keyspace: &vtctldatapb.Keyspace{
   391  								Name:     "ks2",
   392  								Keyspace: &topodatapb.Keyspace{},
   393  							},
   394  						},
   395  					},
   396  					"ks3": {
   397  						Error: topo.NewError(topo.NoNode, "ks3"), /* we need to fail in a particular way */
   398  					},
   399  				},
   400  				GetKeyspacesResults: &struct {
   401  					Keyspaces []*vtctldatapb.Keyspace
   402  					Error     error
   403  				}{
   404  					Keyspaces: []*vtctldatapb.Keyspace{
   405  						{
   406  							Name:     "ks1",
   407  							Keyspace: &topodatapb.Keyspace{},
   408  						},
   409  						{
   410  							Name:     "ks2",
   411  							Keyspace: &topodatapb.Keyspace{},
   412  						},
   413  					},
   414  				},
   415  				FindAllShardsInKeyspaceResults: map[string]struct {
   416  					Response *vtctldatapb.FindAllShardsInKeyspaceResponse
   417  					Error    error
   418  				}{
   419  					"ks1": {
   420  						Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
   421  							Shards: map[string]*vtctldatapb.Shard{
   422  								"-80": {
   423  									Keyspace: "ks1",
   424  									Name:     "-80",
   425  									Shard:    &topodatapb.Shard{},
   426  								},
   427  								"80-": {
   428  									Keyspace: "ks1",
   429  									Name:     "80-",
   430  									Shard:    &topodatapb.Shard{},
   431  								},
   432  							},
   433  						},
   434  					},
   435  					"ks2": {
   436  						Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
   437  							Shards: map[string]*vtctldatapb.Shard{
   438  								"-": {
   439  									Keyspace: "ks2",
   440  									Name:     "-",
   441  									Shard:    &topodatapb.Shard{},
   442  								},
   443  							},
   444  						},
   445  					},
   446  				},
   447  			},
   448  		},
   449  		topoReadPool: pools.NewRPCPool(5, 0, nil),
   450  	}
   451  
   452  	tests := []struct {
   453  		name           string
   454  		keyspaces      []string
   455  		keyspaceShards []string
   456  		result         map[string]sets.Set[string]
   457  		shouldErr      bool
   458  	}{
   459  		{
   460  			name:           "all keyspaces and shards",
   461  			keyspaces:      nil,
   462  			keyspaceShards: nil,
   463  			result: map[string]sets.Set[string]{
   464  				"ks1": sets.New[string]("-80", "80-"),
   465  				"ks2": sets.New[string]("-"),
   466  			},
   467  		},
   468  		{
   469  			name:           "keyspaceShards filter",
   470  			keyspaces:      nil,
   471  			keyspaceShards: []string{"ks1/-80", "ks2/-"},
   472  			result: map[string]sets.Set[string]{
   473  				"ks1": sets.New[string]("-80"),
   474  				"ks2": sets.New[string]("-"),
   475  			},
   476  		},
   477  		{
   478  			name:           "keyspace and shards filters",
   479  			keyspaces:      []string{"ks1"},
   480  			keyspaceShards: []string{"ks1/80-"},
   481  			result: map[string]sets.Set[string]{
   482  				"ks1": sets.New[string]("80-"),
   483  			},
   484  		},
   485  		{
   486  			name:           "skipped non-existing shards and keyspaces",
   487  			keyspaces:      nil,
   488  			keyspaceShards: []string{"ks1/-" /* does not exist */, "ks1/-80", "ks1/80-", "ks3/-" /* does not exist */},
   489  			result: map[string]sets.Set[string]{
   490  				"ks1": sets.New[string]("-80", "80-"),
   491  			},
   492  		},
   493  	}
   494  
   495  	for _, tt := range tests {
   496  		tt := tt
   497  
   498  		t.Run(tt.name, func(t *testing.T) {
   499  			t.Parallel()
   500  
   501  			result, err := c.getShardSets(context.Background(), tt.keyspaces, tt.keyspaceShards)
   502  			if tt.shouldErr {
   503  				assert.Error(t, err)
   504  				return
   505  			}
   506  
   507  			require.NoError(t, err)
   508  			assert.Equal(t, tt.result, result)
   509  		})
   510  	}
   511  }
   512  
   513  func TestPlannedFailoverShard(t *testing.T) {
   514  	t.Parallel()
   515  
   516  	testClusterProto := &vtadminpb.Cluster{
   517  		Id:   "test",
   518  		Name: "test",
   519  	}
   520  
   521  	tests := []struct {
   522  		name      string
   523  		cluster   *Cluster
   524  		setup     func(t testing.TB, c *Cluster)
   525  		timeout   time.Duration
   526  		req       *vtctldatapb.PlannedReparentShardRequest
   527  		expected  *vtadminpb.PlannedFailoverShardResponse
   528  		shouldErr bool
   529  	}{
   530  		{
   531  			name: "ok",
   532  			cluster: &Cluster{
   533  				ID:   "test",
   534  				Name: "test",
   535  				Vtctld: &fakevtctldclient.VtctldClient{
   536  					PlannedReparentShardResults: map[string]struct {
   537  						Response *vtctldatapb.PlannedReparentShardResponse
   538  						Error    error
   539  					}{
   540  						"ks1/-": {
   541  							Response: &vtctldatapb.PlannedReparentShardResponse{
   542  								Keyspace: "ks1",
   543  								Shard:    "-",
   544  								PromotedPrimary: &topodatapb.TabletAlias{
   545  									Cell: "zone1",
   546  									Uid:  100,
   547  								},
   548  								Events: []*logutilpb.Event{{}, {}, {}},
   549  							},
   550  						},
   551  						"ks2/-80": {
   552  							Error: fmt.Errorf("some error: %w", assert.AnError),
   553  						},
   554  					},
   555  				},
   556  				failoverPool: pools.NewRPCPool(1, time.Second, nil),
   557  			},
   558  			req: &vtctldatapb.PlannedReparentShardRequest{
   559  				Keyspace: "ks1",
   560  				Shard:    "-",
   561  				NewPrimary: &topodatapb.TabletAlias{
   562  					Cell: "zone1",
   563  					Uid:  100,
   564  				},
   565  			},
   566  			expected: &vtadminpb.PlannedFailoverShardResponse{
   567  				Cluster:  testClusterProto,
   568  				Keyspace: "ks1",
   569  				Shard:    "-",
   570  				PromotedPrimary: &topodatapb.TabletAlias{
   571  					Cell: "zone1",
   572  					Uid:  100,
   573  				},
   574  				Events: []*logutilpb.Event{{}, {}, {}},
   575  			},
   576  		},
   577  		{
   578  			name: "error",
   579  			cluster: &Cluster{
   580  				ID:   "test",
   581  				Name: "test",
   582  				Vtctld: &fakevtctldclient.VtctldClient{
   583  					PlannedReparentShardResults: map[string]struct {
   584  						Response *vtctldatapb.PlannedReparentShardResponse
   585  						Error    error
   586  					}{
   587  						"ks1/-": {
   588  							Response: &vtctldatapb.PlannedReparentShardResponse{
   589  								Keyspace: "ks1",
   590  								Shard:    "-",
   591  								PromotedPrimary: &topodatapb.TabletAlias{
   592  									Cell: "zone1",
   593  									Uid:  100,
   594  								},
   595  								Events: []*logutilpb.Event{{}, {}, {}},
   596  							},
   597  						},
   598  						"ks2/-80": {
   599  							Error: fmt.Errorf("some error: %w", assert.AnError),
   600  						},
   601  					},
   602  				},
   603  				failoverPool: pools.NewRPCPool(1, time.Second, nil),
   604  			},
   605  			req: &vtctldatapb.PlannedReparentShardRequest{
   606  				Keyspace: "ks2",
   607  				Shard:    "-80",
   608  				NewPrimary: &topodatapb.TabletAlias{
   609  					Cell: "zone1",
   610  					Uid:  200,
   611  				},
   612  			},
   613  			shouldErr: true,
   614  		},
   615  		{
   616  			name: "pool full",
   617  			cluster: &Cluster{
   618  				ID:   "test",
   619  				Name: "test",
   620  				Vtctld: &fakevtctldclient.VtctldClient{
   621  					PlannedReparentShardResults: map[string]struct {
   622  						Response *vtctldatapb.PlannedReparentShardResponse
   623  						Error    error
   624  					}{
   625  						"ks1/-": {
   626  							Response: &vtctldatapb.PlannedReparentShardResponse{
   627  								Keyspace: "ks1",
   628  								Shard:    "-",
   629  								PromotedPrimary: &topodatapb.TabletAlias{
   630  									Cell: "zone1",
   631  									Uid:  100,
   632  								},
   633  								Events: []*logutilpb.Event{{}, {}, {}},
   634  							},
   635  						},
   636  						"ks2/-80": {
   637  							Error: fmt.Errorf("some error: %w", assert.AnError),
   638  						},
   639  					},
   640  				},
   641  				failoverPool: pools.NewRPCPool(1, time.Millisecond*25, nil),
   642  			},
   643  			setup: func(t testing.TB, c *Cluster) {
   644  				ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
   645  				defer cancel()
   646  
   647  				err := c.failoverPool.Acquire(ctx)
   648  				require.NoError(t, err, "could not block prs pool in setup")
   649  				t.Cleanup(c.failoverPool.Release)
   650  			},
   651  			timeout: time.Millisecond * 50,
   652  			req: &vtctldatapb.PlannedReparentShardRequest{
   653  				Keyspace: "ks1",
   654  				Shard:    "-",
   655  				NewPrimary: &topodatapb.TabletAlias{
   656  					Cell: "zone1",
   657  					Uid:  100,
   658  				},
   659  			},
   660  			shouldErr: true,
   661  		},
   662  	}
   663  
   664  	for _, tt := range tests {
   665  		tt := tt
   666  
   667  		t.Run(tt.name, func(t *testing.T) {
   668  			t.Parallel()
   669  
   670  			if tt.setup != nil {
   671  				func(t *testing.T) {
   672  					t.Helper()
   673  					tt.setup(t, tt.cluster)
   674  				}(t)
   675  			}
   676  
   677  			var (
   678  				ctx    context.Context
   679  				cancel context.CancelFunc
   680  			)
   681  			switch tt.timeout {
   682  			case 0:
   683  				ctx, cancel = context.WithCancel(context.Background())
   684  			default:
   685  				ctx, cancel = context.WithTimeout(context.Background(), tt.timeout)
   686  			}
   687  			defer cancel()
   688  
   689  			resp, err := tt.cluster.PlannedFailoverShard(ctx, tt.req)
   690  			if tt.shouldErr {
   691  				assert.Error(t, err)
   692  				return
   693  			}
   694  
   695  			require.NoError(t, err)
   696  			utils.MustMatch(t, tt.expected, resp)
   697  		})
   698  	}
   699  }
   700  
   701  func TestRefreshState(t *testing.T) {
   702  	t.Parallel()
   703  
   704  	testClusterProto := &vtadminpb.Cluster{
   705  		Id:   "test",
   706  		Name: "test",
   707  	}
   708  
   709  	tests := []struct {
   710  		name              string
   711  		cluster           *Cluster
   712  		timeout           time.Duration
   713  		setup             func(t testing.TB, c *Cluster)
   714  		tablet            *vtadminpb.Tablet
   715  		assertion         func(t assert.TestingT, err error, msgAndArgs ...any) bool
   716  		assertionMsgExtra []any
   717  	}{
   718  		{
   719  			name: "ok",
   720  			cluster: &Cluster{
   721  				Vtctld: &fakevtctldclient.VtctldClient{
   722  					RefreshStateResults: map[string]error{
   723  						"zone1-0000000100": nil,
   724  					},
   725  				},
   726  				topoReadPool: pools.NewRPCPool(1, time.Millisecond*100, nil),
   727  			},
   728  			tablet: &vtadminpb.Tablet{
   729  				Tablet: &topodatapb.Tablet{
   730  					Alias: &topodatapb.TabletAlias{
   731  						Cell: "zone1",
   732  						Uid:  100,
   733  					},
   734  				},
   735  			},
   736  			assertion: assert.NoError,
   737  		},
   738  		{
   739  			name: "error",
   740  			cluster: &Cluster{
   741  				Vtctld: &fakevtctldclient.VtctldClient{
   742  					RefreshStateResults: map[string]error{
   743  						"zone1-0000000100": fmt.Errorf("some error"),
   744  					},
   745  				},
   746  				topoReadPool: pools.NewRPCPool(1, time.Millisecond*100, nil),
   747  			},
   748  			tablet: &vtadminpb.Tablet{
   749  				Tablet: &topodatapb.Tablet{
   750  					Alias: &topodatapb.TabletAlias{
   751  						Cell: "zone1",
   752  						Uid:  100,
   753  					},
   754  				},
   755  			},
   756  			assertion: assert.Error,
   757  		},
   758  		{
   759  			name: "RPC pool full",
   760  			cluster: &Cluster{
   761  				Vtctld:       &fakevtctldclient.VtctldClient{},
   762  				topoReadPool: pools.NewRPCPool(1, time.Millisecond*10, nil),
   763  			},
   764  			timeout: time.Millisecond * 50,
   765  			setup: func(t testing.TB, c *Cluster) {
   766  				err := c.topoReadPool.Acquire(context.Background())
   767  				require.NoError(t, err, "failed to lock RPC pool")
   768  				t.Cleanup(c.topoReadPool.Release)
   769  			},
   770  			tablet: &vtadminpb.Tablet{
   771  				Tablet: &topodatapb.Tablet{Alias: &topodatapb.TabletAlias{}},
   772  			},
   773  			assertion: assert.Error,
   774  		},
   775  	}
   776  
   777  	for _, tt := range tests {
   778  		tt := tt
   779  
   780  		t.Run(tt.name, func(t *testing.T) {
   781  			t.Parallel()
   782  
   783  			tt.cluster.ID = testClusterProto.Id
   784  			tt.cluster.Name = testClusterProto.Name
   785  
   786  			if tt.setup != nil {
   787  				tt.setup(t, tt.cluster)
   788  			}
   789  
   790  			var (
   791  				ctx    context.Context
   792  				cancel context.CancelFunc
   793  			)
   794  
   795  			switch tt.timeout {
   796  			case 0:
   797  				ctx, cancel = context.WithCancel(context.Background())
   798  			default:
   799  				ctx, cancel = context.WithTimeout(context.Background(), tt.timeout)
   800  			}
   801  			defer cancel()
   802  
   803  			err := tt.cluster.RefreshState(ctx, tt.tablet)
   804  			tt.assertion(t, err, tt.assertionMsgExtra...)
   805  		})
   806  	}
   807  }
   808  
   809  func TestRefreshTabletReplicationSource(t *testing.T) {
   810  	t.Parallel()
   811  
   812  	testClusterProto := &vtadminpb.Cluster{
   813  		Id:   "test",
   814  		Name: "test",
   815  	}
   816  
   817  	tests := []struct {
   818  		name      string
   819  		cluster   *Cluster
   820  		timeout   time.Duration
   821  		setup     func(t testing.TB, c *Cluster)
   822  		tablet    *vtadminpb.Tablet
   823  		expected  *vtadminpb.RefreshTabletReplicationSourceResponse
   824  		shouldErr bool
   825  	}{
   826  		{
   827  			name: "ok",
   828  			cluster: &Cluster{
   829  				Vtctld: &fakevtctldclient.VtctldClient{
   830  					ReparentTabletResults: map[string]struct {
   831  						Response *vtctldatapb.ReparentTabletResponse
   832  						Error    error
   833  					}{
   834  						"zone1-0000000100": {
   835  							Response: &vtctldatapb.ReparentTabletResponse{
   836  								Keyspace: "testks",
   837  								Shard:    "-",
   838  								Primary: &topodatapb.TabletAlias{
   839  									Cell: "zone1",
   840  									Uid:  500,
   841  								},
   842  							},
   843  						},
   844  					},
   845  				},
   846  				topoRWPool: pools.NewRPCPool(1, time.Millisecond*100, nil),
   847  			},
   848  			tablet: &vtadminpb.Tablet{
   849  				Tablet: &topodatapb.Tablet{
   850  					Alias: &topodatapb.TabletAlias{
   851  						Cell: "zone1",
   852  						Uid:  100,
   853  					},
   854  				},
   855  			},
   856  			expected: &vtadminpb.RefreshTabletReplicationSourceResponse{
   857  				Keyspace: "testks",
   858  				Shard:    "-",
   859  				Primary: &topodatapb.TabletAlias{
   860  					Cell: "zone1",
   861  					Uid:  500,
   862  				},
   863  				Cluster: testClusterProto,
   864  			},
   865  		},
   866  		{
   867  			name: "error",
   868  			cluster: &Cluster{
   869  				Vtctld:     &fakevtctldclient.VtctldClient{},
   870  				topoRWPool: pools.NewRPCPool(1, time.Millisecond*100, nil),
   871  			},
   872  			tablet: &vtadminpb.Tablet{
   873  				Tablet: &topodatapb.Tablet{
   874  					Alias: &topodatapb.TabletAlias{
   875  						Cell: "zone1",
   876  						Uid:  100,
   877  					},
   878  				},
   879  			},
   880  			shouldErr: true,
   881  		},
   882  		{
   883  			name: "RPC pool full",
   884  			cluster: &Cluster{
   885  				Vtctld:     &fakevtctldclient.VtctldClient{},
   886  				topoRWPool: pools.NewRPCPool(1, time.Millisecond*10, nil),
   887  			},
   888  			timeout: time.Millisecond * 50,
   889  			setup: func(t testing.TB, c *Cluster) {
   890  				err := c.topoRWPool.Acquire(context.Background())
   891  				require.NoError(t, err, "failed to lock RPC pool")
   892  				t.Cleanup(c.topoRWPool.Release)
   893  			},
   894  			tablet: &vtadminpb.Tablet{
   895  				Tablet: &topodatapb.Tablet{Alias: &topodatapb.TabletAlias{}},
   896  			},
   897  			shouldErr: true,
   898  		},
   899  	}
   900  
   901  	for _, tt := range tests {
   902  		tt := tt
   903  
   904  		t.Run(tt.name, func(t *testing.T) {
   905  			t.Parallel()
   906  
   907  			tt.cluster.ID = testClusterProto.Id
   908  			tt.cluster.Name = testClusterProto.Name
   909  
   910  			if tt.setup != nil {
   911  				tt.setup(t, tt.cluster)
   912  			}
   913  
   914  			var (
   915  				ctx    context.Context
   916  				cancel context.CancelFunc
   917  			)
   918  
   919  			switch tt.timeout {
   920  			case 0:
   921  				ctx, cancel = context.WithCancel(context.Background())
   922  			default:
   923  				ctx, cancel = context.WithTimeout(context.Background(), tt.timeout)
   924  			}
   925  			defer cancel()
   926  
   927  			resp, err := tt.cluster.RefreshTabletReplicationSource(ctx, tt.tablet)
   928  			if tt.shouldErr {
   929  				assert.Error(t, err)
   930  				return
   931  			}
   932  
   933  			require.NoError(t, err)
   934  			utils.MustMatch(t, tt.expected, resp)
   935  		})
   936  	}
   937  }
   938  
   939  func Test_reloadKeyspaceSchemas(t *testing.T) {
   940  	t.Parallel()
   941  
   942  	ctx := context.Background()
   943  	tests := []struct {
   944  		name      string
   945  		cluster   *Cluster
   946  		req       *vtadminpb.ReloadSchemasRequest
   947  		expected  []*vtadminpb.ReloadSchemasResponse_KeyspaceResult
   948  		shouldErr bool
   949  	}{
   950  		{
   951  			name: "ok",
   952  			cluster: &Cluster{
   953  				ID:   "test",
   954  				Name: "test",
   955  				Vtctld: &fakevtctldclient.VtctldClient{
   956  					GetKeyspacesResults: &struct {
   957  						Keyspaces []*vtctldatapb.Keyspace
   958  						Error     error
   959  					}{
   960  						Keyspaces: []*vtctldatapb.Keyspace{
   961  							{
   962  								Name: "ks1",
   963  							},
   964  							{
   965  								Name: "ks2",
   966  							},
   967  						},
   968  					},
   969  					ReloadSchemaKeyspaceResults: map[string]struct {
   970  						Response *vtctldatapb.ReloadSchemaKeyspaceResponse
   971  						Error    error
   972  					}{
   973  						"ks1": {
   974  							Response: &vtctldatapb.ReloadSchemaKeyspaceResponse{
   975  								Events: []*logutilpb.Event{
   976  									{}, {}, {},
   977  								},
   978  							},
   979  						},
   980  						"ks2": {
   981  							Error: assert.AnError, // _would_ cause a failure but our request filters it out
   982  						},
   983  					},
   984  				},
   985  				topoReadPool: pools.NewRPCPool(5, 0, nil),
   986  			},
   987  			req: &vtadminpb.ReloadSchemasRequest{
   988  				Keyspaces: []string{"ks1"},
   989  			},
   990  			expected: []*vtadminpb.ReloadSchemasResponse_KeyspaceResult{
   991  				{
   992  					Keyspace: &vtadminpb.Keyspace{
   993  						Cluster: &vtadminpb.Cluster{
   994  							Id:   "test",
   995  							Name: "test",
   996  						},
   997  						Keyspace: &vtctldatapb.Keyspace{
   998  							Name: "ks1",
   999  						},
  1000  					},
  1001  					Events: []*logutilpb.Event{
  1002  						{}, {}, {},
  1003  					},
  1004  				},
  1005  			},
  1006  		},
  1007  		{
  1008  			name: "no keyspaces specified defaults to all",
  1009  			cluster: &Cluster{
  1010  				ID:   "test",
  1011  				Name: "test",
  1012  				Vtctld: &fakevtctldclient.VtctldClient{
  1013  					GetKeyspacesResults: &struct {
  1014  						Keyspaces []*vtctldatapb.Keyspace
  1015  						Error     error
  1016  					}{
  1017  						Keyspaces: []*vtctldatapb.Keyspace{
  1018  							{
  1019  								Name: "ks1",
  1020  							},
  1021  							{
  1022  								Name: "ks2",
  1023  							},
  1024  						},
  1025  					},
  1026  					ReloadSchemaKeyspaceResults: map[string]struct {
  1027  						Response *vtctldatapb.ReloadSchemaKeyspaceResponse
  1028  						Error    error
  1029  					}{
  1030  						"ks1": {
  1031  							Response: &vtctldatapb.ReloadSchemaKeyspaceResponse{
  1032  								Events: []*logutilpb.Event{
  1033  									{},
  1034  								},
  1035  							},
  1036  						},
  1037  						"ks2": {
  1038  							Response: &vtctldatapb.ReloadSchemaKeyspaceResponse{
  1039  								Events: []*logutilpb.Event{
  1040  									{}, {},
  1041  								},
  1042  							},
  1043  						},
  1044  					},
  1045  				},
  1046  				topoReadPool: pools.NewRPCPool(5, 0, nil),
  1047  			},
  1048  			req: &vtadminpb.ReloadSchemasRequest{},
  1049  			expected: []*vtadminpb.ReloadSchemasResponse_KeyspaceResult{
  1050  				{
  1051  					Keyspace: &vtadminpb.Keyspace{
  1052  						Cluster: &vtadminpb.Cluster{
  1053  							Id:   "test",
  1054  							Name: "test",
  1055  						},
  1056  						Keyspace: &vtctldatapb.Keyspace{
  1057  							Name: "ks1",
  1058  						},
  1059  					},
  1060  					Events: []*logutilpb.Event{
  1061  						{},
  1062  					},
  1063  				},
  1064  				{
  1065  					Keyspace: &vtadminpb.Keyspace{
  1066  						Cluster: &vtadminpb.Cluster{
  1067  							Id:   "test",
  1068  							Name: "test",
  1069  						},
  1070  						Keyspace: &vtctldatapb.Keyspace{
  1071  							Name: "ks2",
  1072  						},
  1073  					},
  1074  					Events: []*logutilpb.Event{
  1075  						{}, {},
  1076  					},
  1077  				},
  1078  			},
  1079  		},
  1080  		{
  1081  			name: "skip keyspaces not in cluster",
  1082  			cluster: &Cluster{
  1083  				ID:   "test",
  1084  				Name: "test",
  1085  				Vtctld: &fakevtctldclient.VtctldClient{
  1086  					GetKeyspacesResults: &struct {
  1087  						Keyspaces []*vtctldatapb.Keyspace
  1088  						Error     error
  1089  					}{
  1090  						Keyspaces: []*vtctldatapb.Keyspace{
  1091  							{
  1092  								Name: "ks1",
  1093  							},
  1094  						},
  1095  					},
  1096  					ReloadSchemaKeyspaceResults: map[string]struct {
  1097  						Response *vtctldatapb.ReloadSchemaKeyspaceResponse
  1098  						Error    error
  1099  					}{
  1100  						"ks1": {
  1101  							Response: &vtctldatapb.ReloadSchemaKeyspaceResponse{
  1102  								Events: []*logutilpb.Event{
  1103  									{}, {}, {},
  1104  								},
  1105  							},
  1106  						},
  1107  					},
  1108  				},
  1109  				topoReadPool: pools.NewRPCPool(5, 0, nil),
  1110  			},
  1111  			req: &vtadminpb.ReloadSchemasRequest{
  1112  				Keyspaces: []string{"ks1", "anotherclusterks1"},
  1113  			},
  1114  			expected: []*vtadminpb.ReloadSchemasResponse_KeyspaceResult{
  1115  				{
  1116  					Keyspace: &vtadminpb.Keyspace{
  1117  						Cluster: &vtadminpb.Cluster{
  1118  							Id:   "test",
  1119  							Name: "test",
  1120  						},
  1121  						Keyspace: &vtctldatapb.Keyspace{
  1122  							Name: "ks1",
  1123  						},
  1124  					},
  1125  					Events: []*logutilpb.Event{
  1126  						{}, {}, {},
  1127  					},
  1128  				},
  1129  			},
  1130  		},
  1131  		{
  1132  			name: "GetKeyspaces error",
  1133  			cluster: &Cluster{
  1134  				ID:   "test",
  1135  				Name: "test",
  1136  				Vtctld: &fakevtctldclient.VtctldClient{
  1137  					GetKeyspacesResults: &struct {
  1138  						Keyspaces []*vtctldatapb.Keyspace
  1139  						Error     error
  1140  					}{
  1141  						Error: assert.AnError,
  1142  					},
  1143  				},
  1144  				topoReadPool: pools.NewRPCPool(5, 0, nil),
  1145  			},
  1146  			req:       &vtadminpb.ReloadSchemasRequest{},
  1147  			shouldErr: true,
  1148  		},
  1149  		{
  1150  			name: "ReloadSchemaKeyspace error",
  1151  			cluster: &Cluster{
  1152  				ID:   "test",
  1153  				Name: "test",
  1154  				Vtctld: &fakevtctldclient.VtctldClient{
  1155  					GetKeyspacesResults: &struct {
  1156  						Keyspaces []*vtctldatapb.Keyspace
  1157  						Error     error
  1158  					}{
  1159  						Keyspaces: []*vtctldatapb.Keyspace{
  1160  							{
  1161  								Name: "ks1",
  1162  							},
  1163  							{
  1164  								Name: "ks2",
  1165  							},
  1166  						},
  1167  					},
  1168  					ReloadSchemaKeyspaceResults: map[string]struct {
  1169  						Response *vtctldatapb.ReloadSchemaKeyspaceResponse
  1170  						Error    error
  1171  					}{
  1172  						"ks1": {
  1173  							Response: &vtctldatapb.ReloadSchemaKeyspaceResponse{
  1174  								Events: []*logutilpb.Event{
  1175  									{}, {}, {},
  1176  								},
  1177  							},
  1178  						},
  1179  						"ks2": {
  1180  							Error: assert.AnError,
  1181  						},
  1182  					},
  1183  				},
  1184  				topoReadPool: pools.NewRPCPool(5, 0, nil),
  1185  			},
  1186  			req:       &vtadminpb.ReloadSchemasRequest{},
  1187  			shouldErr: true,
  1188  		},
  1189  	}
  1190  
  1191  	for _, tt := range tests {
  1192  		tt := tt
  1193  
  1194  		t.Run(tt.name, func(t *testing.T) {
  1195  			t.Parallel()
  1196  
  1197  			results, err := tt.cluster.reloadKeyspaceSchemas(ctx, tt.req)
  1198  			if tt.shouldErr {
  1199  				assert.Error(t, err)
  1200  				return
  1201  			}
  1202  
  1203  			require.NoError(t, err)
  1204  
  1205  			sort.Slice(tt.expected, func(i, j int) bool {
  1206  				return tt.expected[i].Keyspace.Keyspace.Name < tt.expected[j].Keyspace.Keyspace.Name
  1207  			})
  1208  			sort.Slice(results, func(i, j int) bool {
  1209  				return results[i].Keyspace.Keyspace.Name < results[j].Keyspace.Keyspace.Name
  1210  			})
  1211  			utils.MustMatch(t, tt.expected, results)
  1212  		})
  1213  	}
  1214  }
  1215  
  1216  func Test_reloadShardSchemas(t *testing.T) {
  1217  	t.Parallel()
  1218  
  1219  	ctx := context.Background()
  1220  	tests := []struct {
  1221  		name      string
  1222  		cluster   *Cluster
  1223  		req       *vtadminpb.ReloadSchemasRequest
  1224  		expected  []*vtadminpb.ReloadSchemasResponse_ShardResult
  1225  		shouldErr bool
  1226  	}{
  1227  		{
  1228  			name: "ok",
  1229  			cluster: &Cluster{
  1230  				ID:   "test",
  1231  				Name: "test",
  1232  				Vtctld: &fakevtctldclient.VtctldClient{
  1233  					FindAllShardsInKeyspaceResults: map[string]struct {
  1234  						Response *vtctldatapb.FindAllShardsInKeyspaceResponse
  1235  						Error    error
  1236  					}{
  1237  						"ks1": {
  1238  							Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
  1239  								Shards: map[string]*vtctldatapb.Shard{
  1240  									"-": {
  1241  										Keyspace: "ks1",
  1242  										Name:     "-",
  1243  									},
  1244  								},
  1245  							},
  1246  						},
  1247  						"ks2": {
  1248  							Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
  1249  								Shards: map[string]*vtctldatapb.Shard{
  1250  									"-80": {
  1251  										Keyspace: "ks2",
  1252  										Name:     "-80",
  1253  									},
  1254  									"80-": {
  1255  										Keyspace: "ks2",
  1256  										Name:     "80-",
  1257  									},
  1258  								},
  1259  							},
  1260  						},
  1261  					},
  1262  					GetKeyspaceResults: map[string]struct {
  1263  						Response *vtctldatapb.GetKeyspaceResponse
  1264  						Error    error
  1265  					}{
  1266  						"ks1": {
  1267  							Response: &vtctldatapb.GetKeyspaceResponse{
  1268  								Keyspace: &vtctldatapb.Keyspace{
  1269  									Name: "ks1",
  1270  								},
  1271  							},
  1272  						},
  1273  						"ks2": {
  1274  							Response: &vtctldatapb.GetKeyspaceResponse{
  1275  								Keyspace: &vtctldatapb.Keyspace{
  1276  									Name: "ks2",
  1277  								},
  1278  							},
  1279  						},
  1280  					},
  1281  					GetKeyspacesResults: &struct {
  1282  						Keyspaces []*vtctldatapb.Keyspace
  1283  						Error     error
  1284  					}{
  1285  						Keyspaces: []*vtctldatapb.Keyspace{
  1286  							{
  1287  								Name: "ks1",
  1288  							},
  1289  							{
  1290  								Name: "ks2",
  1291  							},
  1292  						},
  1293  					},
  1294  					ReloadSchemaShardResults: map[string]struct {
  1295  						Response *vtctldatapb.ReloadSchemaShardResponse
  1296  						Error    error
  1297  					}{
  1298  						"ks1/-": {
  1299  							Response: &vtctldatapb.ReloadSchemaShardResponse{
  1300  								Events: []*logutilpb.Event{
  1301  									{},
  1302  								},
  1303  							},
  1304  						},
  1305  						"ks2/-80": {
  1306  							Response: &vtctldatapb.ReloadSchemaShardResponse{
  1307  								Events: []*logutilpb.Event{
  1308  									{}, {},
  1309  								},
  1310  							},
  1311  						},
  1312  						"ks2/80-": { // skipped via request params
  1313  							Error: assert.AnError,
  1314  						},
  1315  					},
  1316  				},
  1317  				topoReadPool: pools.NewRPCPool(5, 0, nil),
  1318  			},
  1319  			req: &vtadminpb.ReloadSchemasRequest{
  1320  				KeyspaceShards: []string{"ks1/-", "ks2/-80"},
  1321  			},
  1322  			expected: []*vtadminpb.ReloadSchemasResponse_ShardResult{
  1323  				{
  1324  					Shard: &vtadminpb.Shard{
  1325  						Cluster: &vtadminpb.Cluster{
  1326  							Id:   "test",
  1327  							Name: "test",
  1328  						},
  1329  						Shard: &vtctldatapb.Shard{
  1330  							Keyspace: "ks1",
  1331  							Name:     "-",
  1332  						},
  1333  					},
  1334  					Events: []*logutilpb.Event{
  1335  						{},
  1336  					},
  1337  				},
  1338  				{
  1339  					Shard: &vtadminpb.Shard{
  1340  						Cluster: &vtadminpb.Cluster{
  1341  							Id:   "test",
  1342  							Name: "test",
  1343  						},
  1344  						Shard: &vtctldatapb.Shard{
  1345  							Keyspace: "ks2",
  1346  							Name:     "-80",
  1347  						},
  1348  					},
  1349  					Events: []*logutilpb.Event{
  1350  						{}, {},
  1351  					},
  1352  				},
  1353  			},
  1354  		},
  1355  		{
  1356  			name: "one shard fails",
  1357  			cluster: &Cluster{
  1358  				ID:   "test",
  1359  				Name: "test",
  1360  				Vtctld: &fakevtctldclient.VtctldClient{
  1361  					FindAllShardsInKeyspaceResults: map[string]struct {
  1362  						Response *vtctldatapb.FindAllShardsInKeyspaceResponse
  1363  						Error    error
  1364  					}{
  1365  						"ks1": {
  1366  							Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
  1367  								Shards: map[string]*vtctldatapb.Shard{
  1368  									"-": {
  1369  										Keyspace: "ks1",
  1370  										Name:     "-",
  1371  									},
  1372  								},
  1373  							},
  1374  						},
  1375  						"ks2": {
  1376  							Response: &vtctldatapb.FindAllShardsInKeyspaceResponse{
  1377  								Shards: map[string]*vtctldatapb.Shard{
  1378  									"-80": {
  1379  										Keyspace: "ks2",
  1380  										Name:     "-80",
  1381  									},
  1382  									"80-": {
  1383  										Keyspace: "ks2",
  1384  										Name:     "80-",
  1385  									},
  1386  								},
  1387  							},
  1388  						},
  1389  					},
  1390  					GetKeyspaceResults: map[string]struct {
  1391  						Response *vtctldatapb.GetKeyspaceResponse
  1392  						Error    error
  1393  					}{
  1394  						"ks1": {
  1395  							Response: &vtctldatapb.GetKeyspaceResponse{
  1396  								Keyspace: &vtctldatapb.Keyspace{
  1397  									Name: "ks1",
  1398  								},
  1399  							},
  1400  						},
  1401  						"ks2": {
  1402  							Response: &vtctldatapb.GetKeyspaceResponse{
  1403  								Keyspace: &vtctldatapb.Keyspace{
  1404  									Name: "ks2",
  1405  								},
  1406  							},
  1407  						},
  1408  					},
  1409  					GetKeyspacesResults: &struct {
  1410  						Keyspaces []*vtctldatapb.Keyspace
  1411  						Error     error
  1412  					}{
  1413  						Keyspaces: []*vtctldatapb.Keyspace{
  1414  							{
  1415  								Name: "ks1",
  1416  							},
  1417  							{
  1418  								Name: "ks2",
  1419  							},
  1420  						},
  1421  					},
  1422  					ReloadSchemaShardResults: map[string]struct {
  1423  						Response *vtctldatapb.ReloadSchemaShardResponse
  1424  						Error    error
  1425  					}{
  1426  						"ks1/-": {
  1427  							Response: &vtctldatapb.ReloadSchemaShardResponse{
  1428  								Events: []*logutilpb.Event{
  1429  									{},
  1430  								},
  1431  							},
  1432  						},
  1433  						"ks2/-80": {
  1434  							Response: &vtctldatapb.ReloadSchemaShardResponse{
  1435  								Events: []*logutilpb.Event{
  1436  									{}, {},
  1437  								},
  1438  							},
  1439  						},
  1440  						"ks2/80-": {
  1441  							Error: assert.AnError,
  1442  						},
  1443  					},
  1444  				},
  1445  				topoReadPool: pools.NewRPCPool(5, 0, nil),
  1446  			},
  1447  			req: &vtadminpb.ReloadSchemasRequest{
  1448  				KeyspaceShards: []string{"ks1/-", "ks2/-80", "ks2/80-"},
  1449  			},
  1450  			shouldErr: true,
  1451  		},
  1452  		{
  1453  			name: "getShardSets failure",
  1454  			cluster: &Cluster{
  1455  				ID:   "test",
  1456  				Name: "test",
  1457  				Vtctld: &fakevtctldclient.VtctldClient{
  1458  					GetKeyspaceResults: map[string]struct {
  1459  						Response *vtctldatapb.GetKeyspaceResponse
  1460  						Error    error
  1461  					}{
  1462  						"ks1": {
  1463  							Error: assert.AnError,
  1464  						},
  1465  						"ks2": {
  1466  							Response: &vtctldatapb.GetKeyspaceResponse{
  1467  								Keyspace: &vtctldatapb.Keyspace{
  1468  									Name: "ks2",
  1469  								},
  1470  							},
  1471  						},
  1472  					},
  1473  					GetKeyspacesResults: &struct {
  1474  						Keyspaces []*vtctldatapb.Keyspace
  1475  						Error     error
  1476  					}{
  1477  						Keyspaces: []*vtctldatapb.Keyspace{
  1478  							{
  1479  								Name: "ks1",
  1480  							},
  1481  							{
  1482  								Name: "ks2",
  1483  							},
  1484  						},
  1485  					},
  1486  					ReloadSchemaShardResults: map[string]struct {
  1487  						Response *vtctldatapb.ReloadSchemaShardResponse
  1488  						Error    error
  1489  					}{
  1490  						"ks1/-": {
  1491  							Response: &vtctldatapb.ReloadSchemaShardResponse{
  1492  								Events: []*logutilpb.Event{
  1493  									{},
  1494  								},
  1495  							},
  1496  						},
  1497  						"ks2/-80": {
  1498  							Response: &vtctldatapb.ReloadSchemaShardResponse{
  1499  								Events: []*logutilpb.Event{
  1500  									{}, {},
  1501  								},
  1502  							},
  1503  						},
  1504  						"ks2/80-": { // skipped via request params
  1505  							Error: assert.AnError,
  1506  						},
  1507  					},
  1508  				},
  1509  				topoReadPool: pools.NewRPCPool(5, 0, nil),
  1510  			},
  1511  			req: &vtadminpb.ReloadSchemasRequest{
  1512  				KeyspaceShards: []string{"ks1/-"},
  1513  			},
  1514  			shouldErr: true,
  1515  		},
  1516  	}
  1517  
  1518  	for _, tt := range tests {
  1519  		tt := tt
  1520  
  1521  		t.Run(tt.name, func(t *testing.T) {
  1522  			t.Parallel()
  1523  
  1524  			results, err := tt.cluster.reloadShardSchemas(ctx, tt.req)
  1525  			if tt.shouldErr {
  1526  				assert.Error(t, err)
  1527  				return
  1528  			}
  1529  
  1530  			require.NoError(t, err)
  1531  
  1532  			keyFn := func(shard *vtadminpb.Shard) string {
  1533  				return fmt.Sprintf("%s/%s", shard.Shard.Keyspace, shard.Shard.Name)
  1534  			}
  1535  			sort.Slice(tt.expected, func(i, j int) bool {
  1536  				return keyFn(tt.expected[i].Shard) < keyFn(tt.expected[j].Shard)
  1537  			})
  1538  			sort.Slice(results, func(i, j int) bool {
  1539  				return keyFn(results[i].Shard) < keyFn(results[j].Shard)
  1540  			})
  1541  			utils.MustMatch(t, tt.expected, results)
  1542  		})
  1543  	}
  1544  }
  1545  
  1546  func Test_reloadTabletSchemas(t *testing.T) {
  1547  	t.Parallel()
  1548  
  1549  	ctx := context.Background()
  1550  	tests := []struct {
  1551  		name      string
  1552  		cluster   *Cluster
  1553  		tablets   []*vtadminpb.Tablet
  1554  		dbErr     bool
  1555  		req       *vtadminpb.ReloadSchemasRequest
  1556  		expected  []*vtadminpb.ReloadSchemasResponse_TabletResult
  1557  		shouldErr bool
  1558  	}{
  1559  		{
  1560  			name: "ok",
  1561  			cluster: &Cluster{
  1562  				ID:   "test",
  1563  				Name: "test",
  1564  				Vtctld: &fakevtctldclient.VtctldClient{
  1565  					ReloadSchemaResults: map[string]struct {
  1566  						Response *vtctldatapb.ReloadSchemaResponse
  1567  						Error    error
  1568  					}{
  1569  						"zone1-0000000100": {
  1570  							Response: &vtctldatapb.ReloadSchemaResponse{},
  1571  						},
  1572  						"zone1-0000000101": {
  1573  							Response: &vtctldatapb.ReloadSchemaResponse{},
  1574  						},
  1575  						"zone2-0000000200": {
  1576  							Response: &vtctldatapb.ReloadSchemaResponse{},
  1577  						},
  1578  						"zone5-0000000500": {
  1579  							Error: assert.AnError,
  1580  						},
  1581  					},
  1582  				},
  1583  			},
  1584  			tablets: []*vtadminpb.Tablet{
  1585  				{
  1586  					Tablet: &topodatapb.Tablet{
  1587  						Alias: &topodatapb.TabletAlias{
  1588  							Cell: "zone1",
  1589  							Uid:  100,
  1590  						},
  1591  					},
  1592  				},
  1593  				{
  1594  					Tablet: &topodatapb.Tablet{
  1595  						Alias: &topodatapb.TabletAlias{
  1596  							Cell: "zone1",
  1597  							Uid:  101,
  1598  						},
  1599  					},
  1600  				},
  1601  				{
  1602  					Tablet: &topodatapb.Tablet{
  1603  						Alias: &topodatapb.TabletAlias{
  1604  							Cell: "zone2",
  1605  							Uid:  200,
  1606  						},
  1607  					},
  1608  				},
  1609  				{
  1610  					Tablet: &topodatapb.Tablet{
  1611  						Alias: &topodatapb.TabletAlias{
  1612  							Cell: "zone5",
  1613  							Uid:  500,
  1614  						},
  1615  					},
  1616  				},
  1617  			},
  1618  			req: &vtadminpb.ReloadSchemasRequest{
  1619  				Tablets: []*topodatapb.TabletAlias{
  1620  					{Cell: "zone1", Uid: 100},
  1621  					{Cell: "zone1", Uid: 101},
  1622  					{Cell: "zone2", Uid: 200},
  1623  					{Cell: "zone5", Uid: 500},
  1624  				},
  1625  			},
  1626  			expected: []*vtadminpb.ReloadSchemasResponse_TabletResult{
  1627  				{
  1628  					Tablet: &vtadminpb.Tablet{
  1629  						Cluster: &vtadminpb.Cluster{
  1630  							Id:   "test",
  1631  							Name: "test",
  1632  						},
  1633  						Tablet: &topodatapb.Tablet{
  1634  							Alias: &topodatapb.TabletAlias{
  1635  								Cell: "zone1",
  1636  								Uid:  100,
  1637  							},
  1638  						},
  1639  					},
  1640  					Result: "ok",
  1641  				},
  1642  				{
  1643  					Tablet: &vtadminpb.Tablet{
  1644  						Cluster: &vtadminpb.Cluster{
  1645  							Id:   "test",
  1646  							Name: "test",
  1647  						},
  1648  						Tablet: &topodatapb.Tablet{
  1649  							Alias: &topodatapb.TabletAlias{
  1650  								Cell: "zone1",
  1651  								Uid:  101,
  1652  							},
  1653  						},
  1654  					},
  1655  					Result: "ok",
  1656  				},
  1657  				{
  1658  					Tablet: &vtadminpb.Tablet{
  1659  						Cluster: &vtadminpb.Cluster{
  1660  							Id:   "test",
  1661  							Name: "test",
  1662  						},
  1663  						Tablet: &topodatapb.Tablet{
  1664  							Alias: &topodatapb.TabletAlias{
  1665  								Cell: "zone2",
  1666  								Uid:  200,
  1667  							},
  1668  						},
  1669  					},
  1670  					Result: "ok",
  1671  				},
  1672  				{
  1673  					Tablet: &vtadminpb.Tablet{
  1674  						Cluster: &vtadminpb.Cluster{
  1675  							Id:   "test",
  1676  							Name: "test",
  1677  						},
  1678  						Tablet: &topodatapb.Tablet{
  1679  							Alias: &topodatapb.TabletAlias{
  1680  								Cell: "zone5",
  1681  								Uid:  500,
  1682  							},
  1683  						},
  1684  					},
  1685  					Result: assert.AnError.Error(),
  1686  				},
  1687  			},
  1688  		},
  1689  		{
  1690  			name:    "FindTablets error",
  1691  			cluster: &Cluster{},
  1692  			dbErr:   true,
  1693  			req: &vtadminpb.ReloadSchemasRequest{
  1694  				Tablets: []*topodatapb.TabletAlias{
  1695  					{Cell: "zone1", Uid: 100},
  1696  				},
  1697  			},
  1698  			shouldErr: true,
  1699  		},
  1700  	}
  1701  
  1702  	for _, tt := range tests {
  1703  		tt := tt
  1704  
  1705  		t.Run(tt.name, func(t *testing.T) {
  1706  			t.Parallel()
  1707  
  1708  			cfg := vtsql.WithDialFunc(func(c vitessdriver.Configuration) (*sql.DB, error) {
  1709  				return sql.OpenDB(&fakevtsql.Connector{
  1710  					Tablets:   tt.tablets,
  1711  					ShouldErr: tt.dbErr,
  1712  				}), nil
  1713  			})(&vtsql.Config{
  1714  				Cluster:         tt.cluster.ToProto(),
  1715  				ResolverOptions: &resolver.Options{},
  1716  			})
  1717  			db, err := vtsql.New(ctx, cfg)
  1718  			require.NoError(t, err)
  1719  			defer db.Close()
  1720  
  1721  			tt.cluster.DB = db
  1722  
  1723  			results, err := tt.cluster.reloadTabletSchemas(ctx, tt.req)
  1724  			if tt.shouldErr {
  1725  				assert.Error(t, err)
  1726  				return
  1727  			}
  1728  
  1729  			require.NoError(t, err)
  1730  
  1731  			sort.Slice(tt.expected, func(i, j int) bool {
  1732  				return topoproto.TabletAliasString(tt.expected[i].Tablet.Tablet.Alias) < topoproto.TabletAliasString(tt.expected[j].Tablet.Tablet.Alias)
  1733  			})
  1734  			sort.Slice(results, func(i, j int) bool {
  1735  				return topoproto.TabletAliasString(results[i].Tablet.Tablet.Alias) < topoproto.TabletAliasString(results[j].Tablet.Tablet.Alias)
  1736  			})
  1737  			utils.MustMatch(t, tt.expected, results)
  1738  		})
  1739  	}
  1740  }
  1741  
  1742  func TestTabletExternallyPromoted(t *testing.T) {
  1743  	t.Parallel()
  1744  
  1745  	testClusterProto := &vtadminpb.Cluster{
  1746  		Id:   "test",
  1747  		Name: "test",
  1748  	}
  1749  
  1750  	tests := []struct {
  1751  		name      string
  1752  		cluster   *Cluster
  1753  		setup     func(t testing.TB, c *Cluster)
  1754  		timeout   time.Duration
  1755  		tablet    *vtadminpb.Tablet
  1756  		expected  *vtadminpb.TabletExternallyPromotedResponse
  1757  		shouldErr bool
  1758  	}{
  1759  		{
  1760  			name: "ok",
  1761  			cluster: &Cluster{
  1762  				ID:   "test",
  1763  				Name: "test",
  1764  				Vtctld: &fakevtctldclient.VtctldClient{
  1765  					TabletExternallyReparentedResults: map[string]struct {
  1766  						Response *vtctldatapb.TabletExternallyReparentedResponse
  1767  						Error    error
  1768  					}{
  1769  						"zone1-0000000100": {
  1770  							Response: &vtctldatapb.TabletExternallyReparentedResponse{
  1771  								Keyspace: "ks1",
  1772  								Shard:    "-",
  1773  								NewPrimary: &topodatapb.TabletAlias{
  1774  									Cell: "zone1",
  1775  									Uid:  100,
  1776  								},
  1777  								OldPrimary: &topodatapb.TabletAlias{
  1778  									Cell: "zone1",
  1779  									Uid:  200,
  1780  								},
  1781  							},
  1782  						},
  1783  						"zone1-0000000200": {
  1784  							Error: fmt.Errorf("some error: %w", assert.AnError),
  1785  						},
  1786  					},
  1787  				},
  1788  				topoRWPool: pools.NewRPCPool(1, time.Second, nil),
  1789  			},
  1790  			tablet: &vtadminpb.Tablet{
  1791  				Tablet: &topodatapb.Tablet{
  1792  					Alias: &topodatapb.TabletAlias{
  1793  						Cell: "zone1",
  1794  						Uid:  100,
  1795  					},
  1796  				},
  1797  			},
  1798  			expected: &vtadminpb.TabletExternallyPromotedResponse{
  1799  				Cluster:  testClusterProto,
  1800  				Keyspace: "ks1",
  1801  				Shard:    "-",
  1802  				NewPrimary: &topodatapb.TabletAlias{
  1803  					Cell: "zone1",
  1804  					Uid:  100,
  1805  				},
  1806  				OldPrimary: &topodatapb.TabletAlias{
  1807  					Cell: "zone1",
  1808  					Uid:  200,
  1809  				},
  1810  			},
  1811  		},
  1812  		{
  1813  			name: "error",
  1814  			cluster: &Cluster{
  1815  				ID:   "test",
  1816  				Name: "test",
  1817  				Vtctld: &fakevtctldclient.VtctldClient{
  1818  					TabletExternallyReparentedResults: map[string]struct {
  1819  						Response *vtctldatapb.TabletExternallyReparentedResponse
  1820  						Error    error
  1821  					}{
  1822  						"zone1-0000000100": {
  1823  							Response: &vtctldatapb.TabletExternallyReparentedResponse{
  1824  								Keyspace: "ks1",
  1825  								Shard:    "-",
  1826  								NewPrimary: &topodatapb.TabletAlias{
  1827  									Cell: "zone1",
  1828  									Uid:  100,
  1829  								},
  1830  								OldPrimary: &topodatapb.TabletAlias{
  1831  									Cell: "zone1",
  1832  									Uid:  200,
  1833  								},
  1834  							},
  1835  						},
  1836  						"zone1-0000000200": {
  1837  							Error: fmt.Errorf("some error: %w", assert.AnError),
  1838  						},
  1839  					},
  1840  				},
  1841  				topoRWPool: pools.NewRPCPool(1, time.Second, nil),
  1842  			},
  1843  			tablet: &vtadminpb.Tablet{
  1844  				Tablet: &topodatapb.Tablet{
  1845  					Alias: &topodatapb.TabletAlias{
  1846  						Cell: "zone1",
  1847  						Uid:  200,
  1848  					},
  1849  				},
  1850  			},
  1851  			shouldErr: true,
  1852  		},
  1853  		{
  1854  			name: "pool full",
  1855  			cluster: &Cluster{
  1856  				ID:   "test",
  1857  				Name: "test",
  1858  				Vtctld: &fakevtctldclient.VtctldClient{
  1859  					TabletExternallyReparentedResults: map[string]struct {
  1860  						Response *vtctldatapb.TabletExternallyReparentedResponse
  1861  						Error    error
  1862  					}{
  1863  						"zone1-0000000100": {
  1864  							Response: &vtctldatapb.TabletExternallyReparentedResponse{
  1865  								Keyspace: "ks1",
  1866  								Shard:    "-",
  1867  								NewPrimary: &topodatapb.TabletAlias{
  1868  									Cell: "zone1",
  1869  									Uid:  100,
  1870  								},
  1871  								OldPrimary: &topodatapb.TabletAlias{
  1872  									Cell: "zone1",
  1873  									Uid:  200,
  1874  								},
  1875  							},
  1876  						},
  1877  						"zone1-0000000200": {
  1878  							Error: fmt.Errorf("some error: %w", assert.AnError),
  1879  						},
  1880  					},
  1881  				},
  1882  				topoRWPool: pools.NewRPCPool(1, time.Millisecond*25, nil),
  1883  			},
  1884  			setup: func(t testing.TB, c *Cluster) {
  1885  				ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
  1886  				defer cancel()
  1887  
  1888  				err := c.topoRWPool.Acquire(ctx)
  1889  				require.NoError(t, err, "could not block topoRW pool in setup")
  1890  				t.Cleanup(c.topoRWPool.Release)
  1891  			},
  1892  			timeout: time.Millisecond * 50,
  1893  			tablet: &vtadminpb.Tablet{
  1894  				Tablet: &topodatapb.Tablet{
  1895  					Alias: &topodatapb.TabletAlias{
  1896  						Cell: "zone1",
  1897  						Uid:  100,
  1898  					},
  1899  				},
  1900  			},
  1901  			shouldErr: true,
  1902  		},
  1903  	}
  1904  
  1905  	for _, tt := range tests {
  1906  		tt := tt
  1907  
  1908  		t.Run(tt.name, func(t *testing.T) {
  1909  			t.Parallel()
  1910  
  1911  			if tt.setup != nil {
  1912  				func(t *testing.T) {
  1913  					t.Helper()
  1914  					tt.setup(t, tt.cluster)
  1915  				}(t)
  1916  			}
  1917  
  1918  			var (
  1919  				ctx    context.Context
  1920  				cancel context.CancelFunc
  1921  			)
  1922  			switch tt.timeout {
  1923  			case 0:
  1924  				ctx, cancel = context.WithCancel(context.Background())
  1925  			default:
  1926  				ctx, cancel = context.WithTimeout(context.Background(), tt.timeout)
  1927  			}
  1928  			defer cancel()
  1929  
  1930  			resp, err := tt.cluster.TabletExternallyPromoted(ctx, tt.tablet)
  1931  			if tt.shouldErr {
  1932  				assert.Error(t, err)
  1933  				return
  1934  			}
  1935  
  1936  			require.NoError(t, err)
  1937  			utils.MustMatch(t, tt.expected, resp)
  1938  		})
  1939  	}
  1940  }