Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
88 changes: 83 additions & 5 deletions internal/cmd/branch/vtctld/move_tables.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func MoveTablesCmd(ch *cmdutil.Helper) *cobra.Command {
Short: "Manage MoveTables workflows",
}

cmd.AddCommand(MoveTablesListCmd(ch))
cmd.AddCommand(MoveTablesCreateCmd(ch))
cmd.AddCommand(MoveTablesShowCmd(ch))
cmd.AddCommand(MoveTablesStatusCmd(ch))
Expand Down Expand Up @@ -120,7 +121,9 @@ func MoveTablesCreateCmd(ch *cmdutil.Helper) *cobra.Command {
}

end()
return ch.Printer.PrettyPrintJSON(data)
return printWorkflowJSON(ch.Printer, data, []workflowNextStep{
moveTablesStatusStep(ch.Config.Organization, database, branch, flags.workflow, flags.targetKeyspace, "Monitor copy and replication progress"),
})
},
}

Expand Down Expand Up @@ -151,6 +154,50 @@ func MoveTablesCreateCmd(ch *cmdutil.Helper) *cobra.Command {
return cmd
}

func MoveTablesListCmd(ch *cmdutil.Helper) *cobra.Command {
var flags struct {
targetKeyspace string
}

cmd := &cobra.Command{
Use: "list <database> <branch>",
Short: "List MoveTables workflows",
Aliases: []string{"ls"},
Args: cmdutil.RequiredArgs("database", "branch"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
database, branch := args[0], args[1]

client, err := ch.Client()
if err != nil {
return err
}

end := ch.Printer.PrintProgress(
fmt.Sprintf("Fetching MoveTables workflows on %s\u2026",
progressTarget(ch.Config.Organization, database, branch)))
defer end()

data, err := client.MoveTables.List(ctx, &ps.MoveTablesListRequest{
Organization: ch.Config.Organization,
Database: database,
Branch: branch,
TargetKeyspace: flags.targetKeyspace,
})
if err != nil {
return cmdutil.HandleError(err)
}

end()
return printMoveTablesListJSON(ch.Printer, data, ch.Config.Organization, database, branch)
},
}

cmd.Flags().StringVar(&flags.targetKeyspace, "target-keyspace", "", "Target keyspace (defaults to the branch's default keyspace)")

return cmd
}

func MoveTablesShowCmd(ch *cmdutil.Helper) *cobra.Command {
var flags struct {
workflow string
Expand Down Expand Up @@ -187,7 +234,9 @@ func MoveTablesShowCmd(ch *cmdutil.Helper) *cobra.Command {
}

end()
return ch.Printer.PrettyPrintJSON(data)
return printWorkflowJSON(ch.Printer, data, []workflowNextStep{
moveTablesStatusStep(ch.Config.Organization, database, branch, flags.workflow, flags.targetKeyspace, "Check workflow copy and traffic state"),
})
},
}

Expand Down Expand Up @@ -235,7 +284,11 @@ func MoveTablesStatusCmd(ch *cmdutil.Helper) *cobra.Command {
}

end()
return ch.Printer.PrettyPrintJSON(data)
return printWorkflowJSON(
ch.Printer,
data,
moveTablesStatusNextSteps(data, ch.Config.Organization, database, branch, flags.workflow, flags.targetKeyspace),
)
},
}

Expand Down Expand Up @@ -305,7 +358,9 @@ func MoveTablesSwitchTrafficCmd(ch *cmdutil.Helper) *cobra.Command {
}

end()
return ch.Printer.PrettyPrintJSON(data)
return printWorkflowJSON(ch.Printer, data, []workflowNextStep{
moveTablesStatusStep(ch.Config.Organization, database, branch, flags.workflow, flags.targetKeyspace, "Confirm the new traffic state"),
})
},
}

Expand Down Expand Up @@ -376,7 +431,9 @@ func MoveTablesReverseTrafficCmd(ch *cmdutil.Helper) *cobra.Command {
}

