vitess.io/vitess@v0.16.2/go/vt/wrangler/testlib/external_reparent_test.go (about)

     1  /*
     2  Copyright 2018 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 testlib
    18  
    19  import (
    20  	"context"
    21  	"flag"
    22  	"testing"
    23  	"time"
    24  
    25  	"vitess.io/vitess/go/vt/discovery"
    26  
    27  	"github.com/stretchr/testify/assert"
    28  
    29  	"vitess.io/vitess/go/vt/logutil"
    30  	"vitess.io/vitess/go/vt/topo/memorytopo"
    31  	"vitess.io/vitess/go/vt/topo/topoproto"
    32  	"vitess.io/vitess/go/vt/topotools"
    33  	"vitess.io/vitess/go/vt/vttablet/tmclient"
    34  	"vitess.io/vitess/go/vt/wrangler"
    35  
    36  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    37  )
    38  
    39  // The tests in this package test the wrangler version of TabletExternallyReparented
    40  // This is the one that is now called by the vtctl command
    41  
    42  // TestTabletExternallyReparentedBasic tests the base cases for TER
    43  func TestTabletExternallyReparentedBasic(t *testing.T) {
    44  	delay := discovery.GetTabletPickerRetryDelay()
    45  	defer func() {
    46  		discovery.SetTabletPickerRetryDelay(delay)
    47  	}()
    48  	discovery.SetTabletPickerRetryDelay(5 * time.Millisecond)
    49  
    50  	ctx := context.Background()
    51  	ts := memorytopo.NewServer("cell1")
    52  	wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient())
    53  	vp := NewVtctlPipe(t, ts)
    54  	defer vp.Close()
    55  
    56  	// Create an old primary, a new primary, two good replicas, one bad replica
    57  	oldPrimary := NewFakeTablet(t, wr, "cell1", 0, topodatapb.TabletType_PRIMARY, nil)
    58  	newPrimary := NewFakeTablet(t, wr, "cell1", 1, topodatapb.TabletType_REPLICA, nil)
    59  
    60  	// Build keyspace graph
    61  	err := topotools.RebuildKeyspace(ctx, logutil.NewConsoleLogger(), ts, oldPrimary.Tablet.Keyspace, []string{"cell1"}, false)
    62  	if err != nil {
    63  		t.Fatalf("RebuildKeyspaceLocked failed: %v", err)
    64  	}
    65  
    66  	// On the elected primary, we will respond to
    67  	// TabletActionReplicaWasPromoted
    68  	newPrimary.StartActionLoop(t, wr)
    69  	defer newPrimary.StopActionLoop(t)
    70  
    71  	// On the old primary, we will only respond to
    72  	// TabletActionReplicaWasRestarted.
    73  	oldPrimary.StartActionLoop(t, wr)
    74  	defer oldPrimary.StopActionLoop(t)
    75  
    76  	// First test: reparent to the same primary, make sure it works
    77  	// as expected.
    78  	if err := vp.Run([]string{"TabletExternallyReparented", topoproto.TabletAliasString(oldPrimary.Tablet.Alias)}); err != nil {
    79  		t.Fatalf("TabletExternallyReparented(same primary) should have worked: %v", err)
    80  	}
    81  
    82  	// check the old primary is still primary
    83  	tablet, err := ts.GetTablet(ctx, oldPrimary.Tablet.Alias)
    84  	if err != nil {
    85  		t.Fatalf("GetTablet(%v) failed: %v", oldPrimary.Tablet.Alias, err)
    86  	}
    87  	if tablet.Type != topodatapb.TabletType_PRIMARY {
    88  		t.Fatalf("old primary should be PRIMARY but is: %v", tablet.Type)
    89  	}
    90  
    91  	oldPrimary.FakeMysqlDaemon.SetReplicationSourceInputs = append(oldPrimary.FakeMysqlDaemon.SetReplicationSourceInputs, topoproto.MysqlAddr(newPrimary.Tablet))
    92  	oldPrimary.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{
    93  		"RESET SLAVE ALL",
    94  		"FAKE SET MASTER",
    95  		"START Replica",
    96  	}
    97  
    98  	// This tests the good case, where everything works as planned
    99  	t.Logf("TabletExternallyReparented(new primary) expecting success")
   100  	if err := wr.TabletExternallyReparented(ctx, newPrimary.Tablet.Alias); err != nil {
   101  		t.Fatalf("TabletExternallyReparented(replica) failed: %v", err)
   102  	}
   103  
   104  	// check the new primary is primary
   105  	tablet, err = ts.GetTablet(ctx, newPrimary.Tablet.Alias)
   106  	if err != nil {
   107  		t.Fatalf("GetTablet(%v) failed: %v", newPrimary.Tablet.Alias, err)
   108  	}
   109  	if tablet.Type != topodatapb.TabletType_PRIMARY {
   110  		t.Fatalf("new primary should be PRIMARY but is: %v", tablet.Type)
   111  	}
   112  
   113  	// We have to wait for shard sync to do its magic in the background
   114  	startTime := time.Now()
   115  	for {
   116  		if time.Since(startTime) > 10*time.Second /* timeout */ {
   117  			tablet, err = ts.GetTablet(ctx, oldPrimary.Tablet.Alias)
   118  			if err != nil {
   119  				t.Fatalf("GetTablet(%v) failed: %v", oldPrimary.Tablet.Alias, err)
   120  			}
   121  			t.Fatalf("old primary (%v) should be replica but is: %v", topoproto.TabletAliasString(oldPrimary.Tablet.Alias), tablet.Type)
   122  		}
   123  		// check the old primary was converted to replica
   124  		tablet, err = ts.GetTablet(ctx, oldPrimary.Tablet.Alias)
   125  		if err != nil {
   126  			t.Fatalf("GetTablet(%v) failed: %v", oldPrimary.Tablet.Alias, err)
   127  		}
   128  		if tablet.Type == topodatapb.TabletType_REPLICA {
   129  			break
   130  		} else {
   131  			time.Sleep(100 * time.Millisecond /* interval at which to check again */)
   132  		}
   133  	}
   134  }
   135  
   136  func TestTabletExternallyReparentedToReplica(t *testing.T) {
   137  	delay := discovery.GetTabletPickerRetryDelay()
   138  	defer func() {
   139  		discovery.SetTabletPickerRetryDelay(delay)
   140  	}()
   141  	discovery.SetTabletPickerRetryDelay(5 * time.Millisecond)
   142  
   143  	ctx := context.Background()
   144  	ts := memorytopo.NewServer("cell1")
   145  	wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient())
   146  
   147  	// Create an old primary, a new primary, two good replicas, one bad replica
   148  	oldPrimary := NewFakeTablet(t, wr, "cell1", 0, topodatapb.TabletType_PRIMARY, nil)
   149  	newPrimary := NewFakeTablet(t, wr, "cell1", 1, topodatapb.TabletType_REPLICA, nil)
   150  	newPrimary.FakeMysqlDaemon.ReadOnly = true
   151  	newPrimary.FakeMysqlDaemon.Replicating = true
   152  
   153  	// Build keyspace graph
   154  	err := topotools.RebuildKeyspace(ctx, logutil.NewConsoleLogger(), ts, oldPrimary.Tablet.Keyspace, []string{"cell1"}, false)
   155  	if err != nil {
   156  		t.Fatalf("RebuildKeyspaceLocked failed: %v", err)
   157  	}
   158  
   159  	// On the elected primary, we will respond to
   160  	// TabletActionReplicaWasPromoted
   161  	newPrimary.StartActionLoop(t, wr)
   162  	defer newPrimary.StopActionLoop(t)
   163  
   164  	// On the old primary, we will only respond to
   165  	// TabletActionReplicaWasRestarted.
   166  	oldPrimary.StartActionLoop(t, wr)
   167  	defer oldPrimary.StopActionLoop(t)
   168  
   169  	// Second test: reparent to a replica, and pretend the old
   170  	// primary is still good to go.
   171  	oldPrimary.FakeMysqlDaemon.SetReplicationSourceInputs = append(oldPrimary.FakeMysqlDaemon.SetReplicationSourceInputs, topoproto.MysqlAddr(newPrimary.Tablet))
   172  	oldPrimary.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{
   173  		"RESET SLAVE ALL",
   174  		"FAKE SET MASTER",
   175  		"START Replica",
   176  	}
   177  
   178  	// This tests a bad case: the new designated primary is a replica at mysql level,
   179  	// but we should do what we're told anyway.
   180  	if err := wr.TabletExternallyReparented(ctx, newPrimary.Tablet.Alias); err != nil {
   181  		t.Fatalf("TabletExternallyReparented(replica) error: %v", err)
   182  	}
   183  
   184  	// check that newPrimary is primary
   185  	tablet, err := ts.GetTablet(ctx, newPrimary.Tablet.Alias)
   186  	if err != nil {
   187  		t.Fatalf("GetTablet(%v) failed: %v", newPrimary.Tablet.Alias, err)
   188  	}
   189  	if tablet.Type != topodatapb.TabletType_PRIMARY {
   190  		t.Fatalf("new primary should be PRIMARY but is: %v", tablet.Type)
   191  	}
   192  
   193  	// We have to wait for shard sync to do its magic in the background
   194  	startTime := time.Now()
   195  	for {
   196  		if time.Since(startTime) > 10*time.Second /* timeout */ {
   197  			tablet, err = ts.GetTablet(ctx, oldPrimary.Tablet.Alias)
   198  			if err != nil {
   199  				t.Fatalf("GetTablet(%v) failed: %v", oldPrimary.Tablet.Alias, err)
   200  			}
   201  			t.Fatalf("old primary (%v) should be replica but is: %v", topoproto.TabletAliasString(oldPrimary.Tablet.Alias), tablet.Type)
   202  		}
   203  		// check the old primary was converted to replica
   204  		tablet, err = ts.GetTablet(ctx, oldPrimary.Tablet.Alias)
   205  		if err != nil {
   206  			t.Fatalf("GetTablet(%v) failed: %v", oldPrimary.Tablet.Alias, err)
   207  		}
   208  		if tablet.Type == topodatapb.TabletType_REPLICA {
   209  			break
   210  		} else {
   211  			time.Sleep(100 * time.Millisecond /* interval at which to check again */)
   212  		}
   213  	}
   214  }
   215  
   216  // TestTabletExternallyReparentedWithDifferentMysqlPort makes sure
   217  // that if mysql is restarted on the primary-elect tablet and has a different
   218  // port, we pick it up correctly.
   219  func TestTabletExternallyReparentedWithDifferentMysqlPort(t *testing.T) {
   220  	delay := discovery.GetTabletPickerRetryDelay()
   221  	defer func() {
   222  		discovery.SetTabletPickerRetryDelay(delay)
   223  	}()
   224  	discovery.SetTabletPickerRetryDelay(5 * time.Millisecond)
   225  
   226  	ctx := context.Background()
   227  	ts := memorytopo.NewServer("cell1")
   228  	wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient())
   229  
   230  	// Create an old primary, a new primary, two good replicas, one bad replica
   231  	oldPrimary := NewFakeTablet(t, wr, "cell1", 0, topodatapb.TabletType_PRIMARY, nil)
   232  	newPrimary := NewFakeTablet(t, wr, "cell1", 1, topodatapb.TabletType_REPLICA, nil)
   233  	goodReplica := NewFakeTablet(t, wr, "cell1", 2, topodatapb.TabletType_REPLICA, nil)
   234  
   235  	// Build keyspace graph
   236  	err := topotools.RebuildKeyspace(context.Background(), logutil.NewConsoleLogger(), ts, oldPrimary.Tablet.Keyspace, []string{"cell1"}, false)
   237  	if err != nil {
   238  		t.Fatalf("RebuildKeyspaceLocked failed: %v", err)
   239  	}
   240  	// Now we're restarting mysql on a different port, 3301->3303
   241  	// but without updating the Tablet record in topology.
   242  
   243  	// On the elected primary, we will respond to
   244  	// TabletActionReplicaWasPromoted, so we need a MysqlDaemon
   245  	// that returns no primary, and the new port (as returned by mysql)
   246  	newPrimary.FakeMysqlDaemon.MysqlPort.Set(3303)
   247  	newPrimary.StartActionLoop(t, wr)
   248  	defer newPrimary.StopActionLoop(t)
   249  
   250  	oldPrimary.FakeMysqlDaemon.SetReplicationSourceInputs = append(oldPrimary.FakeMysqlDaemon.SetReplicationSourceInputs, topoproto.MysqlAddr(newPrimary.Tablet))
   251  	oldPrimary.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{
   252  		"RESET SLAVE ALL",
   253  		"FAKE SET MASTER",
   254  		"START Replica",
   255  	}
   256  	// On the old primary, we will only respond to
   257  	// TabletActionReplicaWasRestarted and point to the new mysql port
   258  	oldPrimary.StartActionLoop(t, wr)
   259  	defer oldPrimary.StopActionLoop(t)
   260  
   261  	// On the good replicas, we will respond to
   262  	// TabletActionReplicaWasRestarted and point to the new mysql port
   263  	goodReplica.FakeMysqlDaemon.SetReplicationSourceInputs = append(goodReplica.FakeMysqlDaemon.SetReplicationSourceInputs, topoproto.MysqlAddr(oldPrimary.Tablet))
   264  	goodReplica.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{
   265  		// These 4 statements come from tablet startup
   266  		"STOP SLAVE",
   267  		"RESET SLAVE ALL",
   268  		"FAKE SET MASTER",
   269  		"START SLAVE",
   270  	}
   271  	goodReplica.StartActionLoop(t, wr)
   272  	defer goodReplica.StopActionLoop(t)
   273  
   274  	// This tests the good case, where everything works as planned
   275  	t.Logf("TabletExternallyReparented(new primary) expecting success")
   276  	if err := wr.TabletExternallyReparented(ctx, newPrimary.Tablet.Alias); err != nil {
   277  		t.Fatalf("TabletExternallyReparented(replica) failed: %v", err)
   278  	}
   279  	// check the new primary is primary
   280  	tablet, err := ts.GetTablet(ctx, newPrimary.Tablet.Alias)
   281  	if err != nil {
   282  		t.Fatalf("GetTablet(%v) failed: %v", newPrimary.Tablet.Alias, err)
   283  	}
   284  	if tablet.Type != topodatapb.TabletType_PRIMARY {
   285  		t.Fatalf("new primary should be PRIMARY but is: %v", tablet.Type)
   286  	}
   287  
   288  	// We have to wait for shard sync to do its magic in the background
   289  	startTime := time.Now()
   290  	for {
   291  		if time.Since(startTime) > 10*time.Second /* timeout */ {
   292  			tablet, err = ts.GetTablet(ctx, oldPrimary.Tablet.Alias)
   293  			if err != nil {
   294  				t.Fatalf("GetTablet(%v) failed: %v", oldPrimary.Tablet.Alias, err)
   295  			}
   296  			t.Fatalf("old primary (%v) should be replica but is: %v", topoproto.TabletAliasString(oldPrimary.Tablet.Alias), tablet.Type)
   297  		}
   298  		// check the old primary was converted to replica
   299  		tablet, err = ts.GetTablet(ctx, oldPrimary.Tablet.Alias)
   300  		if err != nil {
   301  			t.Fatalf("GetTablet(%v) failed: %v", oldPrimary.Tablet.Alias, err)
   302  		}
   303  		if tablet.Type == topodatapb.TabletType_REPLICA {
   304  			break
   305  		} else {
   306  			time.Sleep(100 * time.Millisecond /* interval at which to check again */)
   307  		}
   308  	}
   309  }
   310  
   311  // TestTabletExternallyReparentedContinueOnUnexpectedPrimary makes sure
   312  // that we ignore mysql's primary if the flag is set
   313  func TestTabletExternallyReparentedContinueOnUnexpectedPrimary(t *testing.T) {
   314  	delay := discovery.GetTabletPickerRetryDelay()
   315  	defer func() {
   316  		discovery.SetTabletPickerRetryDelay(delay)
   317  	}()
   318  	discovery.SetTabletPickerRetryDelay(5 * time.Millisecond)
   319  
   320  	ctx := context.Background()
   321  	ts := memorytopo.NewServer("cell1")
   322  	wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient())
   323  
   324  	// Create an old primary, a new primary, two good replicas, one bad replica
   325  	oldPrimary := NewFakeTablet(t, wr, "cell1", 0, topodatapb.TabletType_PRIMARY, nil)
   326  	newPrimary := NewFakeTablet(t, wr, "cell1", 1, topodatapb.TabletType_REPLICA, nil)
   327  	goodReplica := NewFakeTablet(t, wr, "cell1", 2, topodatapb.TabletType_REPLICA, nil)
   328  
   329  	// Build keyspace graph
   330  	err := topotools.RebuildKeyspace(context.Background(), logutil.NewConsoleLogger(), ts, oldPrimary.Tablet.Keyspace, []string{"cell1"}, false)
   331  	if err != nil {
   332  		t.Fatalf("RebuildKeyspaceLocked failed: %v", err)
   333  	}
   334  	// On the elected primary, we will respond to
   335  	// TabletActionReplicaWasPromoted, so we need a MysqlDaemon
   336  	// that returns no primary, and the new port (as returned by mysql)
   337  	newPrimary.StartActionLoop(t, wr)
   338  	defer newPrimary.StopActionLoop(t)
   339  
   340  	oldPrimary.FakeMysqlDaemon.SetReplicationSourceInputs = append(oldPrimary.FakeMysqlDaemon.SetReplicationSourceInputs, topoproto.MysqlAddr(newPrimary.Tablet))
   341  	oldPrimary.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{
   342  		"RESET SLAVE ALL",
   343  		"FAKE SET MASTER",
   344  		"START Replica",
   345  	}
   346  	// On the old primary, we will only respond to
   347  	// TabletActionReplicaWasRestarted and point to a bad host
   348  	oldPrimary.StartActionLoop(t, wr)
   349  	defer oldPrimary.StopActionLoop(t)
   350  
   351  	// On the good replica, we will respond to
   352  	// TabletActionReplicaWasRestarted and point to a bad host
   353  	goodReplica.FakeMysqlDaemon.SetReplicationSourceInputs = append(goodReplica.FakeMysqlDaemon.SetReplicationSourceInputs, topoproto.MysqlAddr(oldPrimary.Tablet))
   354  	goodReplica.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{
   355  		// These 4 statements come from tablet startup
   356  		"STOP SLAVE",
   357  		"RESET SLAVE ALL",
   358  		"FAKE SET MASTER",
   359  		"START SLAVE",
   360  	}
   361  	goodReplica.StartActionLoop(t, wr)
   362  	defer goodReplica.StopActionLoop(t)
   363  
   364  	// This tests the good case, where everything works as planned
   365  	t.Logf("TabletExternallyReparented(new primary) expecting success")
   366  	if err := wr.TabletExternallyReparented(ctx, newPrimary.Tablet.Alias); err != nil {
   367  		t.Fatalf("TabletExternallyReparented(replica) failed: %v", err)
   368  	}
   369  	// check the new primary is primary
   370  	tablet, err := ts.GetTablet(ctx, newPrimary.Tablet.Alias)
   371  	if err != nil {
   372  		t.Fatalf("GetTablet(%v) failed: %v", newPrimary.Tablet.Alias, err)
   373  	}
   374  	if tablet.Type != topodatapb.TabletType_PRIMARY {
   375  		t.Fatalf("new primary should be PRIMARY but is: %v", tablet.Type)
   376  	}
   377  	// We have to wait for shard sync to do its magic in the background
   378  	startTime := time.Now()
   379  	for {
   380  		if time.Since(startTime) > 10*time.Second /* timeout */ {
   381  			tablet, err = ts.GetTablet(ctx, oldPrimary.Tablet.Alias)
   382  			if err != nil {
   383  				t.Fatalf("GetTablet(%v) failed: %v", oldPrimary.Tablet.Alias, err)
   384  			}
   385  			t.Fatalf("old primary (%v) should be replica but is: %v", topoproto.TabletAliasString(oldPrimary.Tablet.Alias), tablet.Type)
   386  		}
   387  		// check the old primary was converted to replica
   388  		tablet, err = ts.GetTablet(ctx, oldPrimary.Tablet.Alias)
   389  		if err != nil {
   390  			t.Fatalf("GetTablet(%v) failed: %v", oldPrimary.Tablet.Alias, err)
   391  		}
   392  		if tablet.Type == topodatapb.TabletType_REPLICA {
   393  			break
   394  		} else {
   395  			time.Sleep(100 * time.Millisecond /* interval at which to check again */)
   396  		}
   397  	}
   398  }
   399  
   400  func TestTabletExternallyReparentedRerun(t *testing.T) {
   401  	delay := discovery.GetTabletPickerRetryDelay()
   402  	defer func() {
   403  		discovery.SetTabletPickerRetryDelay(delay)
   404  	}()
   405  	discovery.SetTabletPickerRetryDelay(5 * time.Millisecond)
   406  
   407  	ctx := context.Background()
   408  	ts := memorytopo.NewServer("cell1")
   409  	wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient())
   410  
   411  	// Create an old primary, a new primary, and a good replica.
   412  	oldPrimary := NewFakeTablet(t, wr, "cell1", 0, topodatapb.TabletType_PRIMARY, nil)
   413  	newPrimary := NewFakeTablet(t, wr, "cell1", 1, topodatapb.TabletType_REPLICA, nil)
   414  	goodReplica := NewFakeTablet(t, wr, "cell1", 2, topodatapb.TabletType_REPLICA, nil)
   415  
   416  	// Build keyspace graph
   417  	err := topotools.RebuildKeyspace(context.Background(), logutil.NewConsoleLogger(), ts, oldPrimary.Tablet.Keyspace, []string{"cell1"}, false)
   418  	if err != nil {
   419  		t.Fatalf("RebuildKeyspaceLocked failed: %v", err)
   420  	}
   421  	// On the elected primary, we will respond to
   422  	// TabletActionReplicaWasPromoted.
   423  	newPrimary.StartActionLoop(t, wr)
   424  	defer newPrimary.StopActionLoop(t)
   425  
   426  	oldPrimary.FakeMysqlDaemon.SetReplicationSourceInputs = append(oldPrimary.FakeMysqlDaemon.SetReplicationSourceInputs, topoproto.MysqlAddr(newPrimary.Tablet))
   427  	oldPrimary.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{
   428  		"RESET SLAVE ALL",
   429  		"FAKE SET MASTER",
   430  		"START Replica",
   431  	}
   432  	// On the old primary, we will only respond to
   433  	// TabletActionReplicaWasRestarted.
   434  	oldPrimary.StartActionLoop(t, wr)
   435  	defer oldPrimary.StopActionLoop(t)
   436  
   437  	goodReplica.FakeMysqlDaemon.SetReplicationSourceInputs = append(goodReplica.FakeMysqlDaemon.SetReplicationSourceInputs, topoproto.MysqlAddr(newPrimary.Tablet), topoproto.MysqlAddr(oldPrimary.Tablet))
   438  	// On the good replica, we will respond to
   439  	// TabletActionReplicaWasRestarted.
   440  	goodReplica.FakeMysqlDaemon.ExpectedExecuteSuperQueryList = []string{
   441  		// These 4 statements come from tablet startup
   442  		"STOP SLAVE",
   443  		"RESET SLAVE ALL",
   444  		"FAKE SET MASTER",
   445  		"START SLAVE",
   446  	}
   447  	goodReplica.StartActionLoop(t, wr)
   448  	defer goodReplica.StopActionLoop(t)
   449  
   450  	// The reparent should work as expected here
   451  	if err := wr.TabletExternallyReparented(ctx, newPrimary.Tablet.Alias); err != nil {
   452  		t.Fatalf("TabletExternallyReparented(replica) failed: %v", err)
   453  	}
   454  
   455  	// check the new primary is primary
   456  	tablet, err := ts.GetTablet(ctx, newPrimary.Tablet.Alias)
   457  	if err != nil {
   458  		t.Fatalf("GetTablet(%v) failed: %v", newPrimary.Tablet.Alias, err)
   459  	}
   460  	if tablet.Type != topodatapb.TabletType_PRIMARY {
   461  		t.Fatalf("new primary should be PRIMARY but is: %v", tablet.Type)
   462  	}
   463  
   464  	// We have to wait for shard sync to do its magic in the background
   465  	startTime := time.Now()
   466  	for {
   467  		if time.Since(startTime) > 10*time.Second /* timeout */ {
   468  			tablet, err = ts.GetTablet(ctx, oldPrimary.Tablet.Alias)
   469  			if err != nil {
   470  				t.Fatalf("GetTablet(%v) failed: %v", oldPrimary.Tablet.Alias, err)
   471  			}
   472  			t.Fatalf("old primary (%v) should be replica but is: %v", topoproto.TabletAliasString(oldPrimary.Tablet.Alias), tablet.Type)
   473  		}
   474  		// check the old primary was converted to replica
   475  		tablet, err = ts.GetTablet(ctx, oldPrimary.Tablet.Alias)
   476  		if err != nil {
   477  			t.Fatalf("GetTablet(%v) failed: %v", oldPrimary.Tablet.Alias, err)
   478  		}
   479  		if tablet.Type == topodatapb.TabletType_REPLICA {
   480  			break
   481  		} else {
   482  			time.Sleep(100 * time.Millisecond /* interval at which to check again */)
   483  		}
   484  	}
   485  
   486  	// run TER again and make sure the primary is still correct
   487  	if err := wr.TabletExternallyReparented(ctx, newPrimary.Tablet.Alias); err != nil {
   488  		t.Fatalf("TabletExternallyReparented(replica) failed: %v", err)
   489  	}
   490  
   491  	// check the new primary is still primary
   492  	tablet, err = ts.GetTablet(ctx, newPrimary.Tablet.Alias)
   493  	if err != nil {
   494  		t.Fatalf("GetTablet(%v) failed: %v", newPrimary.Tablet.Alias, err)
   495  	}
   496  	if tablet.Type != topodatapb.TabletType_PRIMARY {
   497  		t.Fatalf("new primary should be PRIMARY but is: %v", tablet.Type)
   498  	}
   499  
   500  }
   501  
   502  func TestRPCTabletExternallyReparentedDemotesPrimaryToConfiguredTabletType(t *testing.T) {
   503  	delay := discovery.GetTabletPickerRetryDelay()
   504  	defer func() {
   505  		discovery.SetTabletPickerRetryDelay(delay)
   506  	}()
   507  	discovery.SetTabletPickerRetryDelay(5 * time.Millisecond)
   508  
   509  	flag.Set("disable_active_reparents", "true")
   510  	defer flag.Set("disable_active_reparents", "false")
   511  
   512  	ctx := context.Background()
   513  	ts := memorytopo.NewServer("cell1")
   514  	wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient())
   515  
   516  	// Create an old primary and a new primary
   517  	oldPrimary := NewFakeTablet(t, wr, "cell1", 0, topodatapb.TabletType_SPARE, nil)
   518  	newPrimary := NewFakeTablet(t, wr, "cell1", 1, topodatapb.TabletType_SPARE, nil)
   519  
   520  	oldPrimary.StartActionLoop(t, wr)
   521  	newPrimary.StartActionLoop(t, wr)
   522  	defer oldPrimary.StopActionLoop(t)
   523  	defer newPrimary.StopActionLoop(t)
   524  
   525  	// Build keyspace graph
   526  	err := topotools.RebuildKeyspace(context.Background(), logutil.NewConsoleLogger(), ts, oldPrimary.Tablet.Keyspace, []string{"cell1"}, false)
   527  	assert.NoError(t, err, "RebuildKeyspaceLocked failed: %v", err)
   528  
   529  	// Reparent to new primary
   530  	ti, err := ts.GetTablet(ctx, newPrimary.Tablet.Alias)
   531  	if err != nil {
   532  		t.Fatalf("GetTablet failed: %v", err)
   533  	}
   534  
   535  	if err := wr.TabletExternallyReparented(context.Background(), ti.Tablet.Alias); err != nil {
   536  		t.Fatalf("TabletExternallyReparented failed: %v", err)
   537  	}
   538  
   539  	// We have to wait for shard sync to do its magic in the background
   540  	startTime := time.Now()
   541  	for {
   542  		if time.Since(startTime) > 10*time.Second /* timeout */ {
   543  			tablet, err := ts.GetTablet(ctx, oldPrimary.Tablet.Alias)
   544  			if err != nil {
   545  				t.Fatalf("GetTablet(%v) failed: %v", oldPrimary.Tablet.Alias, err)
   546  			}
   547  			t.Fatalf("old primary (%v) should be spare but is: %v", topoproto.TabletAliasString(oldPrimary.Tablet.Alias), tablet.Type)
   548  		}
   549  		// check the old primary was converted to replica
   550  		tablet, err := ts.GetTablet(ctx, oldPrimary.Tablet.Alias)
   551  		if err != nil {
   552  			t.Fatalf("GetTablet(%v) failed: %v", oldPrimary.Tablet.Alias, err)
   553  		}
   554  		if tablet.Type == topodatapb.TabletType_SPARE {
   555  			break
   556  		} else {
   557  			time.Sleep(100 * time.Millisecond /* interval at which to check again */)
   558  		}
   559  	}
   560  
   561  	shardInfo, err := ts.GetShard(context.Background(), newPrimary.Tablet.Keyspace, newPrimary.Tablet.Shard)
   562  	assert.NoError(t, err)
   563  
   564  	assert.True(t, topoproto.TabletAliasEqual(newPrimary.Tablet.Alias, shardInfo.PrimaryAlias))
   565  	assert.Equal(t, topodatapb.TabletType_PRIMARY, newPrimary.TM.Tablet().Type)
   566  }