diff --git a/go/vt/vtctl/reparent.go b/go/vt/vtctl/reparent.go index d66ec73426b..4ef33116d59 100644 --- a/go/vt/vtctl/reparent.go +++ b/go/vt/vtctl/reparent.go @@ -52,6 +52,12 @@ func init() { commandEmergencyReparentShard, "-keyspace_shard= -new_master=", "Reparents the shard to the new master. Assumes the old master is dead and not responsding."}) + addCommand("Shards", command{ + "TabletExternallyReparented", + commandTabletExternallyReparented, + "", + "Changes metadata in the topology server to acknowledge a shard master change performed by an external tool. See the Reparenting guide for more information:" + + "https://vitess.io/docs/user-guides/reparenting/#external-reparenting"}) } func commandReparentTablet(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error { @@ -171,3 +177,18 @@ func commandEmergencyReparentShard(ctx context.Context, wr *wrangler.Wrangler, s } return wr.EmergencyReparentShard(ctx, keyspace, shard, tabletAlias, *waitSlaveTimeout) } + +func commandTabletExternallyReparented(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error { + if err := subFlags.Parse(args); err != nil { + return err + } + if subFlags.NArg() != 1 { + return fmt.Errorf("action TabletExternallyReparented requires ") + } + + tabletAlias, err := topoproto.ParseTabletAlias(subFlags.Arg(0)) + if err != nil { + return err + } + return wr.TabletExternallyReparented(ctx, tabletAlias) +} diff --git a/go/vt/vtctl/vtctl.go b/go/vt/vtctl/vtctl.go index 94cb8744a61..5779825ad4f 100644 --- a/go/vt/vtctl/vtctl.go +++ b/go/vt/vtctl/vtctl.go @@ -228,10 +228,6 @@ var commands = []commandGroup{ {"GetShard", commandGetShard, "", "Outputs a JSON structure that contains information about the Shard."}, - {"TabletExternallyReparented", commandTabletExternallyReparented, - "", - "Changes metadata in the topology server to acknowledge a shard master change performed by an external tool. See the Reparenting guide for more information:" + - "https://github.com/vitessio/vitess/blob/master/doc/Reparenting.md#external-reparents."}, {"ValidateShard", commandValidateShard, "[-ping-tablets] ", "Validates that all nodes that are reachable from this shard are consistent."}, @@ -1203,25 +1199,6 @@ func commandGetShard(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag. return printJSON(wr.Logger(), shardInfo.Shard) } -func commandTabletExternallyReparented(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error { - if err := subFlags.Parse(args); err != nil { - return err - } - if subFlags.NArg() != 1 { - return fmt.Errorf("the argument is required for the TabletExternallyReparented command") - } - - tabletAlias, err := topoproto.ParseTabletAlias(subFlags.Arg(0)) - if err != nil { - return err - } - ti, err := wr.TopoServer().GetTablet(ctx, tabletAlias) - if err != nil { - return err - } - return wr.TabletManagerClient().TabletExternallyReparented(ctx, ti.Tablet, "") -} - func commandValidateShard(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error { pingTablets := subFlags.Bool("ping-tablets", true, "Indicates whether all tablets should be pinged during the validation process") if err := subFlags.Parse(args); err != nil { diff --git a/go/vt/wrangler/reparent.go b/go/vt/wrangler/reparent.go index 06b99e21f32..8c8ae9db7de 100644 --- a/go/vt/wrangler/reparent.go +++ b/go/vt/wrangler/reparent.go @@ -41,9 +41,10 @@ import ( ) const ( - initShardMasterOperation = "InitShardMaster" - plannedReparentShardOperation = "PlannedReparentShard" - emergencyReparentShardOperation = "EmergencyReparentShard" + initShardMasterOperation = "InitShardMaster" + plannedReparentShardOperation = "PlannedReparentShard" + emergencyReparentShardOperation = "EmergencyReparentShard" + tabletExternallyReparentedOperation = "TabletExternallyReparented" ) // ShardReplicationStatuses returns the ReplicationStatus for each tablet in a shard. @@ -812,3 +813,51 @@ func (wr *Wrangler) emergencyReparentShardLocked(ctx context.Context, ev *events return nil } + +// TabletExternallyReparented changes the type of new master for this shard to MASTER +// and updates it's tablet record in the topo. Updating the shard record is handled +// by the new master tablet +func (wr *Wrangler) TabletExternallyReparented(ctx context.Context, newMasterAlias *topodatapb.TabletAlias) error { + + tabletInfo, err := wr.ts.GetTablet(ctx, newMasterAlias) + if err != nil { + log.Warningf("TabletExternallyReparented: failed to read tablet record for %v: %v", newMasterAlias, err) + return err + } + + // Check the global shard record. + tablet := tabletInfo.Tablet + si, err := wr.ts.GetShard(ctx, tablet.Keyspace, tablet.Shard) + if err != nil { + log.Warningf("TabletExternallyReparented: failed to read global shard record for %v/%v: %v", tablet.Keyspace, tablet.Shard, err) + return err + } + + // We update the tablet only if it is not currently master + if tablet.Type != topodatapb.TabletType_MASTER { + log.Infof("TabletExternallyReparented: executing tablet type change to MASTER") + + // Create a reusable Reparent event with available info. + ev := &events.Reparent{ + ShardInfo: *si, + NewMaster: *tablet, + OldMaster: topodatapb.Tablet{ + Alias: si.MasterAlias, + Type: topodatapb.TabletType_MASTER, + }, + } + defer func() { + if err != nil { + event.DispatchUpdate(ev, "failed: "+err.Error()) + } + }() + event.DispatchUpdate(ev, "starting external reparent") + + if err := wr.tmc.ChangeType(ctx, tablet, topodatapb.TabletType_MASTER); err != nil { + log.Warningf("Error calling ChangeType on new master %v: %v", topoproto.TabletAliasString(newMasterAlias), err) + return err + } + event.DispatchUpdate(ev, "finished") + } + return nil +} diff --git a/go/vt/wrangler/testlib/reparent_external_test.go b/go/vt/wrangler/testlib/rpc_reparent_external_test.go similarity index 99% rename from go/vt/wrangler/testlib/reparent_external_test.go rename to go/vt/wrangler/testlib/rpc_reparent_external_test.go index dfd86e55564..1d871a8dec3 100644 --- a/go/vt/wrangler/testlib/reparent_external_test.go +++ b/go/vt/wrangler/testlib/rpc_reparent_external_test.go @@ -44,8 +44,6 @@ func TestTabletExternallyReparented(t *testing.T) { ctx := context.Background() ts := memorytopo.NewServer("cell1", "cell2") wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient()) - vp := NewVtctlPipe(t, ts) - defer vp.Close() // Create an old master, a new master, two good slaves, one bad slave oldMaster := NewFakeTablet(t, wr, "cell1", 0, topodatapb.TabletType_MASTER, nil) @@ -127,7 +125,8 @@ func TestTabletExternallyReparented(t *testing.T) { if err != nil { t.Fatalf("GetTablet failed: %v", err) } - if err := vp.Run([]string{"TabletExternallyReparented", topoproto.TabletAliasString(oldMaster.Tablet.Alias)}); err != nil { + waitID := makeWaitID() + if err := tmc.TabletExternallyReparented(context.Background(), oldMaster.Tablet, waitID); err != nil { t.Fatalf("TabletExternallyReparented(same master) should have worked: %v", err) } @@ -140,7 +139,7 @@ func TestTabletExternallyReparented(t *testing.T) { if err != nil { t.Fatalf("GetTablet failed: %v", err) } - waitID := makeWaitID() + waitID = makeWaitID() if err := tmc.TabletExternallyReparented(context.Background(), ti.Tablet, waitID); err != nil { t.Fatalf("TabletExternallyReparented(slave) error: %v", err) }