end()
return ch.Printer.PrettyPrintJSON(data)
return printWorkflowJSON(ch.Printer, data, []workflowNextStep{
moveTablesStatusStep(ch.Config.Organization, database, branch, flags.workflow, flags.targetKeyspace, "Confirm the new traffic state"),
})
},
}

Expand Down Expand Up @@ -519,6 +576,27 @@ func MoveTablesCompleteCmd(ch *cmdutil.Helper) *cobra.Command {
}

end()
if flags.dryRun {
nextFlags := []string{
fmt.Sprintf("--keep-data=%t", flags.keepData),
fmt.Sprintf("--keep-routing-rules=%t", flags.keepRoutingRules),
}
if flags.renameTables {
nextFlags = append(nextFlags, "--rename-tables")
}
return printWorkflowJSON(ch.Printer, data, []workflowNextStep{{
Command: moveTablesCommand(
ch.Config.Organization,
"complete",
database,
branch,
flags.workflow,
flags.targetKeyspace,
nextFlags...,
),
Reason: "Complete the workflow after reviewing the dry run and getting operator approval",
}})
}
return ch.Printer.PrettyPrintJSON(data)
},
}
Expand Down
176 changes: 171 additions & 5 deletions internal/cmd/branch/vtctld/move_tables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,13 @@ func TestMoveTablesCreate(t *testing.T) {
c.Assert(err, qt.IsNil)
c.Assert(svc.CreateFnInvoked, qt.IsTrue)
c.Assert(vtctldSvc.GetOperationFnInvoked, qt.IsTrue)
c.Assert(buf.String(), qt.JSONEquals, map[string]string{"summary": "created"})
c.Assert(buf.String(), qt.JSONEquals, map[string]any{
"summary": "created",
"next_steps": []any{map[string]any{
"command": "pscale branch vtctld move-tables status my-db my-branch --org my-org --workflow my-workflow --target-keyspace target-ks --format json",
"reason": "Monitor copy and replication progress",
}},
})
}

