func (wr *Wrangler) setKeyspaceShardingInfo(keyspace, shardingColumnName string, shardingColumnType key.KeyspaceIdType, force bool) error { ki, err := wr.ts.GetKeyspace(keyspace) if err != nil { return err } if ki.ShardingColumnName != "" && ki.ShardingColumnName != shardingColumnName { if force { log.Warningf("Forcing keyspace ShardingColumnName change from %v to %v", ki.ShardingColumnName, shardingColumnName) } else { return fmt.Errorf("Cannot change ShardingColumnName from %v to %v (use -force to override)", ki.ShardingColumnName, shardingColumnName) } } if ki.ShardingColumnType != key.KIT_UNSET && ki.ShardingColumnType != shardingColumnType { if force { log.Warningf("Forcing keyspace ShardingColumnType change from %v to %v", ki.ShardingColumnType, shardingColumnType) } else { return fmt.Errorf("Cannot change ShardingColumnType from %v to %v (use -force to override)", ki.ShardingColumnType, shardingColumnType) } } ki.ShardingColumnName = shardingColumnName ki.ShardingColumnType = shardingColumnType return topo.UpdateKeyspace(wr.ts, ki) }
func (wr *Wrangler) setKeyspaceShardingInfo(ctx context.Context, keyspace, shardingColumnName string, shardingColumnType pb.KeyspaceIdType, splitShardCount int32, force bool) error { ki, err := wr.ts.GetKeyspace(ctx, keyspace) if err != nil { return err } if ki.ShardingColumnName != "" && ki.ShardingColumnName != shardingColumnName { if force { wr.Logger().Warningf("Forcing keyspace ShardingColumnName change from %v to %v", ki.ShardingColumnName, shardingColumnName) } else { return fmt.Errorf("Cannot change ShardingColumnName from %v to %v (use -force to override)", ki.ShardingColumnName, shardingColumnName) } } if ki.ShardingColumnType != pb.KeyspaceIdType_UNSET && ki.ShardingColumnType != shardingColumnType { if force { wr.Logger().Warningf("Forcing keyspace ShardingColumnType change from %v to %v", ki.ShardingColumnType, shardingColumnType) } else { return fmt.Errorf("Cannot change ShardingColumnType from %v to %v (use -force to override)", ki.ShardingColumnType, shardingColumnType) } } ki.ShardingColumnName = shardingColumnName ki.ShardingColumnType = shardingColumnType ki.SplitShardCount = splitShardCount return topo.UpdateKeyspace(ctx, wr.ts, ki) }
// replicaMigrateServedFrom handles the slave (replica, rdonly) migration. func (wr *Wrangler) replicaMigrateServedFrom(ctx context.Context, ki *topo.KeyspaceInfo, sourceShard *topo.ShardInfo, destinationShard *topo.ShardInfo, servedType pb.TabletType, cells []string, reverse bool, tables []string, ev *events.MigrateServedFrom) error { // Save the destination keyspace (its ServedFrom has been changed) event.DispatchUpdate(ev, "updating keyspace") if err := topo.UpdateKeyspace(ctx, wr.ts, ki); err != nil { return err } // Save the source shard (its blacklisted tables field has changed) event.DispatchUpdate(ev, "updating source shard") if err := sourceShard.UpdateSourceBlacklistedTables(servedType, cells, reverse, tables); err != nil { return fmt.Errorf("UpdateSourceBlacklistedTables(%v/%v) failed: %v", sourceShard.Keyspace(), sourceShard.ShardName(), err) } if err := topo.UpdateShard(ctx, wr.ts, sourceShard); err != nil { return fmt.Errorf("UpdateShard(%v/%v) failed: %v", sourceShard.Keyspace(), sourceShard.ShardName(), err) } // Now refresh the source servers so they reload their // blacklisted table list event.DispatchUpdate(ev, "refreshing sources tablets state so they update their blacklisted tables") if err := wr.RefreshTablesByShard(ctx, sourceShard, servedType, cells); err != nil { return err } return nil }
// replicaMigrateServedFrom handles the slave (replica, rdonly) migration. func (wr *Wrangler) replicaMigrateServedFrom(ki *topo.KeyspaceInfo, sourceShard *topo.ShardInfo, destinationShard *topo.ShardInfo, servedType topo.TabletType, reverse bool, tables []string, ev *events.MigrateServedFrom) error { // Save the destination keyspace (its ServedFrom has been changed) event.DispatchUpdate(ev, "updating keyspace") if err := topo.UpdateKeyspace(wr.ts, ki); err != nil { return err } // Save the source shard (its blacklisted tables field has changed) event.DispatchUpdate(ev, "updating source shard") if sourceShard.BlacklistedTablesMap == nil { sourceShard.BlacklistedTablesMap = make(map[topo.TabletType][]string) } if reverse { delete(sourceShard.BlacklistedTablesMap, servedType) } else { sourceShard.BlacklistedTablesMap[servedType] = tables } if err := topo.UpdateShard(wr.ts, sourceShard); err != nil { return err } // Now refresh the source servers so they reload their // blacklisted table list event.DispatchUpdate(ev, "refreshing sources tablets state so they update their blacklisted tables") if err := wr.RefreshTablesByShard(sourceShard.Keyspace(), sourceShard.ShardName(), servedType); err != nil { return err } return nil }
func (wr *Wrangler) setKeyspaceServedFrom(ctx context.Context, keyspace string, servedType pb.TabletType, cells []string, sourceKeyspace string, remove bool) error { ki, err := wr.ts.GetKeyspace(ctx, keyspace) if err != nil { return err } if err := ki.UpdateServedFromMap(servedType, cells, sourceKeyspace, remove, nil); err != nil { return err } return topo.UpdateKeyspace(ctx, wr.ts, ki) }
func (wr *Wrangler) migrateServedFrom(ki *topo.KeyspaceInfo, si *topo.ShardInfo, servedType topo.TabletType, reverse bool) (err error) { // re-read and update keyspace info record ki, err = wr.ts.GetKeyspace(ki.KeyspaceName()) if err != nil { return err } if reverse { if _, ok := ki.ServedFrom[servedType]; ok { return fmt.Errorf("Destination Keyspace %s is not serving type %v", ki.KeyspaceName(), servedType) } ki.ServedFrom[servedType] = si.SourceShards[0].Keyspace } else { if _, ok := ki.ServedFrom[servedType]; !ok { return fmt.Errorf("Destination Keyspace %s is already serving type %v", ki.KeyspaceName(), servedType) } delete(ki.ServedFrom, servedType) } // re-read and check the destination shard si, err = wr.ts.GetShard(si.Keyspace(), si.ShardName()) if err != nil { return err } if len(si.SourceShards) != 1 { return fmt.Errorf("Destination shard %v/%v is not a vertical split target", si.Keyspace(), si.ShardName()) } tables := si.SourceShards[0].Tables // read the source shard, we'll need its master sourceShard, err := wr.ts.GetShard(si.SourceShards[0].Keyspace, si.SourceShards[0].Shard) if err != nil { return err } ev := &events.MigrateServedFrom{ Keyspace: *ki, SourceShard: *sourceShard, DestinationShard: *si, ServedType: servedType, Reverse: reverse, } event.DispatchUpdate(ev, "start") defer func() { if err != nil { event.DispatchUpdate(ev, "failed: "+err.Error()) } }() // For master type migration, need to: // - switch the source shard to read-only // - gather the replication point // - wait for filtered replication to catch up before we continue // - disable filtered replication after the fact var sourceMasterTabletInfo *topo.TabletInfo if servedType == topo.TYPE_MASTER { // set master to read-only event.DispatchUpdate(ev, "setting source shard master to read-only") actionPath, err := wr.ai.SetReadOnly(sourceShard.MasterAlias) if err != nil { return err } if err := wr.WaitForCompletion(actionPath); err != nil { return err } // get the position event.DispatchUpdate(ev, "getting master position") sourceMasterTabletInfo, err = wr.ts.GetTablet(sourceShard.MasterAlias) if err != nil { return err } masterPosition, err := wr.ai.MasterPosition(sourceMasterTabletInfo, wr.ActionTimeout()) if err != nil { return err } // wait for it event.DispatchUpdate(ev, "waiting for destination master to catch up to source master") if err := wr.ai.WaitBlpPosition(si.MasterAlias, blproto.BlpPosition{ Uid: 0, Position: masterPosition, }, wr.ActionTimeout()); err != nil { return err } // and clear the shard record si.SourceShards = nil } // All is good, we can save the keyspace and shard (if needed) now event.DispatchUpdate(ev, "updating keyspace") if err = topo.UpdateKeyspace(wr.ts, ki); err != nil { return err } event.DispatchUpdate(ev, "updating destination shard") if servedType == topo.TYPE_MASTER { if err := topo.UpdateShard(wr.ts, si); err != nil { return err } } // Tell the new shards masters they can now be read-write. // Invoking a remote action will also make the tablet stop filtered // replication. event.DispatchUpdate(ev, "setting destination shard masters read-write") if servedType == topo.TYPE_MASTER { if err := wr.makeMastersReadWrite([]*topo.ShardInfo{si}); err != nil { return err } } // Now blacklist the table list on the right servers event.DispatchUpdate(ev, "setting blacklisted tables on source shard") if servedType == topo.TYPE_MASTER { if err := wr.ai.SetBlacklistedTables(sourceMasterTabletInfo, tables, wr.ActionTimeout()); err != nil { return err } } else { // We use the list of tables that are replicating // for the blacklist. In case of a reverse move, we clear the // blacklist. if reverse { tables = nil } if err := wr.SetBlacklistedTablesByShard(sourceShard.Keyspace(), sourceShard.ShardName(), servedType, tables); err != nil { return err } } event.DispatchUpdate(ev, "finished") return nil }
// masterMigrateServedFrom handles the master migration. The ordering is // a bit different than for rdonly / replica to guarantee a smooth transition. // // The order is as follows: // - Add BlacklistedTables on the source shard map for master // - Refresh the source master, so it stops writing on the tables // - Get the source master position, wait until destination master reaches it // - Clear SourceShard on the destination Shard // - Refresh the destination master, so its stops its filtered // replication and starts accepting writes func (wr *Wrangler) masterMigrateServedFrom(ctx context.Context, ki *topo.KeyspaceInfo, sourceShard *topo.ShardInfo, destinationShard *topo.ShardInfo, tables []string, ev *events.MigrateServedFrom, filteredReplicationWaitTime time.Duration) error { // Read the data we need sourceMasterTabletInfo, err := wr.ts.GetTablet(ctx, topo.ProtoToTabletAlias(sourceShard.MasterAlias)) if err != nil { return err } destinationMasterTabletInfo, err := wr.ts.GetTablet(ctx, topo.ProtoToTabletAlias(destinationShard.MasterAlias)) if err != nil { return err } // Update source shard (more blacklisted tables) event.DispatchUpdate(ev, "updating source shard") if err := sourceShard.UpdateSourceBlacklistedTables(pb.TabletType_MASTER, nil, false, tables); err != nil { return fmt.Errorf("UpdateSourceBlacklistedTables(%v/%v) failed: %v", sourceShard.Keyspace(), sourceShard.ShardName(), err) } if err := topo.UpdateShard(ctx, wr.ts, sourceShard); err != nil { return fmt.Errorf("UpdateShard(%v/%v) failed: %v", sourceShard.Keyspace(), sourceShard.ShardName(), err) } // Now refresh the blacklisted table list on the source master event.DispatchUpdate(ev, "refreshing source master so it updates its blacklisted tables") if err := wr.tmc.RefreshState(ctx, sourceMasterTabletInfo); err != nil { return err } // get the position event.DispatchUpdate(ev, "getting master position") masterPosition, err := wr.tmc.MasterPosition(ctx, sourceMasterTabletInfo) if err != nil { return err } // wait for it event.DispatchUpdate(ev, "waiting for destination master to catch up to source master") if err := wr.tmc.WaitBlpPosition(ctx, destinationMasterTabletInfo, blproto.BlpPosition{ Uid: 0, Position: masterPosition, }, filteredReplicationWaitTime); err != nil { return err } // Update the destination keyspace (its ServedFrom has changed) event.DispatchUpdate(ev, "updating keyspace") if err = topo.UpdateKeyspace(ctx, wr.ts, ki); err != nil { return err } // Update the destination shard (no more source shard) event.DispatchUpdate(ev, "updating destination shard") destinationShard.SourceShards = nil if err := topo.UpdateShard(ctx, wr.ts, destinationShard); err != nil { return err } // Tell the new shards masters they can now be read-write. // Invoking a remote action will also make the tablet stop filtered // replication. event.DispatchUpdate(ev, "setting destination shard masters read-write") if err := wr.refreshMasters(ctx, []*topo.ShardInfo{destinationShard}); err != nil { return err } return nil }
// CheckKeyspace tests the keyspace part of the API func CheckKeyspace(ctx context.Context, t *testing.T, ts topo.Server) { keyspaces, err := ts.GetKeyspaces(ctx) if err != nil { t.Errorf("GetKeyspaces(empty): %v", err) } if len(keyspaces) != 0 { t.Errorf("len(GetKeyspaces()) != 0: %v", keyspaces) } if err := ts.CreateKeyspace(ctx, "test_keyspace", &pb.Keyspace{}); err != nil { t.Errorf("CreateKeyspace: %v", err) } if err := ts.CreateKeyspace(ctx, "test_keyspace", &pb.Keyspace{}); err != topo.ErrNodeExists { t.Errorf("CreateKeyspace(again) is not ErrNodeExists: %v", err) } // Delete and re-create. if err := ts.DeleteKeyspace(ctx, "test_keyspace"); err != nil { t.Errorf("DeleteKeyspace: %v", err) } if err := ts.CreateKeyspace(ctx, "test_keyspace", &pb.Keyspace{}); err != nil { t.Errorf("CreateKeyspace: %v", err) } keyspaces, err = ts.GetKeyspaces(ctx) if err != nil { t.Errorf("GetKeyspaces: %v", err) } if len(keyspaces) != 1 || keyspaces[0] != "test_keyspace" { t.Errorf("GetKeyspaces: want %v, got %v", []string{"test_keyspace"}, keyspaces) } k := &pb.Keyspace{ ShardingColumnName: "user_id", ShardingColumnType: pb.KeyspaceIdType_UINT64, ServedFroms: []*pb.Keyspace_ServedFrom{ &pb.Keyspace_ServedFrom{ TabletType: pb.TabletType_REPLICA, Cells: []string{"c1", "c2"}, Keyspace: "test_keyspace3", }, &pb.Keyspace_ServedFrom{ TabletType: pb.TabletType_MASTER, Cells: nil, Keyspace: "test_keyspace3", }, }, SplitShardCount: 64, } if err := ts.CreateKeyspace(ctx, "test_keyspace2", k); err != nil { t.Errorf("CreateKeyspace: %v", err) } keyspaces, err = ts.GetKeyspaces(ctx) if err != nil { t.Errorf("GetKeyspaces: %v", err) } if len(keyspaces) != 2 || keyspaces[0] != "test_keyspace" || keyspaces[1] != "test_keyspace2" { t.Errorf("GetKeyspaces: want %v, got %v", []string{"test_keyspace", "test_keyspace2"}, keyspaces) } // Call delete shards and make sure the keyspace still exists. if err := ts.DeleteKeyspaceShards(ctx, "test_keyspace2"); err != nil { t.Errorf("DeleteKeyspaceShards: %v", err) } ki, err := ts.GetKeyspace(ctx, "test_keyspace2") if err != nil { t.Fatalf("GetKeyspace: %v", err) } if !reflect.DeepEqual(ki.Keyspace, k) { t.Fatalf("returned keyspace doesn't match: got %v expected %v", ki.Keyspace, k) } ki.ShardingColumnName = "other_id" ki.ShardingColumnType = pb.KeyspaceIdType_BYTES var newServedFroms []*pb.Keyspace_ServedFrom for _, ksf := range ki.ServedFroms { if ksf.TabletType == pb.TabletType_MASTER { continue } if ksf.TabletType == pb.TabletType_REPLICA { ksf.Keyspace = "test_keyspace4" } newServedFroms = append(newServedFroms, ksf) } ki.ServedFroms = newServedFroms err = topo.UpdateKeyspace(ctx, ts, ki) if err != nil { t.Fatalf("UpdateKeyspace: %v", err) } ki, err = ts.GetKeyspace(ctx, "test_keyspace2") if err != nil { t.Fatalf("GetKeyspace: %v", err) } if ki.ShardingColumnName != "other_id" || ki.ShardingColumnType != pb.KeyspaceIdType_BYTES || ki.GetServedFrom(pb.TabletType_REPLICA).Keyspace != "test_keyspace4" { t.Errorf("GetKeyspace: unexpected keyspace, got %v", *ki) } }
// CheckKeyspace tests the keyspace part of the API func CheckKeyspace(ctx context.Context, t *testing.T, ts topo.Server) { keyspaces, err := ts.GetKeyspaces(ctx) if err != nil { t.Errorf("GetKeyspaces(empty): %v", err) } if len(keyspaces) != 0 { t.Errorf("len(GetKeyspaces()) != 0: %v", keyspaces) } if err := ts.CreateKeyspace(ctx, "test_keyspace", &topo.Keyspace{}); err != nil { t.Errorf("CreateKeyspace: %v", err) } if err := ts.CreateKeyspace(ctx, "test_keyspace", &topo.Keyspace{}); err != topo.ErrNodeExists { t.Errorf("CreateKeyspace(again) is not ErrNodeExists: %v", err) } // Delete and re-create. if err := ts.DeleteKeyspace(ctx, "test_keyspace"); err != nil { t.Errorf("DeleteKeyspace: %v", err) } if err := ts.CreateKeyspace(ctx, "test_keyspace", &topo.Keyspace{}); err != nil { t.Errorf("CreateKeyspace: %v", err) } keyspaces, err = ts.GetKeyspaces(ctx) if err != nil { t.Errorf("GetKeyspaces: %v", err) } if len(keyspaces) != 1 || keyspaces[0] != "test_keyspace" { t.Errorf("GetKeyspaces: want %v, got %v", []string{"test_keyspace"}, keyspaces) } k := &topo.Keyspace{ ShardingColumnName: "user_id", ShardingColumnType: key.KIT_UINT64, ServedFromMap: map[topo.TabletType]*topo.KeyspaceServedFrom{ topo.TYPE_REPLICA: &topo.KeyspaceServedFrom{ Cells: []string{"c1", "c2"}, Keyspace: "test_keyspace3", }, topo.TYPE_MASTER: &topo.KeyspaceServedFrom{ Cells: nil, Keyspace: "test_keyspace3", }, }, SplitShardCount: 64, } if err := ts.CreateKeyspace(ctx, "test_keyspace2", k); err != nil { t.Errorf("CreateKeyspace: %v", err) } keyspaces, err = ts.GetKeyspaces(ctx) if err != nil { t.Errorf("GetKeyspaces: %v", err) } if len(keyspaces) != 2 || keyspaces[0] != "test_keyspace" || keyspaces[1] != "test_keyspace2" { t.Errorf("GetKeyspaces: want %v, got %v", []string{"test_keyspace", "test_keyspace2"}, keyspaces) } // Call delete shards and make sure the keyspace still exists. if err := ts.DeleteKeyspaceShards(ctx, "test_keyspace2"); err != nil { t.Errorf("DeleteKeyspaceShards: %v", err) } ki, err := ts.GetKeyspace(ctx, "test_keyspace2") if err != nil { t.Fatalf("GetKeyspace: %v", err) } if !reflect.DeepEqual(ki.Keyspace, k) { t.Fatalf("returned keyspace doesn't match: got %v expected %v", ki.Keyspace, k) } ki.ShardingColumnName = "other_id" ki.ShardingColumnType = key.KIT_BYTES delete(ki.ServedFromMap, topo.TYPE_MASTER) ki.ServedFromMap[topo.TYPE_REPLICA].Keyspace = "test_keyspace4" err = topo.UpdateKeyspace(ctx, ts, ki) if err != nil { t.Fatalf("UpdateKeyspace: %v", err) } ki, err = ts.GetKeyspace(ctx, "test_keyspace2") if err != nil { t.Fatalf("GetKeyspace: %v", err) } if ki.ShardingColumnName != "other_id" || ki.ShardingColumnType != key.KIT_BYTES || ki.ServedFromMap[topo.TYPE_REPLICA].Keyspace != "test_keyspace4" { t.Errorf("GetKeyspace: unexpected keyspace, got %v", *ki) } }
func CheckKeyspace(t *testing.T, ts topo.Server) { keyspaces, err := ts.GetKeyspaces() if err != nil { t.Errorf("GetKeyspaces(empty): %v", err) } if len(keyspaces) != 0 { t.Errorf("len(GetKeyspaces()) != 0: %v", keyspaces) } if err := ts.CreateKeyspace("test_keyspace", &topo.Keyspace{}); err != nil { t.Errorf("CreateKeyspace: %v", err) } if err := ts.CreateKeyspace("test_keyspace", &topo.Keyspace{}); err != topo.ErrNodeExists { t.Errorf("CreateKeyspace(again) is not ErrNodeExists: %v", err) } keyspaces, err = ts.GetKeyspaces() if err != nil { t.Errorf("GetKeyspaces: %v", err) } if len(keyspaces) != 1 || keyspaces[0] != "test_keyspace" { t.Errorf("GetKeyspaces: want %v, got %v", []string{"test_keyspace"}, keyspaces) } if err := ts.CreateKeyspace("test_keyspace2", &topo.Keyspace{ ShardingColumnName: "user_id", ShardingColumnType: key.KIT_UINT64, ServedFrom: map[topo.TabletType]string{ topo.TYPE_MASTER: "test_keyspace3", }, }); err != nil { t.Errorf("CreateKeyspace: %v", err) } keyspaces, err = ts.GetKeyspaces() if err != nil { t.Errorf("GetKeyspaces: %v", err) } if len(keyspaces) != 2 || keyspaces[0] != "test_keyspace" || keyspaces[1] != "test_keyspace2" { t.Errorf("GetKeyspaces: want %v, got %v", []string{"test_keyspace", "test_keyspace2"}, keyspaces) } ki, err := ts.GetKeyspace("test_keyspace2") if err != nil { t.Fatalf("GetKeyspace: %v", err) } if ki.ShardingColumnName != "user_id" || ki.ShardingColumnType != key.KIT_UINT64 || ki.ServedFrom[topo.TYPE_MASTER] != "test_keyspace3" { t.Errorf("GetKeyspace: unexpected keyspace, got %v", *ki) } ki.ShardingColumnName = "other_id" ki.ShardingColumnType = key.KIT_BYTES delete(ki.ServedFrom, topo.TYPE_MASTER) ki.ServedFrom[topo.TYPE_REPLICA] = "test_keyspace4" err = topo.UpdateKeyspace(ts, ki) if err != nil { t.Fatalf("UpdateKeyspace: %v", err) } ki, err = ts.GetKeyspace("test_keyspace2") if err != nil { t.Fatalf("GetKeyspace: %v", err) } if ki.ShardingColumnName != "other_id" || ki.ShardingColumnType != key.KIT_BYTES || ki.ServedFrom[topo.TYPE_REPLICA] != "test_keyspace4" { t.Errorf("GetKeyspace: unexpected keyspace, got %v", *ki) } }
// masterMigrateServedFrom handles the master migration. The ordering is // a bit different than for rdonly / replica to guarantee a smooth transition. // // The order is as follows: // - Add BlacklistedTables on the source shard map for master // - Refresh the source master, so it stops writing on the tables // - Get the source master position, wait until destination master reaches it // - Clear SourceShard on the destination Shard // - Refresh the destination master, so its stops its filtered // replication and starts accepting writes func (wr *Wrangler) masterMigrateServedFrom(ki *topo.KeyspaceInfo, sourceShard *topo.ShardInfo, destinationShard *topo.ShardInfo, servedType topo.TabletType, tables []string, ev *events.MigrateServedFrom) error { // Read the data we need sourceMasterTabletInfo, err := wr.ts.GetTablet(sourceShard.MasterAlias) if err != nil { return err } destinationMasterTabletInfo, err := wr.ts.GetTablet(destinationShard.MasterAlias) if err != nil { return err } // Update source shard (more blacklisted tables) event.DispatchUpdate(ev, "updating source shard") if sourceShard.BlacklistedTablesMap == nil { sourceShard.BlacklistedTablesMap = make(map[topo.TabletType][]string) } sourceShard.BlacklistedTablesMap[servedType] = tables if err := topo.UpdateShard(wr.ts, sourceShard); err != nil { return err } // Now refresh the blacklisted table list on the source master event.DispatchUpdate(ev, "refreshing source master so it updates its blacklisted tables") if err := wr.tmc.RefreshState(sourceMasterTabletInfo, wr.ActionTimeout()); err != nil { return err } // get the position event.DispatchUpdate(ev, "getting master position") masterPosition, err := wr.tmc.MasterPosition(sourceMasterTabletInfo, wr.ActionTimeout()) if err != nil { return err } // wait for it event.DispatchUpdate(ev, "waiting for destination master to catch up to source master") if err := wr.tmc.WaitBlpPosition(destinationMasterTabletInfo, blproto.BlpPosition{ Uid: 0, Position: masterPosition, }, wr.ActionTimeout()); err != nil { return err } // Update the destination keyspace (its ServedFrom has changed) event.DispatchUpdate(ev, "updating keyspace") if err = topo.UpdateKeyspace(wr.ts, ki); err != nil { return err } // Update the destination shard (no more source shard) event.DispatchUpdate(ev, "updating destination shard") destinationShard.SourceShards = nil if err := topo.UpdateShard(wr.ts, destinationShard); err != nil { return err } // Tell the new shards masters they can now be read-write. // Invoking a remote action will also make the tablet stop filtered // replication. event.DispatchUpdate(ev, "setting destination shard masters read-write") if err := wr.refreshMasters([]*topo.ShardInfo{destinationShard}); err != nil { return err } return nil }