Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DeleteTablet: allow deletion of old master tablet without -allow_master #5647

Merged
merged 4 commits into from
Jan 7, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 35 additions & 8 deletions go/vt/wrangler/tablet.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,18 @@ func (wr *Wrangler) DeleteTablet(ctx context.Context, tabletAlias *topodatapb.Ta
if err != nil {
return err
}
wasMaster := ti.Type == topodatapb.TabletType_MASTER
if wasMaster && !allowMaster {
return fmt.Errorf("cannot delete tablet %v as it is a master, use allow_master flag", topoproto.TabletAliasString(tabletAlias))
}

// remove the record and its replication graph entry
if err := topotools.DeleteTablet(ctx, wr.ts, ti.Tablet); err != nil {
wasMaster, err := wr.isMasterTablet(ctx, ti)
if err != nil {
return err
}

if wasMaster && !allowMaster {
return fmt.Errorf("cannot delete tablet %v as it is a master, use allow_master flag", topoproto.TabletAliasString(tabletAlias))
}

// update the Shard object if the master was scrapped.
// we do this before calling DeleteTablet so that the operation can be retried in case of failure.
if wasMaster {
// We lock the shard to not conflict with reparent operations.
ctx, unlock, lockErr := wr.ts.LockShard(ctx, ti.Keyspace, ti.Shard, fmt.Sprintf("DeleteTablet(%v)", topoproto.TabletAliasString(tabletAlias)))
Expand All @@ -129,14 +130,20 @@ func (wr *Wrangler) DeleteTablet(ctx context.Context, tabletAlias *topodatapb.Ta
defer unlock(&err)

// update the shard record's master
_, err = wr.ts.UpdateShardFields(ctx, ti.Keyspace, ti.Shard, func(si *topo.ShardInfo) error {
if _, err := wr.ts.UpdateShardFields(ctx, ti.Keyspace, ti.Shard, func(si *topo.ShardInfo) error {
if !topoproto.TabletAliasEqual(si.MasterAlias, tabletAlias) {
wr.Logger().Warningf("Deleting master %v from shard %v/%v but master in Shard object was %v", topoproto.TabletAliasString(tabletAlias), ti.Keyspace, ti.Shard, topoproto.TabletAliasString(si.MasterAlias))
return topo.NewError(topo.NoUpdateNeeded, si.Keyspace()+"/"+si.ShardName())
}
si.MasterAlias = nil
return nil
})
}); err != nil {
return err
}
}

// remove the record and its replication graph entry
if err := topotools.DeleteTablet(ctx, wr.ts, ti.Tablet); err != nil {
return err
}

Expand Down Expand Up @@ -201,3 +208,23 @@ func (wr *Wrangler) VReplicationExec(ctx context.Context, tabletAlias *topodatap
}
return wr.tmc.VReplicationExec(ctx, ti.Tablet, query)
}

func (wr *Wrangler) isMasterTablet(ctx context.Context, ti *topo.TabletInfo) (bool, error) {
deepthi marked this conversation as resolved.
Show resolved Hide resolved
// Tablet record claims to be non-master, we believe it
if ti.Type != topodatapb.TabletType_MASTER {
return false, nil
}
si, err := wr.ts.GetShard(ctx, ti.Keyspace, ti.Shard)
if err != nil {
// strictly speaking it isn't correct to return false here, the tablet status is unknown
return false, err
}
// Tablet record claims to be master, and shard record matches
if topoproto.TabletAliasEqual(si.MasterAlias, ti.Tablet.Alias) {
return true, nil
}
// Shard record has another tablet as master, so check MasterTermStartTime
// If tablet record's MasterTermStartTime is equal to or later than the one in the shard record, then tablet is master
// !Before == Equal || After
return !logutil.ProtoToTime(ti.MasterTermStartTime).Before(logutil.ProtoToTime(si.MasterTermStartTime)), nil
deepthi marked this conversation as resolved.
Show resolved Hide resolved
}
122 changes: 122 additions & 0 deletions go/vt/wrangler/tablet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ limitations under the License.
package wrangler

import (
"strings"
"testing"

"golang.org/x/net/context"
"vitess.io/vitess/go/vt/logutil"
topodatapb "vitess.io/vitess/go/vt/proto/topodata"
"vitess.io/vitess/go/vt/topo"
"vitess.io/vitess/go/vt/topo/memorytopo"
)

Expand Down Expand Up @@ -56,3 +58,123 @@ func TestInitTabletShardConversion(t *testing.T) {
t.Errorf("Got wrong tablet.KeyRange, got %v expected 80-c0", ti.KeyRange)
}
}

// TestDeleteTabletBasic tests delete of non-master tablet
func TestDeleteTabletBasic(t *testing.T) {
cell := "cell1"
ts := memorytopo.NewServer(cell)
wr := New(logutil.NewConsoleLogger(), ts, nil)

tablet := &topodatapb.Tablet{
Alias: &topodatapb.TabletAlias{
Cell: cell,
Uid: 1,
},
Shard: "0",
}

if err := wr.InitTablet(context.Background(), tablet, false /*allowMasterOverride*/, true /*createShardAndKeyspace*/, false /*allowUpdate*/); err != nil {
t.Fatalf("InitTablet failed: %v", err)
}

if _, err := ts.GetTablet(context.Background(), tablet.Alias); err != nil {
t.Fatalf("GetTablet failed: %v", err)
}

if err := wr.DeleteTablet(context.Background(), tablet.Alias, false); err != nil {
t.Fatalf("DeleteTablet failed: %v", err)
}
}

// TestDeleteTabletTrueMaster tests that you can delete a true master tablet
// only if allowMaster is set to true
func TestDeleteTabletTrueMaster(t *testing.T) {
cell := "cell1"
ts := memorytopo.NewServer(cell)
wr := New(logutil.NewConsoleLogger(), ts, nil)

tablet := &topodatapb.Tablet{
Alias: &topodatapb.TabletAlias{
Cell: cell,
Uid: 1,
},
Keyspace: "test",
Shard: "0",
Type: topodatapb.TabletType_MASTER,
}

if err := wr.InitTablet(context.Background(), tablet, false /*allowMasterOverride*/, true /*createShardAndKeyspace*/, false /*allowUpdate*/); err != nil {
t.Fatalf("InitTablet failed: %v", err)
}
if _, err := ts.GetTablet(context.Background(), tablet.Alias); err != nil {
t.Fatalf("GetTablet failed: %v", err)
}

// set MasterAlias and MasterTermStartTime on shard to match chosen master tablet
if _, err := ts.UpdateShardFields(context.Background(), "test", "0", func(si *topo.ShardInfo) error {
si.MasterAlias = tablet.Alias
si.MasterTermStartTime = tablet.MasterTermStartTime
return nil
}); err != nil {
t.Fatalf("UpdateShardFields failed: %v", err)
}

err := wr.DeleteTablet(context.Background(), tablet.Alias, false)
wantError := "as it is a master, use allow_master flag"
if err == nil || !strings.Contains(err.Error(), wantError) {
t.Fatalf("DeleteTablet on master: want error = %v, got error = %v", wantError, err)
}

if err := wr.DeleteTablet(context.Background(), tablet.Alias, true); err != nil {
t.Fatalf("DeleteTablet failed: %v", err)
}
}

// TestDeleteTabletFalseMaster tests that you can delete a false master tablet
// with allowMaster set to false
func TestDeleteTabletFalseMaster(t *testing.T) {
cell := "cell1"
ts := memorytopo.NewServer(cell)
wr := New(logutil.NewConsoleLogger(), ts, nil)

tablet1 := &topodatapb.Tablet{
Alias: &topodatapb.TabletAlias{
Cell: cell,
Uid: 1,
},
Keyspace: "test",
Shard: "0",
Type: topodatapb.TabletType_MASTER,
}

if err := wr.InitTablet(context.Background(), tablet1, false /*allowMasterOverride*/, true /*createShardAndKeyspace*/, false /*allowUpdate*/); err != nil {
t.Fatalf("InitTablet failed: %v", err)
}

tablet2 := &topodatapb.Tablet{
Alias: &topodatapb.TabletAlias{
Cell: cell,
Uid: 2,
},
Keyspace: "test",
Shard: "0",
Type: topodatapb.TabletType_MASTER,
}
if err := wr.InitTablet(context.Background(), tablet2, true /*allowMasterOverride*/, false /*createShardAndKeyspace*/, false /*allowUpdate*/); err != nil {
t.Fatalf("InitTablet failed: %v", err)
}

// set MasterAlias and MasterTermStartTime on shard to match chosen master tablet
if _, err := ts.UpdateShardFields(context.Background(), "test", "0", func(si *topo.ShardInfo) error {
si.MasterAlias = tablet2.Alias
si.MasterTermStartTime = tablet2.MasterTermStartTime
return nil
}); err != nil {
t.Fatalf("UpdateShardFields failed: %v", err)
}

// Should be able to delete old (false) master with allowMaster = false
if err := wr.DeleteTablet(context.Background(), tablet1.Alias, false); err != nil {
t.Fatalf("DeleteTablet failed: %v", err)
}
}