func TestMoveTablesCreateWithDeferSecondaryKeysFalse(t *testing.T) {
Expand Down Expand Up @@ -226,7 +232,13 @@ func TestMoveTablesCreateWithAllFlags(t *testing.T) {
c.Assert(err, qt.IsNil)
c.Assert(svc.CreateFnInvoked, qt.IsTrue)
c.Assert(vtctldSvc.GetOperationFnInvoked, qt.IsTrue)
c.Assert(buf.String(), qt.JSONEquals, map[string]string{"summary": "created"})
c.Assert(buf.String(), qt.JSONEquals, map[string]any{
"summary": "created",
"next_steps": []any{map[string]any{
"command": "pscale branch vtctld move-tables status my-db my-branch --org my-org --workflow my-workflow --target-keyspace target-ks --format json",
"reason": "Monitor copy and replication progress",
}},
})
}

func TestMoveTablesSwitchTrafficWithMaxLag(t *testing.T) {
Expand Down Expand Up @@ -286,7 +298,13 @@ func TestMoveTablesSwitchTrafficWithMaxLag(t *testing.T) {
c.Assert(svc.SwitchTrafficFnInvoked, qt.IsTrue)
c.Assert(vtctldSvc.GetOperationFnInvoked, qt.IsTrue)
c.Assert(getOperationCalls, qt.Equals, 2)
c.Assert(buf.String(), qt.JSONEquals, map[string]string{"summary": "switched"})
c.Assert(buf.String(), qt.JSONEquals, map[string]any{
"summary": "switched",
"next_steps": []any{map[string]any{
"command": "pscale branch vtctld move-tables status my-db my-branch --org my-org --workflow my-workflow --target-keyspace target-ks --format json",
"reason": "Confirm the new traffic state",
}},
})
}

func TestMoveTablesSwitchTrafficRequiresTabletTypes(t *testing.T) {
Expand Down Expand Up @@ -361,7 +379,13 @@ func TestMoveTablesReverseTrafficWithFlags(t *testing.T) {
c.Assert(err, qt.IsNil)
c.Assert(svc.ReverseTrafficFnInvoked, qt.IsTrue)
c.Assert(vtctldSvc.GetOperationFnInvoked, qt.IsTrue)
c.Assert(buf.String(), qt.JSONEquals, map[string]string{"summary": "reversed"})
c.Assert(buf.String(), qt.JSONEquals, map[string]any{
"summary": "reversed",
"next_steps": []any{map[string]any{
"command": "pscale branch vtctld move-tables status my-db my-branch --org my-org --workflow my-workflow --target-keyspace target-ks --format json",
"reason": "Confirm the new traffic state",
}},
})
}

func TestMoveTablesCompleteWithFlags(t *testing.T) {
Expand Down Expand Up @@ -421,7 +445,13 @@ func TestMoveTablesCompleteWithFlags(t *testing.T) {
c.Assert(err, qt.IsNil)
c.Assert(svc.CompleteFnInvoked, qt.IsTrue)
c.Assert(vtctldSvc.GetOperationFnInvoked, qt.IsTrue)
c.Assert(buf.String(), qt.JSONEquals, map[string]string{"summary": "completed"})
c.Assert(buf.String(), qt.JSONEquals, map[string]any{
"summary": "completed",
"next_steps": []any{map[string]any{
"command": "pscale branch vtctld move-tables complete my-db my-branch --org my-org --workflow my-workflow --target-keyspace target-ks --keep-data=true --keep-routing-rules=true --rename-tables --format json",
"reason": "Complete the workflow after reviewing the dry run and getting operator approval",
}},
})
}

func TestMoveTablesSwitchTrafficOperationFailure(t *testing.T) {
Expand Down Expand Up @@ -556,6 +586,94 @@ func TestMoveTablesCancelWithFlags(t *testing.T) {
c.Assert(buf.String(), qt.JSONEquals, map[string]string{"summary": "cancelled"})
}

func TestMoveTablesList(t *testing.T) {
c := qt.New(t)

org := "my-org"
db := "my-db"
branch := "my-branch"

svc := &mock.MoveTablesService{
ListFn: func(ctx context.Context, req *ps.MoveTablesListRequest) (json.RawMessage, error) {
c.Assert(req.Organization, qt.Equals, org)
c.Assert(req.Database, qt.Equals, db)
c.Assert(req.Branch, qt.Equals, branch)
c.Assert(req.TargetKeyspace, qt.Equals, "target-ks")
return json.RawMessage(`{"workflows":[{"name":"my-workflow","target":{"keyspace":"target-ks"}}]}`), nil
},
}

var buf bytes.Buffer
ch := moveTablesTestHelper(org, svc, nil, &buf)

cmd := MoveTablesCmd(ch)
cmd.SetArgs([]string{"list", db, branch, "--target-keyspace", "target-ks"})
err := cmd.Execute()
c.Assert(err, qt.IsNil)
c.Assert(svc.ListFnInvoked, qt.IsTrue)
c.Assert(buf.String(), qt.JSONEquals, map[string]any{
"workflows": []any{map[string]any{
"name": "my-workflow",
"target": map[string]any{"keyspace": "target-ks"},
"next_steps": []any{map[string]any{
"command": "pscale branch vtctld move-tables status my-db my-branch --org my-org --workflow my-workflow --target-keyspace target-ks --format json",
"reason": "Check workflow copy and traffic state",
}},
}},
})
}

func TestMoveTablesListLeavesEntriesWithoutTargetAlone(t *testing.T) {
c := qt.New(t)

org := "my-org"
db := "my-db"
branch := "my-branch"

svc := &mock.MoveTablesService{
ListFn: func(ctx context.Context, req *ps.MoveTablesListRequest) (json.RawMessage, error) {
return json.RawMessage(`[{"name":"my-workflow","target_keyspace":null}]`), nil
},
}

var buf bytes.Buffer
ch := moveTablesTestHelper(org, svc, nil, &buf)

cmd := MoveTablesCmd(ch)
cmd.SetArgs([]string{"list", db, branch})
err := cmd.Execute()
c.Assert(err, qt.IsNil)
c.Assert(buf.String(), qt.JSONEquals, []any{map[string]any{
"name": "my-workflow",
"target_keyspace": nil,
}})
}

func TestMoveTablesListWithoutTargetKeyspace(t *testing.T) {
c := qt.New(t)

org := "my-org"
db := "my-db"
branch := "my-branch"

svc := &mock.MoveTablesService{
ListFn: func(ctx context.Context, req *ps.MoveTablesListRequest) (json.RawMessage, error) {
c.Assert(req.TargetKeyspace, qt.Equals, "")
return json.RawMessage(`[]`), nil
},
}

var buf bytes.Buffer
ch := moveTablesTestHelper(org, svc, nil, &buf)

cmd := MoveTablesCmd(ch)
cmd.SetArgs([]string{"list", db, branch})
err := cmd.Execute()
c.Assert(err, qt.IsNil)
c.Assert(svc.ListFnInvoked, qt.IsTrue)
c.Assert(buf.String(), qt.JSONEquals, []any{})
}

func TestMoveTablesShow(t *testing.T) {
c := qt.New(t)

Expand Down Expand Up @@ -598,3 +716,51 @@ func TestMoveTablesShow(t *testing.T) {
c.Assert(err, qt.IsNil)
c.Assert(svc.ShowFnInvoked, qt.IsTrue)
}

func TestMoveTablesStatusAddsNextSteps(t *testing.T) {
c := qt.New(t)

org := "my-org"
db := "my-db"
branch := "my-branch"

svc := &mock.MoveTablesService{
StatusFn: func(ctx context.Context, req *ps.MoveTablesStatusRequest) (json.RawMessage, error) {
c.Assert(req.Organization, qt.Equals, org)
c.Assert(req.Database, qt.Equals, db)
c.Assert(req.Branch, qt.Equals, branch)
c.Assert(req.Workflow, qt.Equals, "my-workflow")
c.Assert(req.TargetKeyspace, qt.Equals, "target-ks")
return json.RawMessage(`{"table_copy_state":{},"traffic_state":"All Reads Switched. Writes Not Switched"}`), nil
},
}

var buf bytes.Buffer
ch := moveTablesTestHelper(org, svc, nil, &buf)

cmd := MoveTablesCmd(ch)
cmd.SetArgs([]string{
"status",
db,
branch,
"--workflow",
"my-workflow",
"--target-keyspace",
"target-ks",
})
err := cmd.Execute()

c.Assert(err, qt.IsNil)
c.Assert(svc.StatusFnInvoked, qt.IsTrue)
// The vtctld fields stay in the order the API returned them, with next_steps
// appended after them.
c.Assert(buf.String(), qt.Matches, `(?s).*"table_copy_state".*"traffic_state".*"next_steps".*`)
c.Assert(buf.String(), qt.JSONEquals, map[string]any{
"table_copy_state": map[string]any{},
"traffic_state": "All Reads Switched. Writes Not Switched",
"next_steps": []any{map[string]any{
"command": "pscale branch vtctld move-tables switch-traffic my-db my-branch --org my-org --workflow my-workflow --target-keyspace target-ks --tablet-types PRIMARY --format json",
"reason": "Switch primary traffic after validating replica traffic",
}},
})
}
12 changes: 10 additions & 2 deletions internal/cmd/branch/vtctld/vdiff.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,11 @@ func VDiffCreateCmd(ch *cmdutil.Helper) *cobra.Command {
}

end()
return ch.Printer.PrettyPrintJSON(data)
return printWorkflowJSON(
ch.Printer,
data,
vdiffCreateNextSteps(data, ch.Config.Organization, database, branch, flags.workflow, flags.targetKeyspace),
)
},
}

Expand Down Expand Up @@ -218,7 +222,11 @@ func VDiffShowCmd(ch *cmdutil.Helper) *cobra.Command {
}

end()
return ch.Printer.PrettyPrintJSON(data)
return printWorkflowJSON(
ch.Printer,
data,
vdiffShowNextSteps(data, ch.Config.Organization, database, branch, flags.workflow, flags.targetKeyspace, flags.uuid),
)
},
}

Expand Down
Loading