diff --git a/go/test/endtoend/vreplication/cluster.go b/go/test/endtoend/vreplication/cluster.go index a8ec0c5c81f..b17e551360a 100644 --- a/go/test/endtoend/vreplication/cluster.go +++ b/go/test/endtoend/vreplication/cluster.go @@ -317,14 +317,17 @@ func (vc *VitessCluster) AddShards(t testing.TB, cells []*Cell, keyspace *Keyspa tablets = append(tablets, tablet) dbProcesses = append(dbProcesses, proc) } - for i := 0; i < numRdonly; i++ { - log.Infof("Adding RdOnly tablet") - tablet, proc, err := vc.AddTablet(t, cell, keyspace, shard, "rdonly", tabletID+tabletIndex) - require.NoError(t, err) - require.NotNil(t, tablet) - tabletIndex++ - tablets = append(tablets, tablet) - dbProcesses = append(dbProcesses, proc) + // Only create RDONLY tablets in the default cell + if cell.Name == cluster.DefaultCell { + for i := 0; i < numRdonly; i++ { + log.Infof("Adding RdOnly tablet") + tablet, proc, err := vc.AddTablet(t, cell, keyspace, shard, "rdonly", tabletID+tabletIndex) + require.NoError(t, err) + require.NotNil(t, tablet) + tabletIndex++ + tablets = append(tablets, tablet) + dbProcesses = append(dbProcesses, proc) + } } for ind, proc := range dbProcesses { diff --git a/go/test/endtoend/vreplication/resharding_workflows_v2_test.go b/go/test/endtoend/vreplication/resharding_workflows_v2_test.go index d69a0f5a56e..8333ecceef0 100644 --- a/go/test/endtoend/vreplication/resharding_workflows_v2_test.go +++ b/go/test/endtoend/vreplication/resharding_workflows_v2_test.go @@ -51,8 +51,8 @@ const ( ) var ( - targetTab1, targetTab2, targetReplicaTab1 *cluster.VttabletProcess - sourceReplicaTab, sourceTab *cluster.VttabletProcess + targetTab1, targetTab2, targetReplicaTab1, targetRdonlyTab1 *cluster.VttabletProcess + sourceTab, sourceReplicaTab, sourceRdonlyTab *cluster.VttabletProcess lastOutput string currentWorkflowType wrangler.VReplicationWorkflowType @@ -142,12 +142,16 @@ func tstWorkflowReverseWrites(t *testing.T) { require.NoError(t, tstWorkflowAction(t, workflowActionReverseTraffic, "master", "")) } +// tstWorkflowSwitchReadsAndWrites tests that SwitchWrites w/o any user provided --tablet_types +// value switches all traffic func tstWorkflowSwitchReadsAndWrites(t *testing.T) { - require.NoError(t, tstWorkflowAction(t, workflowActionSwitchTraffic, "replica,rdonly,master", "")) + require.NoError(t, tstWorkflowAction(t, workflowActionSwitchTraffic, "", "")) } +// tstWorkflowReversesReadsAndWrites tests that SwitchWrites w/o any user provided --tablet_types +// value switches all traffic in reverse func tstWorkflowReverseReadsAndWrites(t *testing.T) { - require.NoError(t, tstWorkflowAction(t, workflowActionReverseTraffic, "replica,rdonly,master", "")) + require.NoError(t, tstWorkflowAction(t, workflowActionReverseTraffic, "", "")) } func tstWorkflowComplete(t *testing.T) error { @@ -231,6 +235,8 @@ func getCurrentState(t *testing.T) string { // but CI currently fails on creating multiple clusters even after the previous ones are torn down func TestBasicV2Workflows(t *testing.T) { + defaultRdonly = 1 + defer func() { defaultRdonly = 0 }() vc = setupCluster(t) defer vtgateConn.Close() defer vc.TearDown(t) @@ -365,8 +371,10 @@ func testRestOfWorkflow(t *testing.T) { tstWorkflowSwitchReadsAndWrites(t) validateReadsRouteToTarget(t, "replica") + validateReadsRoute(t, "rdonly", targetRdonlyTab1) validateWritesRouteToTarget(t) tstWorkflowReverseReadsAndWrites(t) + validateReadsRoute(t, "rdonly", sourceRdonlyTab) validateReadsRouteToSource(t, "replica") validateWritesRouteToSource(t) @@ -377,6 +385,7 @@ func testRestOfWorkflow(t *testing.T) { // fully switch and complete tstWorkflowSwitchReadsAndWrites(t) + validateReadsRoute(t, "rdonly", targetRdonlyTab1) validateReadsRouteToTarget(t, "replica") validateWritesRouteToTarget(t) @@ -402,13 +411,15 @@ func setupCluster(t *testing.T) *VitessCluster { require.NotNil(t, vtgate) vtgate.WaitForStatusOfTabletInShard(fmt.Sprintf("%s.%s.master", "product", "0"), 1) vtgate.WaitForStatusOfTabletInShard(fmt.Sprintf("%s.%s.replica", "product", "0"), 2) + vtgate.WaitForStatusOfTabletInShard(fmt.Sprintf("%s.%s.rdonly", "product", "0"), 1) vtgateConn = getConnection(t, vc.ClusterConfig.hostname, vc.ClusterConfig.vtgateMySQLPort) verifyClusterHealth(t, vc) insertInitialData(t) - sourceReplicaTab = vc.Cells[defaultCell.Name].Keyspaces["product"].Shards["0"].Tablets["zone1-101"].Vttablet sourceTab = vc.Cells[defaultCell.Name].Keyspaces["product"].Shards["0"].Tablets["zone1-100"].Vttablet + sourceReplicaTab = vc.Cells[defaultCell.Name].Keyspaces["product"].Shards["0"].Tablets["zone1-101"].Vttablet + sourceRdonlyTab = vc.Cells[defaultCell.Name].Keyspaces["product"].Shards["0"].Tablets["zone1-102"].Vttablet return vc } @@ -430,10 +441,17 @@ func setupCustomerKeyspace(t *testing.T) { if err := vtgate.WaitForStatusOfTabletInShard(fmt.Sprintf("%s.%s.replica", "customer", "80-"), 2); err != nil { t.Fatal(err) } + if err := vtgate.WaitForStatusOfTabletInShard(fmt.Sprintf("%s.%s.rdonly", "customer", "-80"), 1); err != nil { + t.Fatal(err) + } + if err := vtgate.WaitForStatusOfTabletInShard(fmt.Sprintf("%s.%s.rdonly", "customer", "80-"), 1); err != nil { + t.Fatal(err) + } custKs := vc.Cells[defaultCell.Name].Keyspaces["customer"] targetTab1 = custKs.Shards["-80"].Tablets["zone1-200"].Vttablet targetTab2 = custKs.Shards["80-"].Tablets["zone1-300"].Vttablet targetReplicaTab1 = custKs.Shards["-80"].Tablets["zone1-201"].Vttablet + targetRdonlyTab1 = custKs.Shards["-80"].Tablets["zone1-202"].Vttablet } func TestSwitchReadsWritesInAnyOrder(t *testing.T) { @@ -556,12 +574,16 @@ func createAdditionalCustomerShards(t *testing.T, shards string) { if err := vtgate.WaitForStatusOfTabletInShard(fmt.Sprintf("%s.%s.replica", ksName, shardName), 2); err != nil { t.Fatal(err) } + if err := vtgate.WaitForStatusOfTabletInShard(fmt.Sprintf("%s.%s.rdonly", ksName, shardName), 1); err != nil { + require.NoError(t, err) + } } custKs := vc.Cells[defaultCell.Name].Keyspaces[ksName] targetTab2 = custKs.Shards["80-c0"].Tablets["zone1-600"].Vttablet targetTab1 = custKs.Shards["40-80"].Tablets["zone1-500"].Vttablet targetReplicaTab1 = custKs.Shards["-40"].Tablets["zone1-401"].Vttablet - sourceReplicaTab = custKs.Shards["-80"].Tablets["zone1-201"].Vttablet sourceTab = custKs.Shards["-80"].Tablets["zone1-200"].Vttablet + sourceReplicaTab = custKs.Shards["-80"].Tablets["zone1-201"].Vttablet + sourceRdonlyTab = custKs.Shards["-80"].Tablets["zone1-202"].Vttablet } diff --git a/go/vt/vtctl/vtctl.go b/go/vt/vtctl/vtctl.go index d20607f1c99..01966eb1bb4 100644 --- a/go/vt/vtctl/vtctl.go +++ b/go/vt/vtctl/vtctl.go @@ -2027,11 +2027,11 @@ func commandVRWorkflow(ctx context.Context, wr *wrangler.Wrangler, subFlags *fla workflowType wrangler.VReplicationWorkflowType) error { cells := subFlags.String("cells", "", "Cell(s) or CellAlias(es) (comma-separated) to replicate from.") - tabletTypes := subFlags.String("tablet_types", "in_order:REPLICA,MASTER", "Source tablet types to replicate from (e.g. MASTER, REPLICA, RDONLY). Defaults to --vreplication_tablet_type parameter value for the tablet, which has the default value of in_order:REPLICA,MASTER.") - dryRun := subFlags.Bool("dry_run", false, "Does a dry run of SwitchReads and only reports the actions to be taken. -dry_run is only supported for SwitchTraffic, ReverseTraffic and Complete.") - timeout := subFlags.Duration("timeout", 30*time.Second, "Specifies the maximum time to wait, in seconds, for vreplication to catch up on master migrations. The migration will be cancelled on a timeout.") - reverseReplication := subFlags.Bool("reverse_replication", true, "Also reverse the replication") - keepData := subFlags.Bool("keep_data", false, "Do not drop tables or shards (if true, only vreplication artifacts are cleaned up)") + tabletTypes := subFlags.String("tablet_types", "in_order:REPLICA,MASTER", "Source tablet types to replicate from (e.g. MASTER, REPLICA, RDONLY). Defaults to --vreplication_tablet_type parameter value for the tablet, which has the default value of in_order:REPLICA,MASTER. Note: SwitchTraffic overrides this default and uses in_order:RDONLY,REPLICA,MASTER to switch all traffic by default.") + dryRun := subFlags.Bool("dry_run", false, "Does a dry run of SwitchReads and only reports the actions to be taken. --dry_run is only supported for SwitchTraffic, ReverseTraffic and Complete.") + timeout := subFlags.Duration("timeout", 30*time.Second, "Specifies the maximum time to wait, in seconds, for vreplication to catch up on master migrations. The migration will be cancelled on a timeout. --timeout is only supported for SwitchTraffic and ReverseTraffic.") + reverseReplication := subFlags.Bool("reverse_replication", true, "Also reverse the replication (default true). --reverse_replication is only supported for SwitchTraffic.") + keepData := subFlags.Bool("keep_data", false, "Do not drop tables or shards (if true, only vreplication artifacts are cleaned up). --keep_data is only supported for Complete and Cancel.") autoStart := subFlags.Bool("auto_start", true, "If false, streams will start in the Stopped state and will need to be explicitly started") stopAfterCopy := subFlags.Bool("stop_after_copy", false, "Streams will be stopped once the copy phase is completed") @@ -2179,9 +2179,12 @@ func commandVRWorkflow(ctx context.Context, wr *wrangler.Wrangler, subFlags *fla vrwp.TabletTypes = *tabletTypes case vReplicationWorkflowActionSwitchTraffic, vReplicationWorkflowActionReverseTraffic: vrwp.Cells = *cells - vrwp.TabletTypes = *tabletTypes - if vrwp.TabletTypes == "" { - vrwp.TabletTypes = "in_order:REPLICA,MASTER" + if userPassedFlag(subFlags, "tablet_types") { + vrwp.TabletTypes = *tabletTypes + } else { + // When no tablet types are specified we are supposed to switch all traffic so + // we override the normal default for tablet_types. + vrwp.TabletTypes = "in_order:RDONLY,REPLICA,MASTER" } vrwp.Timeout = *timeout vrwp.EnableReverseReplication = *reverseReplication @@ -3806,3 +3809,15 @@ func PrintAllCommands(logger logutil.Logger) { logger.Printf("\n") } } + +// userPassedFlag returns true if the flag name given was provided +// as a command-line argument by the user. +func userPassedFlag(flags *flag.FlagSet, name string) bool { + passed := false + flags.Visit(func(f *flag.Flag) { + if f.Name == name { + passed = true + } + }) + return passed +}