diff --git a/go/vt/vttablet/tabletmanager/rpc_external_reparent.go b/go/vt/vttablet/tabletmanager/rpc_external_reparent.go index 7b066ed1e1a..4cdebdb5ab0 100644 --- a/go/vt/vttablet/tabletmanager/rpc_external_reparent.go +++ b/go/vt/vttablet/tabletmanager/rpc_external_reparent.go @@ -21,6 +21,9 @@ import ( "sync" "time" + "vitess.io/vitess/go/vt/proto/vtrpc" + "vitess.io/vitess/go/vt/vterrors" + "github.com/golang/protobuf/proto" "golang.org/x/net/context" "vitess.io/vitess/go/event" @@ -40,8 +43,18 @@ var ( finalizeReparentTimeout = flag.Duration("finalize_external_reparent_timeout", 30*time.Second, "Timeout for the finalize stage of a fast external reparent reconciliation.") externalReparentStats = stats.NewTimings("ExternalReparents", "External reparenting time", "stage", "NewMasterVisible", "FullRebuild") + + neverReparent = flag.Bool("never_reparent", false, "if set to true, this tablet will not become MASTER") ) +// IsItSafeToReparent returns nil if we can reparent, and an error if we shouldn't +func IsItSafeToReparent() error { + if *neverReparent { + return vterrors.New(vtrpc.Code_INVALID_ARGUMENT, "never_reparent set to true, so will not reparent") + } + return nil +} + // SetReparentFlags changes flag values. It should only be used in tests. func SetReparentFlags(timeout time.Duration) { *finalizeReparentTimeout = timeout @@ -50,6 +63,10 @@ func SetReparentFlags(timeout time.Duration) { // TabletExternallyReparented updates all topo records so the current // tablet is the new master for this shard. func (agent *ActionAgent) TabletExternallyReparented(ctx context.Context, externalID string) error { + err := IsItSafeToReparent() + if err != nil { + return err + } if err := agent.lock(ctx); err != nil { return err } diff --git a/go/vt/vttablet/tabletmanager/rpc_external_reparent_test.go b/go/vt/vttablet/tabletmanager/rpc_external_reparent_test.go index 05cf42110b6..98d566d73ef 100644 --- a/go/vt/vttablet/tabletmanager/rpc_external_reparent_test.go +++ b/go/vt/vttablet/tabletmanager/rpc_external_reparent_test.go @@ -19,6 +19,8 @@ package tabletmanager import ( "context" "testing" + + "github.com/stretchr/testify/assert" ) func TestTabletExternallyReparentedAlwaysUpdatesTimestamp(t *testing.T) { @@ -43,3 +45,13 @@ func TestTabletExternallyReparentedAlwaysUpdatesTimestamp(t *testing.T) { t.Fatalf("subsequent TER call did not update the timestamp: %v = %v", ter1, ter2) } } + +func TestShouldNotReparent(t *testing.T) { + ctx := context.Background() + agent := createTestAgent(ctx, t, nil) + soTrue := true + neverReparent = &soTrue + + err := agent.TabletExternallyReparented(ctx, "unused_id") + assert.Error(t, err) +} diff --git a/go/vt/wrangler/reparent.go b/go/vt/wrangler/reparent.go index 06b99e21f32..a1c1d30a174 100644 --- a/go/vt/wrangler/reparent.go +++ b/go/vt/wrangler/reparent.go @@ -26,6 +26,8 @@ import ( "sync" "time" + "vitess.io/vitess/go/vt/vttablet/tabletmanager" + "vitess.io/vitess/go/event" "vitess.io/vitess/go/mysql" "vitess.io/vitess/go/sqlescape" @@ -329,6 +331,10 @@ func (wr *Wrangler) initShardMasterLocked(ctx context.Context, ev *events.Repare // PlannedReparentShard will make the provided tablet the master for the shard, // when both the current and new master are reachable and in good shape. func (wr *Wrangler) PlannedReparentShard(ctx context.Context, keyspace, shard string, masterElectTabletAlias, avoidMasterAlias *topodatapb.TabletAlias, waitSlaveTimeout time.Duration) (err error) { + err = tabletmanager.IsItSafeToReparent() + if err != nil { + return err + } // lock the shard lockAction := fmt.Sprintf( "PlannedReparentShard(%v, avoid_master=%v)", @@ -604,6 +610,11 @@ func (wr *Wrangler) chooseNewMaster( // EmergencyReparentShard will make the provided tablet the master for // the shard, when the old master is completely unreachable. func (wr *Wrangler) EmergencyReparentShard(ctx context.Context, keyspace, shard string, masterElectTabletAlias *topodatapb.TabletAlias, waitSlaveTimeout time.Duration) (err error) { + err = tabletmanager.IsItSafeToReparent() + if err != nil { + return err + } + // lock the shard ctx, unlock, lockErr := wr.ts.LockShard(ctx, keyspace, shard, fmt.Sprintf("EmergencyReparentShard(%v)", topoproto.TabletAliasString(masterElectTabletAlias))) if lockErr != nil {