diff --git a/go/vt/vtctl/workflow/server_test.go b/go/vt/vtctl/workflow/server_test.go index b227485a3b1..9feb64232b0 100644 --- a/go/vt/vtctl/workflow/server_test.go +++ b/go/vt/vtctl/workflow/server_test.go @@ -1820,6 +1820,7 @@ func TestMirrorTraffic(t *testing.T) { sourceShards := []string{"-"} targetKs := "target" targetShards := []string{"-80", "80-"} + otherKs := "otherks" table1 := "table1" table2 := "table2" workflow := "src2target" @@ -2066,6 +2067,30 @@ func TestMirrorTraffic(t *testing.T) { }, }, }, + { + name: "percent zero preserves other mirror targets", + mirrorRules: map[string]map[string]float32{ + fmt.Sprintf("%s.%s", sourceKs, table1): { + fmt.Sprintf("%s.%s", targetKs, table1): 50.0, + fmt.Sprintf("%s.%s", otherKs, table1): 25.0, + }, + fmt.Sprintf("%s.%s", sourceKs, table2): { + fmt.Sprintf("%s.%s", targetKs, table2): 50.0, + }, + }, + req: &vtctldatapb.WorkflowMirrorTrafficRequest{ + Keyspace: targetKs, + Workflow: workflow, + TabletTypes: []topodatapb.TabletType{topodatapb.TabletType_PRIMARY}, + Percent: 0.0, + }, + routingRules: initialRoutingRules, + wantMirrorRules: map[string]map[string]float32{ + fmt.Sprintf("%s.%s", sourceKs, table1): { + fmt.Sprintf("%s.%s", otherKs, table1): 25.0, + }, + }, + }, { name: "does not overwrite unrelated mirror rules", mirrorRules: map[string]map[string]float32{ diff --git a/go/vt/vtctl/workflow/traffic_switcher.go b/go/vt/vtctl/workflow/traffic_switcher.go index c518cda36f4..dc767e15562 100644 --- a/go/vt/vtctl/workflow/traffic_switcher.go +++ b/go/vt/vtctl/workflow/traffic_switcher.go @@ -1567,7 +1567,10 @@ func (ts *trafficSwitcher) mirrorTableTraffic(ctx context.Context, types []topod if percent == 0 { // When percent is 0, remove mirror rule if it exists. if _, ok := mrs[fromTable][toTable]; ok { - delete(mrs, fromTable) + delete(mrs[fromTable], toTable) + if len(mrs[fromTable]) == 0 { + delete(mrs, fromTable) + } } } else { mrs[fromTable][toTable] = percent diff --git a/go/vt/vtctl/workflow/traffic_switcher_test.go b/go/vt/vtctl/workflow/traffic_switcher_test.go index d9b5e349630..24a0ce8014c 100644 --- a/go/vt/vtctl/workflow/traffic_switcher_test.go +++ b/go/vt/vtctl/workflow/traffic_switcher_test.go @@ -32,8 +32,10 @@ import ( tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata" topodatapb "vitess.io/vitess/go/vt/proto/topodata" "vitess.io/vitess/go/vt/proto/vschema" + vtctldatapb "vitess.io/vitess/go/vt/proto/vtctldata" "vitess.io/vitess/go/vt/sqlparser" "vitess.io/vitess/go/vt/topo" + "vitess.io/vitess/go/vt/topotools" "vitess.io/vitess/go/vt/vtgate/vindexes" ) @@ -450,3 +452,170 @@ func TestCancelMigration_SHARDS(t *testing.T) { assert.Empty(t, env.tmc.vrQueries[100]) assert.Empty(t, env.tmc.vrQueries[200]) } + +func TestDeleteRoutingRulesPreservesUnrelated(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + workflowName := "wf1" + tableName := "t1" + sourceKeyspaceName := "sourceks" + targetKeyspaceName := "targetks" + + sourceKeyspace := &testKeyspace{ + KeyspaceName: sourceKeyspaceName, + ShardNames: []string{"0"}, + } + targetKeyspace := &testKeyspace{ + KeyspaceName: targetKeyspaceName, + ShardNames: []string{"0"}, + } + + schema := map[string]*tabletmanagerdatapb.SchemaDefinition{ + tableName: { + TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ + { + Name: tableName, + Schema: fmt.Sprintf("CREATE TABLE %s (id BIGINT, name VARCHAR(64), PRIMARY KEY (id))", tableName), + }, + }, + }, + } + + env := newTestEnv(t, ctx, defaultCellName, sourceKeyspace, targetKeyspace) + defer env.close() + env.tmc.schema = schema + + ts, _, err := env.ws.getWorkflowState(ctx, targetKeyspaceName, workflowName) + require.NoError(t, err) + + rules := map[string][]string{ + tableName: {fmt.Sprintf("%s.%s", sourceKeyspaceName, tableName)}, + tableName + "@replica": {fmt.Sprintf("%s.%s", sourceKeyspaceName, tableName)}, + sourceKeyspaceName + "." + tableName: {fmt.Sprintf("%s.%s", sourceKeyspaceName, tableName)}, + targetKeyspaceName + "." + tableName: {fmt.Sprintf("%s.%s", sourceKeyspaceName, tableName)}, + "unrelated": {"otherks.unrelated"}, + } + require.NoError(t, topotools.SaveRoutingRules(ctx, env.ts, rules)) + + err = ts.deleteRoutingRules(ctx) + require.NoError(t, err) + + got, err := topotools.GetRoutingRules(ctx, env.ts) + require.NoError(t, err) + require.Equal(t, map[string][]string{ + "unrelated": {"otherks.unrelated"}, + }, got) +} + +func TestDeleteShardRoutingRulesPreservesUnrelated(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + workflowName := "wf1" + tableName := "t1" + sourceKeyspaceName := "sourceks" + targetKeyspaceName := "targetks" + + sourceKeyspace := &testKeyspace{ + KeyspaceName: sourceKeyspaceName, + ShardNames: []string{"0"}, + } + targetKeyspace := &testKeyspace{ + KeyspaceName: targetKeyspaceName, + ShardNames: []string{"0"}, + } + + schema := map[string]*tabletmanagerdatapb.SchemaDefinition{ + tableName: { + TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ + { + Name: tableName, + Schema: fmt.Sprintf("CREATE TABLE %s (id BIGINT, name VARCHAR(64), PRIMARY KEY (id))", tableName), + }, + }, + }, + } + + env := newTestEnv(t, ctx, defaultCellName, sourceKeyspace, targetKeyspace) + defer env.close() + env.tmc.schema = schema + + ts, _, err := env.ws.getWorkflowState(ctx, targetKeyspaceName, workflowName) + require.NoError(t, err) + ts.isPartialMigration = true + + require.NoError(t, topotools.SaveShardRoutingRules(ctx, env.ts, map[string]string{ + fmt.Sprintf("%s.%s", targetKeyspaceName, "0"): sourceKeyspaceName, + "otherks.0": "otherks", + })) + + err = ts.deleteShardRoutingRules(ctx) + require.NoError(t, err) + + got, err := topotools.GetShardRoutingRules(ctx, env.ts) + require.NoError(t, err) + require.Equal(t, map[string]string{ + "otherks.0": "otherks", + }, got) +} + +func TestDeleteKeyspaceRoutingRulesPreservesUnrelated(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + workflowName := "wf1" + tableName := "t1" + sourceKeyspaceName := "sourceks" + targetKeyspaceName := "targetks" + + sourceKeyspace := &testKeyspace{ + KeyspaceName: sourceKeyspaceName, + ShardNames: []string{"0"}, + } + targetKeyspace := &testKeyspace{ + KeyspaceName: targetKeyspaceName, + ShardNames: []string{"0"}, + } + + schema := map[string]*tabletmanagerdatapb.SchemaDefinition{ + tableName: { + TableDefinitions: []*tabletmanagerdatapb.TableDefinition{ + { + Name: tableName, + Schema: fmt.Sprintf("CREATE TABLE %s (id BIGINT, name VARCHAR(64), PRIMARY KEY (id))", tableName), + }, + }, + }, + } + + env := newTestEnv(t, ctx, defaultCellName, sourceKeyspace, targetKeyspace) + defer env.close() + env.tmc.schema = schema + + ts, _, err := env.ws.getWorkflowState(ctx, targetKeyspaceName, workflowName) + require.NoError(t, err) + ts.options = &vtctldatapb.WorkflowOptions{TenantId: "tenant"} + + rules := map[string]string{ + sourceKeyspaceName: targetKeyspaceName, + sourceKeyspaceName + "@replica": targetKeyspaceName, + sourceKeyspaceName + "@rdonly": targetKeyspaceName, + "otherks": "otherks", + } + require.NoError(t, topotools.UpdateKeyspaceRoutingRules(ctx, env.ts, "test", func(ctx context.Context, existing *map[string]string) error { + for key, value := range rules { + (*existing)[key] = value + } + return nil + })) + + err = ts.deleteKeyspaceRoutingRules(ctx) + require.NoError(t, err) + + got, err := topotools.GetKeyspaceRoutingRules(ctx, env.ts) + require.NoError(t, err) + require.Equal(t, map[string]string{ + "otherks": "otherks", + }, got) +}