vitess.io/vitess@v0.16.2/go/vt/vtctl/reparentutil/util_test.go (about)

     1  /*
     2  Copyright 20201 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 reparentutil
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/stretchr/testify/assert"
    26  	"github.com/stretchr/testify/require"
    27  
    28  	"vitess.io/vitess/go/mysql"
    29  	"vitess.io/vitess/go/test/utils"
    30  	"vitess.io/vitess/go/vt/logutil"
    31  	"vitess.io/vitess/go/vt/topo"
    32  	"vitess.io/vitess/go/vt/vtctl/grpcvtctldserver/testutil"
    33  	"vitess.io/vitess/go/vt/vtctl/reparentutil/promotionrule"
    34  	"vitess.io/vitess/go/vt/vterrors"
    35  	"vitess.io/vitess/go/vt/vttablet/tmclient"
    36  
    37  	replicationdatapb "vitess.io/vitess/go/vt/proto/replicationdata"
    38  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    39  	"vitess.io/vitess/go/vt/proto/vttime"
    40  	"vitess.io/vitess/go/vt/topo/topoproto"
    41  )
    42  
    43  type chooseNewPrimaryTestTMClient struct {
    44  	tmclient.TabletManagerClient
    45  	replicationStatuses map[string]*replicationdatapb.Status
    46  }
    47  
    48  func (fake *chooseNewPrimaryTestTMClient) ReplicationStatus(ctx context.Context, tablet *topodatapb.Tablet) (*replicationdatapb.Status, error) {
    49  	if fake.replicationStatuses == nil {
    50  		return nil, assert.AnError
    51  	}
    52  
    53  	key := topoproto.TabletAliasString(tablet.Alias)
    54  
    55  	if status, ok := fake.replicationStatuses[key]; ok {
    56  		return status, nil
    57  	}
    58  
    59  	return nil, assert.AnError
    60  }
    61  
    62  func TestChooseNewPrimary(t *testing.T) {
    63  	t.Parallel()
    64  
    65  	ctx := context.Background()
    66  	logger := logutil.NewMemoryLogger()
    67  	tests := []struct {
    68  		name              string
    69  		tmc               *chooseNewPrimaryTestTMClient
    70  		shardInfo         *topo.ShardInfo
    71  		tabletMap         map[string]*topo.TabletInfo
    72  		avoidPrimaryAlias *topodatapb.TabletAlias
    73  		expected          *topodatapb.TabletAlias
    74  		shouldErr         bool
    75  	}{
    76  		{
    77  			name: "found a replica",
    78  			tmc: &chooseNewPrimaryTestTMClient{
    79  				// zone1-101 is behind zone1-102
    80  				replicationStatuses: map[string]*replicationdatapb.Status{
    81  					"zone1-0000000101": {
    82  						Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1",
    83  					},
    84  					"zone1-0000000102": {
    85  						Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5",
    86  					},
    87  				},
    88  			},
    89  			shardInfo: topo.NewShardInfo("testkeyspace", "-", &topodatapb.Shard{
    90  				PrimaryAlias: &topodatapb.TabletAlias{
    91  					Cell: "zone1",
    92  					Uid:  100,
    93  				},
    94  			}, nil),
    95  			tabletMap: map[string]*topo.TabletInfo{
    96  				"primary": {
    97  					Tablet: &topodatapb.Tablet{
    98  						Alias: &topodatapb.TabletAlias{
    99  							Cell: "zone1",
   100  							Uid:  100,
   101  						},
   102  						Type: topodatapb.TabletType_PRIMARY,
   103  					},
   104  				},
   105  				"replica1": {
   106  					Tablet: &topodatapb.Tablet{
   107  						Alias: &topodatapb.TabletAlias{
   108  							Cell: "zone1",
   109  							Uid:  101,
   110  						},
   111  						Type: topodatapb.TabletType_REPLICA,
   112  					},
   113  				},
   114  				"replica2": {
   115  					Tablet: &topodatapb.Tablet{
   116  						Alias: &topodatapb.TabletAlias{
   117  							Cell: "zone1",
   118  							Uid:  102,
   119  						},
   120  						Type: topodatapb.TabletType_REPLICA,
   121  					},
   122  				},
   123  			},
   124  			avoidPrimaryAlias: &topodatapb.TabletAlias{
   125  				Cell: "zone1",
   126  				Uid:  0,
   127  			},
   128  			expected: &topodatapb.TabletAlias{
   129  				Cell: "zone1",
   130  				Uid:  102,
   131  			},
   132  			shouldErr: false,
   133  		},
   134  		{
   135  			name: "found a replica - more advanced relay log position",
   136  			tmc: &chooseNewPrimaryTestTMClient{
   137  				// zone1-101 is behind zone1-102
   138  				// since the relay log position for zone1-102 is more advanced
   139  				replicationStatuses: map[string]*replicationdatapb.Status{
   140  					"zone1-0000000101": {
   141  						Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-2",
   142  					},
   143  					"zone1-0000000102": {
   144  						Position:         "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1",
   145  						RelayLogPosition: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5",
   146  					},
   147  				},
   148  			},
   149  			shardInfo: topo.NewShardInfo("testkeyspace", "-", &topodatapb.Shard{
   150  				PrimaryAlias: &topodatapb.TabletAlias{
   151  					Cell: "zone1",
   152  					Uid:  100,
   153  				},
   154  			}, nil),
   155  			tabletMap: map[string]*topo.TabletInfo{
   156  				"primary": {
   157  					Tablet: &topodatapb.Tablet{
   158  						Alias: &topodatapb.TabletAlias{
   159  							Cell: "zone1",
   160  							Uid:  100,
   161  						},
   162  						Type: topodatapb.TabletType_PRIMARY,
   163  					},
   164  				},
   165  				"replica1": {
   166  					Tablet: &topodatapb.Tablet{
   167  						Alias: &topodatapb.TabletAlias{
   168  							Cell: "zone1",
   169  							Uid:  101,
   170  						},
   171  						Type: topodatapb.TabletType_REPLICA,
   172  					},
   173  				},
   174  				"replica2": {
   175  					Tablet: &topodatapb.Tablet{
   176  						Alias: &topodatapb.TabletAlias{
   177  							Cell: "zone1",
   178  							Uid:  102,
   179  						},
   180  						Type: topodatapb.TabletType_REPLICA,
   181  					},
   182  				},
   183  			},
   184  			avoidPrimaryAlias: &topodatapb.TabletAlias{
   185  				Cell: "zone1",
   186  				Uid:  0,
   187  			},
   188  			expected: &topodatapb.TabletAlias{
   189  				Cell: "zone1",
   190  				Uid:  102,
   191  			},
   192  			shouldErr: false,
   193  		},
   194  		{
   195  			name: "no active primary in shard",
   196  			tmc: &chooseNewPrimaryTestTMClient{
   197  				replicationStatuses: map[string]*replicationdatapb.Status{
   198  					"zone1-0000000101": {
   199  						Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1",
   200  					},
   201  				},
   202  			},
   203  			shardInfo: topo.NewShardInfo("testkeyspace", "-", &topodatapb.Shard{}, nil),
   204  			tabletMap: map[string]*topo.TabletInfo{
   205  				"primary": {
   206  					Tablet: &topodatapb.Tablet{
   207  						Alias: &topodatapb.TabletAlias{
   208  							Cell: "zone1",
   209  							Uid:  100,
   210  						},
   211  						Type: topodatapb.TabletType_PRIMARY,
   212  					},
   213  				},
   214  				"replica1": {
   215  					Tablet: &topodatapb.Tablet{
   216  						Alias: &topodatapb.TabletAlias{
   217  							Cell: "zone1",
   218  							Uid:  101,
   219  						},
   220  						Type: topodatapb.TabletType_REPLICA,
   221  					},
   222  				},
   223  			},
   224  			avoidPrimaryAlias: &topodatapb.TabletAlias{
   225  				Cell: "zone1",
   226  				Uid:  0,
   227  			},
   228  			expected: &topodatapb.TabletAlias{
   229  				Cell: "zone1",
   230  				Uid:  101,
   231  			},
   232  			shouldErr: false,
   233  		},
   234  		{
   235  			name: "avoid primary alias is nil",
   236  			tmc: &chooseNewPrimaryTestTMClient{
   237  				replicationStatuses: map[string]*replicationdatapb.Status{
   238  					"zone1-0000000101": {
   239  						Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1",
   240  					},
   241  				},
   242  			},
   243  			shardInfo: topo.NewShardInfo("testkeyspace", "-", &topodatapb.Shard{
   244  				PrimaryAlias: &topodatapb.TabletAlias{
   245  					Cell: "zone1",
   246  					Uid:  100,
   247  				},
   248  			}, nil),
   249  			tabletMap: map[string]*topo.TabletInfo{
   250  				"primary": {
   251  					Tablet: &topodatapb.Tablet{
   252  						Alias: &topodatapb.TabletAlias{
   253  							Cell: "zone1",
   254  							Uid:  100,
   255  						},
   256  						Type: topodatapb.TabletType_PRIMARY,
   257  					},
   258  				},
   259  				"replica1": {
   260  					Tablet: &topodatapb.Tablet{
   261  						Alias: &topodatapb.TabletAlias{
   262  							Cell: "zone1",
   263  							Uid:  101,
   264  						},
   265  						Type: topodatapb.TabletType_REPLICA,
   266  					},
   267  				},
   268  			},
   269  			avoidPrimaryAlias: nil,
   270  			expected: &topodatapb.TabletAlias{
   271  				Cell: "zone1",
   272  				Uid:  101,
   273  			},
   274  			shouldErr: false,
   275  		}, {
   276  			name: "avoid primary alias and shard primary are nil",
   277  			tmc: &chooseNewPrimaryTestTMClient{
   278  				replicationStatuses: map[string]*replicationdatapb.Status{
   279  					"zone1-0000000100": {
   280  						Position: "",
   281  					},
   282  					"zone1-0000000101": {
   283  						Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1",
   284  					},
   285  				},
   286  			},
   287  			shardInfo: topo.NewShardInfo("testkeyspace", "-", &topodatapb.Shard{}, nil),
   288  			tabletMap: map[string]*topo.TabletInfo{
   289  				"replica1": {
   290  					Tablet: &topodatapb.Tablet{
   291  						Alias: &topodatapb.TabletAlias{
   292  							Cell: "zone1",
   293  							Uid:  100,
   294  						},
   295  						Type: topodatapb.TabletType_REPLICA,
   296  					},
   297  				},
   298  				"replica2": {
   299  					Tablet: &topodatapb.Tablet{
   300  						Alias: &topodatapb.TabletAlias{
   301  							Cell: "zone1",
   302  							Uid:  101,
   303  						},
   304  						Type: topodatapb.TabletType_REPLICA,
   305  					},
   306  				},
   307  			},
   308  			avoidPrimaryAlias: nil,
   309  			expected: &topodatapb.TabletAlias{
   310  				Cell: "zone1",
   311  				Uid:  101,
   312  			},
   313  			shouldErr: false,
   314  		},
   315  		{
   316  			name: "no replicas in primary cell",
   317  			tmc: &chooseNewPrimaryTestTMClient{
   318  				// zone1-101 is behind zone1-102
   319  				replicationStatuses: map[string]*replicationdatapb.Status{
   320  					"zone1-0000000101": {
   321  						Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1",
   322  					},
   323  					"zone1-0000000102": {
   324  						Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5",
   325  					},
   326  				},
   327  			},
   328  			shardInfo: topo.NewShardInfo("testkeyspace", "-", &topodatapb.Shard{
   329  				PrimaryAlias: &topodatapb.TabletAlias{
   330  					Cell: "zone2",
   331  					Uid:  200,
   332  				},
   333  			}, nil),
   334  			tabletMap: map[string]*topo.TabletInfo{
   335  				"primary": {
   336  					Tablet: &topodatapb.Tablet{
   337  						Alias: &topodatapb.TabletAlias{
   338  							Cell: "zone2",
   339  							Uid:  200,
   340  						},
   341  						Type: topodatapb.TabletType_PRIMARY,
   342  					},
   343  				},
   344  				"replica1": {
   345  					Tablet: &topodatapb.Tablet{
   346  						Alias: &topodatapb.TabletAlias{
   347  							Cell: "zone1",
   348  							Uid:  101,
   349  						},
   350  						Type: topodatapb.TabletType_REPLICA,
   351  					},
   352  				},
   353  				"replica2": {
   354  					Tablet: &topodatapb.Tablet{
   355  						Alias: &topodatapb.TabletAlias{
   356  							Cell: "zone1",
   357  							Uid:  102,
   358  						},
   359  						Type: topodatapb.TabletType_REPLICA,
   360  					},
   361  				},
   362  			},
   363  			avoidPrimaryAlias: &topodatapb.TabletAlias{
   364  				Cell: "zone1",
   365  				Uid:  0,
   366  			},
   367  			expected:  nil,
   368  			shouldErr: false,
   369  		},
   370  		{
   371  			name: "only available tablet is AvoidPrimary",
   372  			tmc: &chooseNewPrimaryTestTMClient{
   373  				// zone1-101 is behind zone1-102
   374  				replicationStatuses: map[string]*replicationdatapb.Status{
   375  					"zone1-0000000101": {
   376  						Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1",
   377  					},
   378  					"zone1-0000000102": {
   379  						Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5",
   380  					},
   381  				},
   382  			},
   383  			shardInfo: topo.NewShardInfo("testkeyspace", "-", &topodatapb.Shard{
   384  				PrimaryAlias: &topodatapb.TabletAlias{
   385  					Cell: "zone1",
   386  					Uid:  100,
   387  				},
   388  			}, nil),
   389  			tabletMap: map[string]*topo.TabletInfo{
   390  				"avoid-primary": {
   391  					Tablet: &topodatapb.Tablet{
   392  						Alias: &topodatapb.TabletAlias{
   393  							Cell: "zone1",
   394  							Uid:  101,
   395  						},
   396  						Type: topodatapb.TabletType_REPLICA,
   397  					},
   398  				},
   399  			},
   400  			avoidPrimaryAlias: &topodatapb.TabletAlias{
   401  				Cell: "zone1",
   402  				Uid:  101,
   403  			},
   404  			expected:  nil,
   405  			shouldErr: false,
   406  		},
   407  		{
   408  			name: "no replicas in shard",
   409  			tmc:  &chooseNewPrimaryTestTMClient{},
   410  			shardInfo: topo.NewShardInfo("testkeyspace", "-", &topodatapb.Shard{
   411  				PrimaryAlias: &topodatapb.TabletAlias{
   412  					Cell: "zone1",
   413  					Uid:  100,
   414  				},
   415  			}, nil),
   416  			tabletMap: map[string]*topo.TabletInfo{
   417  				"primary": {
   418  					Tablet: &topodatapb.Tablet{
   419  						Alias: &topodatapb.TabletAlias{
   420  							Cell: "zone1",
   421  							Uid:  100,
   422  						},
   423  						Type: topodatapb.TabletType_PRIMARY,
   424  					},
   425  				},
   426  			},
   427  			avoidPrimaryAlias: &topodatapb.TabletAlias{
   428  				Cell: "zone1",
   429  				Uid:  0,
   430  			},
   431  			expected:  nil,
   432  			shouldErr: false,
   433  		},
   434  	}
   435  
   436  	durability, err := GetDurabilityPolicy("none")
   437  	require.NoError(t, err)
   438  	for _, tt := range tests {
   439  		tt := tt
   440  
   441  		t.Run(tt.name, func(t *testing.T) {
   442  			t.Parallel()
   443  
   444  			actual, err := ChooseNewPrimary(ctx, tt.tmc, tt.shardInfo, tt.tabletMap, tt.avoidPrimaryAlias, time.Millisecond*50, durability, logger)
   445  			if tt.shouldErr {
   446  				assert.Error(t, err)
   447  				return
   448  			}
   449  
   450  			assert.NoError(t, err)
   451  			utils.MustMatch(t, tt.expected, actual)
   452  		})
   453  	}
   454  }
   455  
   456  func TestFindPositionForTablet(t *testing.T) {
   457  	t.Parallel()
   458  
   459  	ctx := context.Background()
   460  	logger := logutil.NewMemoryLogger()
   461  	tests := []struct {
   462  		name             string
   463  		tmc              *testutil.TabletManagerClient
   464  		tablet           *topodatapb.Tablet
   465  		expectedPosition string
   466  		expectedErr      string
   467  	}{
   468  		{
   469  			name: "executed gtid set",
   470  			tmc: &testutil.TabletManagerClient{
   471  				ReplicationStatusResults: map[string]struct {
   472  					Position *replicationdatapb.Status
   473  					Error    error
   474  				}{
   475  					"zone1-0000000100": {
   476  						Position: &replicationdatapb.Status{
   477  							Position: "MySQL56/3e11fa47-71ca-11e1-9e33-c80aa9429562:1-5",
   478  						},
   479  					},
   480  				},
   481  			},
   482  			tablet: &topodatapb.Tablet{
   483  				Alias: &topodatapb.TabletAlias{
   484  					Cell: "zone1",
   485  					Uid:  100,
   486  				},
   487  			},
   488  			expectedPosition: "MySQL56/3e11fa47-71ca-11e1-9e33-c80aa9429562:1-5",
   489  		}, {
   490  			name: "no replication status",
   491  			tmc: &testutil.TabletManagerClient{
   492  				ReplicationStatusResults: map[string]struct {
   493  					Position *replicationdatapb.Status
   494  					Error    error
   495  				}{
   496  					"zone1-0000000100": {
   497  						Error: vterrors.ToGRPC(vterrors.Wrap(mysql.ErrNotReplica, "before status failed")),
   498  					},
   499  				},
   500  			},
   501  			tablet: &topodatapb.Tablet{
   502  				Alias: &topodatapb.TabletAlias{
   503  					Cell: "zone1",
   504  					Uid:  100,
   505  				},
   506  			},
   507  			expectedPosition: "",
   508  		}, {
   509  			name: "relay log",
   510  			tmc: &testutil.TabletManagerClient{
   511  				ReplicationStatusResults: map[string]struct {
   512  					Position *replicationdatapb.Status
   513  					Error    error
   514  				}{
   515  					"zone1-0000000100": {
   516  						Position: &replicationdatapb.Status{
   517  							Position:         "unused",
   518  							RelayLogPosition: "MySQL56/3e11fa47-71ca-11e1-9e33-c80aa9429562:1-5",
   519  						},
   520  					},
   521  				},
   522  			},
   523  			tablet: &topodatapb.Tablet{
   524  				Alias: &topodatapb.TabletAlias{
   525  					Cell: "zone1",
   526  					Uid:  100,
   527  				},
   528  			},
   529  			expectedPosition: "MySQL56/3e11fa47-71ca-11e1-9e33-c80aa9429562:1-5",
   530  		}, {
   531  			name: "error in parsing position",
   532  			tmc: &testutil.TabletManagerClient{
   533  				ReplicationStatusResults: map[string]struct {
   534  					Position *replicationdatapb.Status
   535  					Error    error
   536  				}{
   537  					"zone1-0000000100": {
   538  						Position: &replicationdatapb.Status{
   539  							Position: "unused",
   540  						},
   541  					},
   542  				},
   543  			},
   544  			tablet: &topodatapb.Tablet{
   545  				Alias: &topodatapb.TabletAlias{
   546  					Cell: "zone1",
   547  					Uid:  100,
   548  				},
   549  			},
   550  			expectedErr: `parse error: unknown GTIDSet flavor ""`,
   551  		},
   552  	}
   553  
   554  	for _, test := range tests {
   555  		t.Run(test.name, func(t *testing.T) {
   556  			pos, err := findPositionForTablet(ctx, test.tablet, logger, test.tmc, 10*time.Second)
   557  			if test.expectedErr != "" {
   558  				require.EqualError(t, err, test.expectedErr)
   559  				return
   560  			}
   561  			require.NoError(t, err)
   562  			posString := mysql.EncodePosition(pos)
   563  			require.Equal(t, test.expectedPosition, posString)
   564  		})
   565  	}
   566  }
   567  
   568  func TestFindCurrentPrimary(t *testing.T) {
   569  	t.Parallel()
   570  
   571  	// The exact values of the tablet aliases don't matter to this function, but
   572  	// we need them to be non-nil, so we'll just make one and reuse it.
   573  	alias := &topodatapb.TabletAlias{
   574  		Cell: "zone1",
   575  		Uid:  100,
   576  	}
   577  	logger := logutil.NewMemoryLogger()
   578  	tests := []struct {
   579  		name     string
   580  		in       map[string]*topo.TabletInfo
   581  		expected *topo.TabletInfo
   582  	}{
   583  		{
   584  			name: "single current primary",
   585  			in: map[string]*topo.TabletInfo{
   586  				"primary": {
   587  					Tablet: &topodatapb.Tablet{
   588  						Alias: alias,
   589  						Type:  topodatapb.TabletType_PRIMARY,
   590  						PrimaryTermStartTime: &vttime.Time{
   591  							Seconds: 100,
   592  						},
   593  						Hostname: "primary-tablet",
   594  					},
   595  				},
   596  				"replica": {
   597  					Tablet: &topodatapb.Tablet{
   598  						Alias:    alias,
   599  						Type:     topodatapb.TabletType_REPLICA,
   600  						Hostname: "replica-tablet",
   601  					},
   602  				},
   603  				"rdonly": {
   604  					Tablet: &topodatapb.Tablet{
   605  						Alias:    alias,
   606  						Type:     topodatapb.TabletType_RDONLY,
   607  						Hostname: "rdonly-tablet",
   608  					},
   609  				},
   610  			},
   611  			expected: &topo.TabletInfo{
   612  				Tablet: &topodatapb.Tablet{
   613  					Alias: alias,
   614  					Type:  topodatapb.TabletType_PRIMARY,
   615  					PrimaryTermStartTime: &vttime.Time{
   616  						Seconds: 100,
   617  					},
   618  					Hostname: "primary-tablet",
   619  				},
   620  			},
   621  		},
   622  		{
   623  			name: "no primaries",
   624  			in: map[string]*topo.TabletInfo{
   625  				"replica1": {
   626  					Tablet: &topodatapb.Tablet{
   627  						Alias:    alias,
   628  						Type:     topodatapb.TabletType_REPLICA,
   629  						Hostname: "replica-tablet-1",
   630  					},
   631  				},
   632  				"replica2": {
   633  					Tablet: &topodatapb.Tablet{
   634  						Alias:    alias,
   635  						Type:     topodatapb.TabletType_REPLICA,
   636  						Hostname: "replica-tablet-2",
   637  					},
   638  				},
   639  				"rdonly": {
   640  					Tablet: &topodatapb.Tablet{
   641  						Alias:    alias,
   642  						Type:     topodatapb.TabletType_RDONLY,
   643  						Hostname: "rdonly-tablet",
   644  					},
   645  				},
   646  			},
   647  			expected: nil,
   648  		},
   649  		{
   650  			name: "multiple primaries with one true primary",
   651  			in: map[string]*topo.TabletInfo{
   652  				"stale-primary": {
   653  					Tablet: &topodatapb.Tablet{
   654  						Alias: alias,
   655  						Type:  topodatapb.TabletType_PRIMARY,
   656  						PrimaryTermStartTime: &vttime.Time{
   657  							Seconds: 100,
   658  						},
   659  						Hostname: "stale-primary-tablet",
   660  					},
   661  				},
   662  				"true-primary": {
   663  					Tablet: &topodatapb.Tablet{
   664  						Alias: alias,
   665  						Type:  topodatapb.TabletType_PRIMARY,
   666  						PrimaryTermStartTime: &vttime.Time{
   667  							Seconds: 1000,
   668  						},
   669  						Hostname: "true-primary-tablet",
   670  					},
   671  				},
   672  				"rdonly": {
   673  					Tablet: &topodatapb.Tablet{
   674  						Alias:    alias,
   675  						Type:     topodatapb.TabletType_RDONLY,
   676  						Hostname: "rdonly-tablet",
   677  					},
   678  				},
   679  			},
   680  			expected: &topo.TabletInfo{
   681  				Tablet: &topodatapb.Tablet{
   682  					Alias: alias,
   683  					Type:  topodatapb.TabletType_PRIMARY,
   684  					PrimaryTermStartTime: &vttime.Time{
   685  						Seconds: 1000,
   686  					},
   687  					Hostname: "true-primary-tablet",
   688  				},
   689  			},
   690  		},
   691  		{
   692  			name: "multiple primaries with same term start",
   693  			in: map[string]*topo.TabletInfo{
   694  				"primary1": {
   695  					Tablet: &topodatapb.Tablet{
   696  						Alias: alias,
   697  						Type:  topodatapb.TabletType_PRIMARY,
   698  						PrimaryTermStartTime: &vttime.Time{
   699  							Seconds: 100,
   700  						},
   701  						Hostname: "primary-tablet-1",
   702  					},
   703  				},
   704  				"primary2": {
   705  					Tablet: &topodatapb.Tablet{
   706  						Alias: alias,
   707  						Type:  topodatapb.TabletType_PRIMARY,
   708  						PrimaryTermStartTime: &vttime.Time{
   709  							Seconds: 100,
   710  						},
   711  						Hostname: "primary-tablet-2",
   712  					},
   713  				},
   714  				"rdonly": {
   715  					Tablet: &topodatapb.Tablet{
   716  						Alias:    alias,
   717  						Type:     topodatapb.TabletType_RDONLY,
   718  						Hostname: "rdonly-tablet",
   719  					},
   720  				},
   721  			},
   722  			expected: nil,
   723  		},
   724  	}
   725  
   726  	for _, tt := range tests {
   727  		tt := tt
   728  
   729  		t.Run(tt.name, func(t *testing.T) {
   730  			t.Parallel()
   731  
   732  			actual := FindCurrentPrimary(tt.in, logger)
   733  			assert.Equal(t, tt.expected, actual)
   734  		})
   735  	}
   736  }
   737  
   738  func TestGetValidCandidatesAndPositionsAsList(t *testing.T) {
   739  	sid1 := mysql.SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
   740  	mysqlGTID1 := mysql.Mysql56GTID{
   741  		Server:   sid1,
   742  		Sequence: 9,
   743  	}
   744  	mysqlGTID2 := mysql.Mysql56GTID{
   745  		Server:   sid1,
   746  		Sequence: 10,
   747  	}
   748  	mysqlGTID3 := mysql.Mysql56GTID{
   749  		Server:   sid1,
   750  		Sequence: 11,
   751  	}
   752  
   753  	positionMostAdvanced := mysql.Position{GTIDSet: mysql.Mysql56GTIDSet{}}
   754  	positionMostAdvanced.GTIDSet = positionMostAdvanced.GTIDSet.AddGTID(mysqlGTID1)
   755  	positionMostAdvanced.GTIDSet = positionMostAdvanced.GTIDSet.AddGTID(mysqlGTID2)
   756  	positionMostAdvanced.GTIDSet = positionMostAdvanced.GTIDSet.AddGTID(mysqlGTID3)
   757  
   758  	positionIntermediate1 := mysql.Position{GTIDSet: mysql.Mysql56GTIDSet{}}
   759  	positionIntermediate1.GTIDSet = positionIntermediate1.GTIDSet.AddGTID(mysqlGTID1)
   760  
   761  	positionIntermediate2 := mysql.Position{GTIDSet: mysql.Mysql56GTIDSet{}}
   762  	positionIntermediate2.GTIDSet = positionIntermediate2.GTIDSet.AddGTID(mysqlGTID1)
   763  	positionIntermediate2.GTIDSet = positionIntermediate2.GTIDSet.AddGTID(mysqlGTID2)
   764  
   765  	tests := []struct {
   766  		name            string
   767  		validCandidates map[string]mysql.Position
   768  		tabletMap       map[string]*topo.TabletInfo
   769  		tabletRes       []*topodatapb.Tablet
   770  	}{
   771  		{
   772  			name: "test conversion",
   773  			validCandidates: map[string]mysql.Position{
   774  				"zone1-0000000100": positionMostAdvanced,
   775  				"zone1-0000000101": positionIntermediate1,
   776  				"zone1-0000000102": positionIntermediate2,
   777  			},
   778  			tabletMap: map[string]*topo.TabletInfo{
   779  				"zone1-0000000100": {
   780  					Tablet: &topodatapb.Tablet{
   781  						Alias: &topodatapb.TabletAlias{
   782  							Cell: "zone1",
   783  							Uid:  100,
   784  						},
   785  						Hostname: "primary-elect",
   786  					},
   787  				},
   788  				"zone1-0000000101": {
   789  					Tablet: &topodatapb.Tablet{
   790  						Alias: &topodatapb.TabletAlias{
   791  							Cell: "zone1",
   792  							Uid:  101,
   793  						},
   794  					},
   795  				},
   796  				"zone1-0000000102": {
   797  					Tablet: &topodatapb.Tablet{
   798  						Alias: &topodatapb.TabletAlias{
   799  							Cell: "zone1",
   800  							Uid:  102,
   801  						},
   802  						Hostname: "requires force start",
   803  					},
   804  				},
   805  				"zone1-0000000404": {
   806  					Tablet: &topodatapb.Tablet{
   807  						Alias: &topodatapb.TabletAlias{
   808  							Cell: "zone1",
   809  							Uid:  404,
   810  						},
   811  						Hostname: "ignored tablet",
   812  					},
   813  				},
   814  			},
   815  			tabletRes: []*topodatapb.Tablet{
   816  				{
   817  					Alias: &topodatapb.TabletAlias{
   818  						Cell: "zone1",
   819  						Uid:  100,
   820  					},
   821  					Hostname: "primary-elect",
   822  				}, {
   823  					Alias: &topodatapb.TabletAlias{
   824  						Cell: "zone1",
   825  						Uid:  101,
   826  					},
   827  				}, {
   828  					Alias: &topodatapb.TabletAlias{
   829  						Cell: "zone1",
   830  						Uid:  102,
   831  					},
   832  					Hostname: "requires force start",
   833  				},
   834  			},
   835  		},
   836  	}
   837  
   838  	for _, test := range tests {
   839  		t.Run(test.name, func(t *testing.T) {
   840  			tabletRes, posRes, err := getValidCandidatesAndPositionsAsList(test.validCandidates, test.tabletMap)
   841  			assert.NoError(t, err)
   842  			assert.ElementsMatch(t, test.tabletRes, tabletRes)
   843  			assert.Equal(t, len(tabletRes), len(posRes))
   844  			for i, tablet := range tabletRes {
   845  				assert.Equal(t, test.validCandidates[topoproto.TabletAliasString(tablet.Alias)], posRes[i])
   846  			}
   847  		})
   848  	}
   849  }
   850  
   851  func TestWaitForCatchUp(t *testing.T) {
   852  	tests := []struct {
   853  		name       string
   854  		tmc        tmclient.TabletManagerClient
   855  		source     *topodatapb.Tablet
   856  		newPrimary *topodatapb.Tablet
   857  		err        string
   858  	}{
   859  		{
   860  			name: "success",
   861  			tmc: &testutil.TabletManagerClient{
   862  				PrimaryPositionResults: map[string]struct {
   863  					Position string
   864  					Error    error
   865  				}{
   866  					"zone1-0000000100": {
   867  						Position: "abc",
   868  						Error:    nil,
   869  					},
   870  				},
   871  				WaitForPositionResults: map[string]map[string]error{
   872  					"zone1-0000000101": {
   873  						"abc": nil,
   874  					},
   875  				},
   876  			},
   877  			source: &topodatapb.Tablet{
   878  				Alias: &topodatapb.TabletAlias{
   879  					Cell: "zone1",
   880  					Uid:  100,
   881  				},
   882  			},
   883  			newPrimary: &topodatapb.Tablet{
   884  				Alias: &topodatapb.TabletAlias{
   885  					Cell: "zone1",
   886  					Uid:  101,
   887  				},
   888  			},
   889  		}, {
   890  			name: "error in primary position",
   891  			tmc: &testutil.TabletManagerClient{
   892  				PrimaryPositionResults: map[string]struct {
   893  					Position string
   894  					Error    error
   895  				}{
   896  					"zone1-0000000100": {
   897  						Position: "abc",
   898  						Error:    fmt.Errorf("found error in primary position"),
   899  					},
   900  				},
   901  				WaitForPositionResults: map[string]map[string]error{
   902  					"zone1-0000000101": {
   903  						"abc": nil,
   904  					},
   905  				},
   906  			},
   907  			source: &topodatapb.Tablet{
   908  				Alias: &topodatapb.TabletAlias{
   909  					Cell: "zone1",
   910  					Uid:  100,
   911  				},
   912  			},
   913  			newPrimary: &topodatapb.Tablet{
   914  				Alias: &topodatapb.TabletAlias{
   915  					Cell: "zone1",
   916  					Uid:  101,
   917  				},
   918  			},
   919  			err: "found error in primary position",
   920  		}, {
   921  			name: "error in waiting for position",
   922  			tmc: &testutil.TabletManagerClient{
   923  				PrimaryPositionResults: map[string]struct {
   924  					Position string
   925  					Error    error
   926  				}{
   927  					"zone1-0000000100": {
   928  						Position: "abc",
   929  						Error:    nil,
   930  					},
   931  				},
   932  				WaitForPositionResults: map[string]map[string]error{
   933  					"zone1-0000000101": {
   934  						"abc": fmt.Errorf("found error in waiting for position"),
   935  					},
   936  				},
   937  			},
   938  			source: &topodatapb.Tablet{
   939  				Alias: &topodatapb.TabletAlias{
   940  					Cell: "zone1",
   941  					Uid:  100,
   942  				},
   943  			},
   944  			newPrimary: &topodatapb.Tablet{
   945  				Alias: &topodatapb.TabletAlias{
   946  					Cell: "zone1",
   947  					Uid:  101,
   948  				},
   949  			},
   950  			err: "found error in waiting for position",
   951  		},
   952  	}
   953  
   954  	for _, test := range tests {
   955  		t.Run(test.name, func(t *testing.T) {
   956  			ctx := context.Background()
   957  			logger := logutil.NewMemoryLogger()
   958  			err := waitForCatchUp(ctx, test.tmc, logger, test.newPrimary, test.source, 2*time.Second)
   959  			if test.err != "" {
   960  				assert.EqualError(t, err, test.err)
   961  			} else {
   962  				assert.NoError(t, err)
   963  			}
   964  		})
   965  	}
   966  }
   967  
   968  func TestRestrictValidCandidates(t *testing.T) {
   969  	tests := []struct {
   970  		name            string
   971  		validCandidates map[string]mysql.Position
   972  		tabletMap       map[string]*topo.TabletInfo
   973  		result          map[string]mysql.Position
   974  	}{
   975  		{
   976  			name: "remove invalid tablets",
   977  			validCandidates: map[string]mysql.Position{
   978  				"zone1-0000000100": {},
   979  				"zone1-0000000101": {},
   980  				"zone1-0000000102": {},
   981  				"zone1-0000000103": {},
   982  				"zone1-0000000104": {},
   983  				"zone1-0000000105": {},
   984  			},
   985  			tabletMap: map[string]*topo.TabletInfo{
   986  				"zone1-0000000100": {
   987  					Tablet: &topodatapb.Tablet{
   988  						Alias: &topodatapb.TabletAlias{
   989  							Cell: "zone1",
   990  							Uid:  100,
   991  						},
   992  						Type: topodatapb.TabletType_PRIMARY,
   993  					},
   994  				},
   995  				"zone1-0000000101": {
   996  					Tablet: &topodatapb.Tablet{
   997  						Alias: &topodatapb.TabletAlias{
   998  							Cell: "zone1",
   999  							Uid:  101,
  1000  						},
  1001  						Type: topodatapb.TabletType_RDONLY,
  1002  					},
  1003  				},
  1004  				"zone1-0000000102": {
  1005  					Tablet: &topodatapb.Tablet{
  1006  						Alias: &topodatapb.TabletAlias{
  1007  							Cell: "zone1",
  1008  							Uid:  102,
  1009  						},
  1010  						Type: topodatapb.TabletType_RESTORE,
  1011  					},
  1012  				},
  1013  				"zone1-0000000103": {
  1014  					Tablet: &topodatapb.Tablet{
  1015  						Alias: &topodatapb.TabletAlias{
  1016  							Cell: "zone1",
  1017  							Uid:  103,
  1018  						},
  1019  						Type: topodatapb.TabletType_DRAINED,
  1020  					},
  1021  				},
  1022  				"zone1-0000000104": {
  1023  					Tablet: &topodatapb.Tablet{
  1024  						Alias: &topodatapb.TabletAlias{
  1025  							Cell: "zone1",
  1026  							Uid:  104,
  1027  						},
  1028  						Type: topodatapb.TabletType_SPARE,
  1029  					},
  1030  				},
  1031  				"zone1-0000000105": {
  1032  					Tablet: &topodatapb.Tablet{
  1033  						Alias: &topodatapb.TabletAlias{
  1034  							Cell: "zone1",
  1035  							Uid:  103,
  1036  						},
  1037  						Type: topodatapb.TabletType_BACKUP,
  1038  					},
  1039  				},
  1040  			},
  1041  			result: map[string]mysql.Position{
  1042  				"zone1-0000000100": {},
  1043  				"zone1-0000000101": {},
  1044  				"zone1-0000000104": {},
  1045  			},
  1046  		},
  1047  	}
  1048  
  1049  	for _, test := range tests {
  1050  		t.Run(test.name, func(t *testing.T) {
  1051  			res, err := restrictValidCandidates(test.validCandidates, test.tabletMap)
  1052  			assert.NoError(t, err)
  1053  			assert.Equal(t, res, test.result)
  1054  		})
  1055  	}
  1056  }
  1057  
  1058  func Test_findCandidate(t *testing.T) {
  1059  	tests := []struct {
  1060  		name               string
  1061  		intermediateSource *topodatapb.Tablet
  1062  		possibleCandidates []*topodatapb.Tablet
  1063  		candidate          *topodatapb.Tablet
  1064  	}{
  1065  		{
  1066  			name:      "empty possible candidates list",
  1067  			candidate: nil,
  1068  		}, {
  1069  			name: "intermediate source in possible candidates list",
  1070  			intermediateSource: &topodatapb.Tablet{
  1071  				Alias: &topodatapb.TabletAlias{
  1072  					Cell: "zone1",
  1073  					Uid:  103,
  1074  				},
  1075  			},
  1076  			possibleCandidates: []*topodatapb.Tablet{
  1077  				{
  1078  					Alias: &topodatapb.TabletAlias{
  1079  						Cell: "zone1",
  1080  						Uid:  101,
  1081  					},
  1082  				}, {
  1083  					Alias: &topodatapb.TabletAlias{
  1084  						Cell: "zone1",
  1085  						Uid:  103,
  1086  					},
  1087  				}, {
  1088  					Alias: &topodatapb.TabletAlias{
  1089  						Cell: "zone1",
  1090  						Uid:  102,
  1091  					},
  1092  				},
  1093  			},
  1094  			candidate: &topodatapb.Tablet{
  1095  				Alias: &topodatapb.TabletAlias{
  1096  					Cell: "zone1",
  1097  					Uid:  103,
  1098  				},
  1099  			},
  1100  		}, {
  1101  			name: "intermediate source not in possible candidates list",
  1102  			intermediateSource: &topodatapb.Tablet{
  1103  				Alias: &topodatapb.TabletAlias{
  1104  					Cell: "zone1",
  1105  					Uid:  103,
  1106  				},
  1107  			},
  1108  			possibleCandidates: []*topodatapb.Tablet{
  1109  				{
  1110  					Alias: &topodatapb.TabletAlias{
  1111  						Cell: "zone1",
  1112  						Uid:  101,
  1113  					},
  1114  				}, {
  1115  					Alias: &topodatapb.TabletAlias{
  1116  						Cell: "zone1",
  1117  						Uid:  104,
  1118  					},
  1119  				}, {
  1120  					Alias: &topodatapb.TabletAlias{
  1121  						Cell: "zone1",
  1122  						Uid:  102,
  1123  					},
  1124  				},
  1125  			},
  1126  			candidate: &topodatapb.Tablet{
  1127  				Alias: &topodatapb.TabletAlias{
  1128  					Cell: "zone1",
  1129  					Uid:  101,
  1130  				},
  1131  			},
  1132  		},
  1133  	}
  1134  	for _, tt := range tests {
  1135  		t.Run(tt.name, func(t *testing.T) {
  1136  			res := findCandidate(tt.intermediateSource, tt.possibleCandidates)
  1137  			if tt.candidate == nil {
  1138  				require.Nil(t, res)
  1139  			} else {
  1140  				require.NotNil(t, res)
  1141  				require.Equal(t, topoproto.TabletAliasString(tt.candidate.Alias), topoproto.TabletAliasString(res.Alias))
  1142  			}
  1143  		})
  1144  	}
  1145  }
  1146  
  1147  func Test_getTabletsWithPromotionRules(t *testing.T) {
  1148  	var (
  1149  		primaryTablet = &topodatapb.Tablet{
  1150  			Alias: &topodatapb.TabletAlias{
  1151  				Cell: "zone-1",
  1152  				Uid:  1,
  1153  			},
  1154  			Type: topodatapb.TabletType_PRIMARY,
  1155  		}
  1156  		replicaTablet = &topodatapb.Tablet{
  1157  			Alias: &topodatapb.TabletAlias{
  1158  				Cell: "zone-1",
  1159  				Uid:  2,
  1160  			},
  1161  			Type: topodatapb.TabletType_REPLICA,
  1162  		}
  1163  		rdonlyTablet = &topodatapb.Tablet{
  1164  			Alias: &topodatapb.TabletAlias{
  1165  				Cell: "zone-1",
  1166  				Uid:  3,
  1167  			},
  1168  			Type: topodatapb.TabletType_RDONLY,
  1169  		}
  1170  		replicaCrossCellTablet = &topodatapb.Tablet{
  1171  			Alias: &topodatapb.TabletAlias{
  1172  				Cell: "zone-2",
  1173  				Uid:  2,
  1174  			},
  1175  			Type: topodatapb.TabletType_REPLICA,
  1176  		}
  1177  		rdonlyCrossCellTablet = &topodatapb.Tablet{
  1178  			Alias: &topodatapb.TabletAlias{
  1179  				Cell: "zone-2",
  1180  				Uid:  3,
  1181  			},
  1182  			Type: topodatapb.TabletType_RDONLY,
  1183  		}
  1184  	)
  1185  	allTablets := []*topodatapb.Tablet{primaryTablet, replicaTablet, rdonlyTablet, replicaCrossCellTablet, rdonlyCrossCellTablet}
  1186  	tests := []struct {
  1187  		name            string
  1188  		tablets         []*topodatapb.Tablet
  1189  		rule            promotionrule.CandidatePromotionRule
  1190  		filteredTablets []*topodatapb.Tablet
  1191  	}{
  1192  		{
  1193  			name:            "filter candidates with Neutral promotion rule",
  1194  			tablets:         allTablets,
  1195  			rule:            promotionrule.Neutral,
  1196  			filteredTablets: []*topodatapb.Tablet{primaryTablet, replicaTablet, replicaCrossCellTablet},
  1197  		},
  1198  		{
  1199  			name:            "filter candidates with MustNot promotion rule",
  1200  			tablets:         allTablets,
  1201  			rule:            promotionrule.MustNot,
  1202  			filteredTablets: []*topodatapb.Tablet{rdonlyTablet, rdonlyCrossCellTablet},
  1203  		},
  1204  		{
  1205  			name:            "filter candidates with Must promotion rule",
  1206  			tablets:         allTablets,
  1207  			rule:            promotionrule.Must,
  1208  			filteredTablets: nil,
  1209  		},
  1210  	}
  1211  	durability, _ := GetDurabilityPolicy("none")
  1212  	for _, tt := range tests {
  1213  		t.Run(tt.name, func(t *testing.T) {
  1214  			res := getTabletsWithPromotionRules(durability, tt.tablets, tt.rule)
  1215  			require.EqualValues(t, tt.filteredTablets, res)
  1216  		})
  1217  	}
  1218  }