From aeb4cdd084648e7c55adf5710b0d964a72d0add1 Mon Sep 17 00:00:00 2001 From: James Chacon Date: Thu, 16 Jun 2022 10:18:39 -0700 Subject: [PATCH] Implement fdbcli missing commands (#137) * Add missing fdbcli commands (10) Realized that transaction isn't needed as it's own message. Instead we simply take one or more commands and then chain them together with exec. Implement begin and commit so transactions will work in this fashion. Fix kill (it has options I missed). Add tests for everything. * Add a multi-command test * Refactor to account for N commands separated by semicolon and new Request structure. Also fix Kill since we changed it's proto struct as well. TODO: Remaining new commands and some sanity testing for N commands. * regen * Implement all the remaining missing CLI commands. Standardize usage Fix help/flags/commands to work correctly. Rename fdbcli help to fdbclihelp as we have a conflict otherwise. * Make sure for basic top level help/flags/commands we still pass an execute state. * Fix tabwriter so the output actually looks good when we're nested a bit. * Update reserved syntax to actual proto3 style * Add default cases for internal oneof's so we error if one gets added we don't handle * Remove transaction. it's no longer a distinct message * Rewrite parse loop to be a lot cleaner --- client/client.go | 2 +- cmd/sanssh/client/client.go | 2 +- services/fdb/client/client.go | 1607 +++-- services/fdb/fdb.pb.go | 9675 +++++++++++++++++++--------- services/fdb/fdb.proto | 233 +- services/fdb/fdb_grpc.pb.go | 4 - services/fdb/server/fdbcli.go | 412 +- services/fdb/server/fdbcli_test.go | 2232 +++++-- 8 files changed, 10019 insertions(+), 4148 deletions(-) diff --git a/client/client.go b/client/client.go index 954ebfdd..0e023c24 100644 --- a/client/client.go +++ b/client/client.go @@ -44,7 +44,7 @@ func SetupSubpackage(name string, f *flag.FlagSet) *subcommands.Commander { // unless you're a subcommand of a subcommand and then you'll want more. func GenerateSynopsis(c *subcommands.Commander, leading int) string { b := &bytes.Buffer{} - w := tabwriter.NewWriter(b, 2, 8, 2, '\t', 0) + w := tabwriter.NewWriter(b, 8, 0, 2, ' ', 0) w.Write([]byte("\n")) fn := func(c *subcommands.CommandGroup, comm subcommands.Command) { switch comm.Name() { diff --git a/cmd/sanssh/client/client.go b/cmd/sanssh/client/client.go index 171a7029..d79dfb95 100644 --- a/cmd/sanssh/client/client.go +++ b/cmd/sanssh/client/client.go @@ -77,7 +77,7 @@ func Run(ctx context.Context, rs RunState) { for _, f := range flag.Args() { switch f { case "help", "flags", "commands": - os.Exit(int(subcommands.Execute(ctx))) + os.Exit(int(subcommands.Execute(ctx, &util.ExecuteState{}))) } } diff --git a/services/fdb/client/client.go b/services/fdb/client/client.go index c001949a..1fa07155 100644 --- a/services/fdb/client/client.go +++ b/services/fdb/client/client.go @@ -122,15 +122,23 @@ const fdbCLIPackage = "fdbcli" func setupFDBCLI(f *flag.FlagSet) *subcommands.Commander { c := client.SetupSubpackage(fdbCLIPackage, f) c.Register(&fdbCLIAdvanceversionCmd{}, "") + c.Register(&fdbCLIBeginCmd{}, "") + c.Register(&fdbCLIBlobrangeCmd{}, "") + c.Register(&fdbCLICacheRangeCmd{}, "") + c.Register(&fdbCLIChangefeedCmd{}, "") + c.Register(&fdbCLIClearrangeCmd{}, "") c.Register(&fdbCLIClearCmd{}, "") c.Register(&fdbCLIClearrangeCmd{}, "") + c.Register(&fdbCLICommitCmd{}, "") c.Register(&fdbCLIConfigureCmd{}, "") c.Register(&fdbCLIConsistencycheckCmd{}, "") c.Register(&fdbCLICoordinatorsCmd{}, "") c.Register(&fdbCLICreatetenantCmd{}, "") + c.Register(&fdbCLIDatadistributionCmd{}, "") c.Register(&fdbCLIDefaulttenantCmd{}, "") c.Register(&fdbCLIDeletetenantCmd{}, "") c.Register(&fdbCLIExcludeCmd{}, "") + c.Register(&fdbCLIExpensiveDataCheckCmd{}, "") c.Register(&fdbCLIFileconfigureCmd{}, "") c.Register(&fdbCLIForceRecoveryWithDataLossCmd{}, "") c.Register(&fdbCLIGetCmd{}, "") @@ -149,13 +157,18 @@ func setupFDBCLI(f *flag.FlagSet) *subcommands.Commander { c.Register(&fdbCLISetCmd{}, "") c.Register(&fdbCLISetclassCmd{}, "") c.Register(&fdbCLISleepCmd{}, "") + c.Register(&fdbCLISnapshotCmd{}, "") c.Register(&fdbCLIStatusCmd{}, "") + c.Register(&fdbCLISuspendCmd{}, "") c.Register(&fdbCLIThrottleCmd{}, "") c.Register(&fdbCLITriggerddteaminfologCmd{}, "") + c.Register(&fdbCLITssqCmd{}, "") c.Register(&fdbCLIUnlockCmd{}, "") c.Register(&fdbCLIUsetenantCmd{}, "") c.Register(&fdbCLIWritemodeCmd{}, "") - c.Register(&fdbCLITssqCmd{}, "") + c.Register(&fdbCLIVersionepochCmd{}, "") + c.Register(&fdbCLIWaitconnectedCmd{}, "") + c.Register(&fdbCLIWaitopenCmd{}, "") return c } @@ -298,36 +311,63 @@ func (r *fdbCLICmd) SetFlags(f *flag.FlagSet) { }) } -// Execute acts like the top level metadataCmd does and delegates actual execution to the parsed sub-command. +// Execute acts like the top level fdbCmd does and delegates actual execution to the parsed sub-command. // It does pass along the top level request since any top level flags need to be filled in there and available // for the final request. Sub commands will implement the specific oneof Command and any command specific flags. func (r *fdbCLICmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { + c := setupFDBCLI(f) + // If it's just asking for flags or commands run those and exit. Help is special since we have that + // as its own subcommand which executes remotely. + if f.Arg(0) == "flags" || f.Arg(0) == "commands" || f.Arg(0) == "help" { + return c.Execute(ctx, args...) + } + // We allow --exec and also just a trailing command/args to work. The former is to mimic how people are used // to doing this from the existing CLI so migration is simpler. - if f.NArg() < 1 { - if r.exec == "" { - fmt.Fprintln(os.Stderr, "Must specify a command after all options or with --exec") + if f.NArg() > 0 { + if r.exec != "" { + fmt.Fprintln(os.Stderr, "Must specify a command after all options or with --exec but not both") return subcommands.ExitFailure } + } + if r.exec != "" { // We want this to still use the parser so move the string from --exec in as Args to the flagset f = flag.NewFlagSet("", flag.ContinueOnError) args := strings.Fields(r.exec) f.Parse(args) } - c := setupFDBCLI(f) + + c = setupFDBCLI(f) + args = append(args, r.req) - return c.Execute(ctx, args...) + + // Join everything back into one string and then resplit around semi-colon. + // A command of fdbcli "kill; kill X" will parse through here one by one. + for _, a := range strings.Split(strings.Join(f.Args(), " "), ";") { + a = strings.TrimSpace(a) + f.Parse(strings.Split(a, " ")) + exit := c.Execute(ctx, args...) + if exit != subcommands.ExitSuccess { + fmt.Fprintln(os.Stderr, "Error parsing command") + return exit + } + } + + state := args[0].(*util.ExecuteState) + return r.runFDBCLI(ctx, state) } // runFDBCLI does the legwork for final RPC exection since for each command this is the same. // Send the request and process the response stream. The only difference being the command to name // in error messages. -func runFDBCLI(ctx context.Context, c pb.CLIClientProxy, state *util.ExecuteState, req *pb.FDBCLIRequest, command string) subcommands.ExitStatus { - stream, err := c.FDBCLIOneMany(ctx, req) +func (r *fdbCLICmd) runFDBCLI(ctx context.Context, state *util.ExecuteState) subcommands.ExitStatus { + c := pb.NewCLIClientProxy(state.Conn) + + stream, err := c.FDBCLIOneMany(ctx, r.req) if err != nil { // Emit this to every error file as it's not specific to a given target. for _, e := range state.Err { - fmt.Fprintf(e, "All targets - fdbcli %s error: %v\n", command, err) + fmt.Fprintf(e, "All targets - fdbcli error: %v\n", err) } return subcommands.ExitFailure } @@ -349,7 +389,7 @@ func runFDBCLI(ctx context.Context, c pb.CLIClientProxy, state *util.ExecuteStat } for _, r := range resp { if r.Error != nil { - fmt.Fprintf(state.Err[r.Index], "fdbcli %s error: %v\n", command, r.Error) + fmt.Fprintf(state.Err[r.Index], "fdbcli error: %v\n", r.Error) // If any target had errors it needs to be reported for that target but we still // need to process responses off the channel. Final return code though should // indicate something failed. @@ -380,6 +420,16 @@ func runFDBCLI(ctx context.Context, c pb.CLIClientProxy, state *util.ExecuteStat return retCode } +// anyEmpty will return true if any entry in the passed in slice is an empty string. +func anyEmpty(s []string) bool { + for _, e := range s { + if e == "" { + return true + } + } + return false +} + type fdbCLIAdvanceversionCmd struct { req *pb.FDBCLIAdvanceversion } @@ -388,7 +438,7 @@ func (*fdbCLIAdvanceversionCmd) Name() string { return "advanceversion" } func (*fdbCLIAdvanceversionCmd) Synopsis() string { return "Force the cluster to recover at the specified version" } -func (p *fdbCLIAdvanceversionCmd) Usage() string { +func (*fdbCLIAdvanceversionCmd) Usage() string { return "advanceversion " } @@ -397,13 +447,10 @@ func (r *fdbCLIAdvanceversionCmd) SetFlags(f *flag.FlagSet) { } func (r *fdbCLIAdvanceversionCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - state := args[0].(*util.ExecuteState) req := args[1].(*pb.FDBCLIRequest) - c := pb.NewCLIClientProxy(state.Conn) - if f.NArg() != 1 { - fmt.Fprintln(os.Stderr, "must specify one version") + fmt.Fprintln(os.Stderr, "usage: ", r.Usage()) return subcommands.ExitFailure } @@ -414,15 +461,292 @@ func (r *fdbCLIAdvanceversionCmd) Execute(ctx context.Context, f *flag.FlagSet, } r.req.Version = v - req.Request = &pb.FDBCLIRequest_Command{ - Command: &pb.FDBCLICommand{ + req.Commands = append(req.Commands, + &pb.FDBCLICommand{ Command: &pb.FDBCLICommand_Advanceversion{ Advanceversion: r.req, }, - }, + }) + + return subcommands.ExitSuccess +} + +type fdbCLIBeginCmd struct { + req *pb.FDBCLIBegin +} + +func (*fdbCLIBeginCmd) Name() string { return "begin" } +func (*fdbCLIBeginCmd) Synopsis() string { + return "Begin a new transaction" +} +func (*fdbCLIBeginCmd) Usage() string { + return "begin" +} + +func (r *fdbCLIBeginCmd) SetFlags(f *flag.FlagSet) { + r.req = &pb.FDBCLIBegin{} +} + +func (r *fdbCLIBeginCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { + req := args[1].(*pb.FDBCLIRequest) + + if f.NArg() != 0 { + fmt.Fprintln(os.Stderr, "usage: ", r.Usage()) + return subcommands.ExitFailure + } - return runFDBCLI(ctx, c, state, req, "advanceversion") + req.Commands = append(req.Commands, + &pb.FDBCLICommand{ + Command: &pb.FDBCLICommand_Begin{ + Begin: r.req, + }, + }) + + return subcommands.ExitSuccess +} + +type fdbCLIBlobrangeCmd struct { + req *pb.FDBCLIBlobrange +} + +func (*fdbCLIBlobrangeCmd) Name() string { return "blobrange" } +func (*fdbCLIBlobrangeCmd) Synopsis() string { + return "blobify (or not) a given key range" +} +func (*fdbCLIBlobrangeCmd) Usage() string { + return "blobrange " +} + +func (r *fdbCLIBlobrangeCmd) SetFlags(f *flag.FlagSet) { + r.req = &pb.FDBCLIBlobrange{} +} + +func (r *fdbCLIBlobrangeCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { + req := args[1].(*pb.FDBCLIRequest) + + if f.NArg() != 3 || anyEmpty(f.Args()) { + fmt.Fprintln(os.Stderr, "usage: ", r.Usage()) + return subcommands.ExitFailure + } + + switch f.Arg(0) { + case "start": + r.req.Request = &pb.FDBCLIBlobrange_Start{ + Start: &pb.FDBCLIBlobrangeStart{ + BeginKey: f.Arg(1), + EndKey: f.Arg(2), + }, + } + case "stop": + r.req.Request = &pb.FDBCLIBlobrange_Stop{ + Stop: &pb.FDBCLIBlobrangeStop{ + BeginKey: f.Arg(1), + EndKey: f.Arg(2), + }, + } + } + req.Commands = append(req.Commands, + &pb.FDBCLICommand{ + Command: &pb.FDBCLICommand_Blobrange{ + Blobrange: r.req, + }, + }) + + return subcommands.ExitSuccess +} + +type fdbCLICacheRangeCmd struct { + req *pb.FDBCLICacheRange +} + +func (*fdbCLICacheRangeCmd) Name() string { return "cacherange" } +func (*fdbCLICacheRangeCmd) Synopsis() string { + return "Mark a key range to add to or remove from storage caches." +} +func (*fdbCLICacheRangeCmd) Usage() string { + return "cacherange " +} + +func (r *fdbCLICacheRangeCmd) SetFlags(f *flag.FlagSet) { + r.req = &pb.FDBCLICacheRange{} +} + +func (r *fdbCLICacheRangeCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { + req := args[1].(*pb.FDBCLIRequest) + + if f.NArg() != 3 || anyEmpty(f.Args()) { + fmt.Fprintln(os.Stderr, "usage: ", r.Usage()) + return subcommands.ExitFailure + } + + switch f.Arg(0) { + case "set": + r.req.Request = &pb.FDBCLICacheRange_Set{ + Set: &pb.FDBCLICacheRangeSet{ + BeginKey: f.Arg(1), + EndKey: f.Arg(2), + }, + } + case "clear": + r.req.Request = &pb.FDBCLICacheRange_Clear{ + Clear: &pb.FDBCLICacheRangeClear{ + BeginKey: f.Arg(1), + EndKey: f.Arg(2), + }, + } + } + req.Commands = append(req.Commands, + &pb.FDBCLICommand{ + Command: &pb.FDBCLICommand_CacheRange{ + CacheRange: r.req, + }, + }) + + return subcommands.ExitSuccess +} + +type fdbCLIChangefeedCmd struct { + req *pb.FDBCLIChangefeed +} + +func (*fdbCLIChangefeedCmd) Name() string { return "changefeed" } +func (*fdbCLIChangefeedCmd) Synopsis() string { + return "List, setup, destroy or stream changefeeds" +} +func (p *fdbCLIChangefeedCmd) Usage() string { + return "changefeed |destroy |stop |stream [start_version [end_version]]|pop " +} + +func (r *fdbCLIChangefeedCmd) SetFlags(f *flag.FlagSet) { + r.req = &pb.FDBCLIChangefeed{} +} + +func (r *fdbCLIChangefeedCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { + req := args[1].(*pb.FDBCLIRequest) + + if f.NArg() == 0 || f.Arg(0) == "" { + fmt.Fprintln(os.Stderr, "usage: ", r.Usage()) + return subcommands.ExitFailure + } + + usage := true + switch f.Arg(0) { + case "list": + if f.NArg() != 1 { + break + } + usage = false + r.req.Request = &pb.FDBCLIChangefeed_List{ + List: &pb.FDBCLIChangefeedList{}, + } + case "register": + if f.NArg() != 4 || anyEmpty(f.Args()) { + break + } + usage = false + r.req.Request = &pb.FDBCLIChangefeed_Register{ + Register: &pb.FDBCLIChangefeedRegister{ + RangeId: f.Arg(1), + Begin: f.Arg(2), + End: f.Arg(3), + }, + } + case "stop": + if f.NArg() != 2 || anyEmpty(f.Args()) { + break + } + usage = false + r.req.Request = &pb.FDBCLIChangefeed_Stop{ + Stop: &pb.FDBCLIChangefeedStop{ + RangeId: f.Arg(1), + }, + } + case "destroy": + if f.NArg() != 2 || anyEmpty(f.Args()) { + break + } + usage = false + r.req.Request = &pb.FDBCLIChangefeed_Destroy{ + Destroy: &pb.FDBCLIChangefeedDestroy{ + RangeId: f.Arg(1), + }, + } + case "stream": + if f.NArg() < 2 || f.NArg() > 4 || anyEmpty(f.Args()) { + break + } + var s, e int64 + var err error + if f.NArg() > 2 { + s, err = strconv.ParseInt(f.Arg(2), 10, 64) + if err != nil { + fmt.Fprintf(os.Stderr, "can't parse start version: %v\n", err) + break + } + } + if f.NArg() > 3 { + e, err = strconv.ParseInt(f.Arg(2), 10, 64) + if err != nil { + fmt.Fprintf(os.Stderr, "can't parse end version: %v\n", err) + break + } + } + usage = false + r.req.Request = &pb.FDBCLIChangefeed_Stream{ + Stream: &pb.FDBCLIChangefeedStream{ + RangeId: f.Arg(1), + }, + } + switch f.NArg() { + case 3: + r.req.GetStream().Type = &pb.FDBCLIChangefeedStream_StartVersion{ + StartVersion: &pb.FDBCLIChangefeedStreamStartVersion{ + StartVersion: s, + }, + } + case 4: + r.req.GetStream().Type = &pb.FDBCLIChangefeedStream_StartEndVersion{ + StartEndVersion: &pb.FDBCLIChangefeedStreamStartEndVersion{ + StartVersion: s, + EndVersion: e, + }, + } + default: + r.req.GetStream().Type = &pb.FDBCLIChangefeedStream_NoVersion{ + NoVersion: &pb.FDBCLIChangefeedStreamNoVersion{}, + } + } + case "pop": + if f.NArg() != 3 || anyEmpty(f.Args()) { + break + } + v, err := strconv.ParseInt(f.Arg(1), 10, 64) + if err != nil { + fmt.Fprintf(os.Stderr, "can't parse version: %v\n", err) + break + } + usage = false + r.req.Request = &pb.FDBCLIChangefeed_Pop{ + Pop: &pb.FDBCLIChangefeedStreamPop{ + RangeId: f.Arg(1), + Version: v, + }, + } + } + if usage { + fmt.Fprintln(os.Stderr, "usage: ", r.Usage()) + return subcommands.ExitFailure + } + + req.Commands = append(req.Commands, + &pb.FDBCLICommand{ + Command: &pb.FDBCLICommand_Changefeed{ + Changefeed: r.req, + }, + }) + + return subcommands.ExitSuccess } type fdbCLIClearCmd struct { @@ -442,27 +766,22 @@ func (r *fdbCLIClearCmd) SetFlags(f *flag.FlagSet) { } func (r *fdbCLIClearCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - state := args[0].(*util.ExecuteState) req := args[1].(*pb.FDBCLIRequest) - c := pb.NewCLIClientProxy(state.Conn) - if f.NArg() != 1 || f.Arg(0) == "" { - fmt.Fprintln(os.Stderr, "must specify a key") + fmt.Fprintln(os.Stderr, "usage: ", r.Usage()) return subcommands.ExitFailure - } r.req.Key = f.Arg(0) - req.Request = &pb.FDBCLIRequest_Command{ - Command: &pb.FDBCLICommand{ + req.Commands = append(req.Commands, + &pb.FDBCLICommand{ Command: &pb.FDBCLICommand_Clear{ Clear: r.req, }, - }, - } + }) - return runFDBCLI(ctx, c, state, req, "clear") + return subcommands.ExitSuccess } type fdbCLIClearrangeCmd struct { @@ -482,28 +801,57 @@ func (r *fdbCLIClearrangeCmd) SetFlags(f *flag.FlagSet) { } func (r *fdbCLIClearrangeCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - state := args[0].(*util.ExecuteState) req := args[1].(*pb.FDBCLIRequest) - c := pb.NewCLIClientProxy(state.Conn) - - if f.NArg() != 2 || f.Arg(0) == "" || f.Arg(1) == "" { - fmt.Fprintln(os.Stderr, "must specify a begin key and an end key") + if f.NArg() != 2 || anyEmpty(f.Args()) { + fmt.Fprintln(os.Stderr, "usage: ", r.Usage()) return subcommands.ExitFailure - } r.req.BeginKey = f.Arg(0) r.req.EndKey = f.Arg(1) - req.Request = &pb.FDBCLIRequest_Command{ - Command: &pb.FDBCLICommand{ + req.Commands = append(req.Commands, + &pb.FDBCLICommand{ Command: &pb.FDBCLICommand_Clearrange{ Clearrange: r.req, }, - }, + }) + + return subcommands.ExitSuccess +} + +type fdbCLICommitCmd struct { + req *pb.FDBCLICommit +} + +func (*fdbCLICommitCmd) Name() string { return "commit" } +func (*fdbCLICommitCmd) Synopsis() string { + return "Commit the current transaction" +} +func (p *fdbCLICommitCmd) Usage() string { + return "commit" +} + +func (r *fdbCLICommitCmd) SetFlags(f *flag.FlagSet) { + r.req = &pb.FDBCLICommit{} +} + +func (r *fdbCLICommitCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { + req := args[1].(*pb.FDBCLIRequest) + + if f.NArg() != 0 { + fmt.Fprintln(os.Stderr, "usage: ", r.Usage()) + return subcommands.ExitFailure } - return runFDBCLI(ctx, c, state, req, "clearrange") + req.Commands = append(req.Commands, + &pb.FDBCLICommand{ + Command: &pb.FDBCLICommand_Commit{ + Commit: r.req, + }, + }) + + return subcommands.ExitSuccess } type fdbCLIConfigureCmd struct { @@ -523,38 +871,42 @@ func (r *fdbCLIConfigureCmd) SetFlags(f *flag.FlagSet) { } func (r *fdbCLIConfigureCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - state := args[0].(*util.ExecuteState) req := args[1].(*pb.FDBCLIRequest) - c := pb.NewCLIClientProxy(state.Conn) - if f.NArg() == 0 { - fmt.Fprintln(os.Stderr, "Must supply at least one configure option") + fmt.Fprintln(os.Stderr, "usage: ", r.Usage()) return subcommands.ExitFailure } // Technically these have an order but can be parsed regardless of position. // The only real constraint is each is only set once possibly. + usage := true for _, opt := range f.Args() { switch opt { case "new", "tss": if r.req.NewOrTss != nil { fmt.Fprintln(os.Stderr, "new|tss can only be set once") + break } + usage = false r.req.NewOrTss = &wrapperspb.StringValue{ Value: opt, } case "single", "double", "triple", "three_data_hall", "three_datacenter": if r.req.RedundancyMode != nil { fmt.Fprintln(os.Stderr, "redundancy mode can only be set once") + break } + usage = false r.req.RedundancyMode = &wrapperspb.StringValue{ Value: opt, } case "ssd", "memory": if r.req.StorageEngine != nil { fmt.Fprintln(os.Stderr, "storage engine can only be set once") + break } + usage = false r.req.StorageEngine = &wrapperspb.StringValue{ Value: opt, } @@ -562,9 +914,9 @@ func (r *fdbCLIConfigureCmd) Execute(ctx context.Context, f *flag.FlagSet, args // Need to be K=V style kv := strings.SplitN(opt, "=", 2) // foo= will parse as 2 entries just the 2nd is blank. - if len(kv) != 2 || (len(kv) == 2 && kv[1] == "") { + if len(kv) != 2 || anyEmpty(kv) { fmt.Fprintf(os.Stderr, "can't parse configure option %q\n", opt) - return subcommands.ExitFailure + break } setUintVal := func(e string, val string, field **wrapperspb.UInt32Value) subcommands.ExitStatus { if *field != nil { @@ -584,69 +936,75 @@ func (r *fdbCLIConfigureCmd) Execute(ctx context.Context, f *flag.FlagSet, args switch kv[0] { case "grv_proxies": - if setUintVal(opt, kv[1], &r.req.GrvProxies) != subcommands.ExitSuccess { - return subcommands.ExitFailure + if setUintVal(opt, kv[1], &r.req.GrvProxies) == subcommands.ExitSuccess { + usage = false } case "commit_proxies": - if setUintVal(opt, kv[1], &r.req.CommitProxies) != subcommands.ExitSuccess { - return subcommands.ExitFailure + if setUintVal(opt, kv[1], &r.req.CommitProxies) == subcommands.ExitSuccess { + usage = false } case "resolvers": - if setUintVal(opt, kv[1], &r.req.Resolvers) != subcommands.ExitSuccess { - return subcommands.ExitFailure + if setUintVal(opt, kv[1], &r.req.Resolvers) == subcommands.ExitSuccess { + usage = false } case "logs": - if setUintVal(opt, kv[1], &r.req.Logs) != subcommands.ExitSuccess { - return subcommands.ExitFailure + if setUintVal(opt, kv[1], &r.req.Logs) == subcommands.ExitSuccess { + usage = false } case "count": - if setUintVal(opt, kv[1], &r.req.Count) != subcommands.ExitSuccess { - return subcommands.ExitFailure + if setUintVal(opt, kv[1], &r.req.Count) == subcommands.ExitSuccess { + usage = false } case "perpetual_storage_wiggle": - if setUintVal(opt, kv[1], &r.req.PerpetualStorageWiggle) != subcommands.ExitSuccess { - return subcommands.ExitFailure + if setUintVal(opt, kv[1], &r.req.PerpetualStorageWiggle) == subcommands.ExitSuccess { + usage = false } case "perpetual_storage_wiggle_locality": if r.req.PerpetualStorageWiggleLocality != nil { fmt.Fprintf(os.Stderr, "option %s already set\n", opt) - return subcommands.ExitFailure + break } + usage = false r.req.PerpetualStorageWiggleLocality = &wrapperspb.StringValue{ Value: kv[1], } case "storage_migration_type": if r.req.StorageMigrationType != nil { fmt.Fprintf(os.Stderr, "option %s already set\n", opt) - return subcommands.ExitFailure + break } + usage = false r.req.StorageMigrationType = &wrapperspb.StringValue{ Value: kv[1], } case "tenant_mode": if r.req.TenantMode != nil { fmt.Fprintf(os.Stderr, "option %s already set\n", opt) - return subcommands.ExitFailure + break } + usage = false r.req.TenantMode = &wrapperspb.StringValue{ Value: kv[1], } default: fmt.Fprintf(os.Stderr, "can't parse configure option %q\n", opt) - return subcommands.ExitFailure } } + + } + if usage { + fmt.Fprintln(os.Stderr, "usage: ", r.Usage()) + return subcommands.ExitFailure } - req.Request = &pb.FDBCLIRequest_Command{ - Command: &pb.FDBCLICommand{ + req.Commands = append(req.Commands, + &pb.FDBCLICommand{ Command: &pb.FDBCLICommand_Configure{ Configure: r.req, }, - }, - } + }) - return runFDBCLI(ctx, c, state, req, "configure") + return subcommands.ExitSuccess } type fdbCLIConsistencycheckCmd struct { @@ -666,39 +1024,32 @@ func (r *fdbCLIConsistencycheckCmd) SetFlags(f *flag.FlagSet) { } func (r *fdbCLIConsistencycheckCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - state := args[0].(*util.ExecuteState) req := args[1].(*pb.FDBCLIRequest) - c := pb.NewCLIClientProxy(state.Conn) - - if f.NArg() > 1 { - fmt.Fprintln(os.Stderr, "Only one option of either on or off can be specified") + if f.NArg() != 1 { + fmt.Fprintln(os.Stderr, "usage: ", r.Usage()) return subcommands.ExitFailure } - if f.NArg() == 1 { - switch f.Args()[0] { - case "on": - r.req.Mode = &wrapperspb.BoolValue{ - Value: true, - } - case "off": - r.req.Mode = &wrapperspb.BoolValue{} - default: - fmt.Fprintln(os.Stderr, "Only one option of either on or off can be specified") - return subcommands.ExitFailure + switch f.Arg(0) { + case "on": + r.req.Mode = &wrapperspb.BoolValue{ + Value: true, } - + case "off": + r.req.Mode = &wrapperspb.BoolValue{} + default: + fmt.Fprintln(os.Stderr, "Only one option of either on or off can be specified") + return subcommands.ExitFailure } - req.Request = &pb.FDBCLIRequest_Command{ - Command: &pb.FDBCLICommand{ + req.Commands = append(req.Commands, + &pb.FDBCLICommand{ Command: &pb.FDBCLICommand_Consistencycheck{ Consistencycheck: r.req, }, - }, - } + }) - return runFDBCLI(ctx, c, state, req, "consistencycheck") + return subcommands.ExitSuccess } type fdbCLICoordinatorsCmd struct { @@ -718,26 +1069,25 @@ func (r *fdbCLICoordinatorsCmd) SetFlags(f *flag.FlagSet) { } func (r *fdbCLICoordinatorsCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - state := args[0].(*util.ExecuteState) req := args[1].(*pb.FDBCLIRequest) - c := pb.NewCLIClientProxy(state.Conn) - if f.NArg() == 0 { - fmt.Fprintln(os.Stderr, "must specify auto or at least one address") + fmt.Fprintln(os.Stderr, "usage: ", r.Usage()) return subcommands.ExitFailure } - auto, desc := false, false + auto, desc, usage := false, false, false for _, opt := range f.Args() { if desc { fmt.Fprintln(os.Stderr, "description cannot be set more than once and must be last") - return subcommands.ExitFailure + usage = true + break } if opt == "auto" { if auto { fmt.Fprintln(os.Stderr, "auto can only be set once and not after addresses") - return subcommands.ExitFailure + usage = true + break } r.req.Request = &pb.FDBCLICoordinators_Auto{ Auto: &pb.FDBCLICoordinatorsAuto{}, @@ -748,12 +1098,14 @@ func (r *fdbCLICoordinatorsCmd) Execute(ctx context.Context, f *flag.FlagSet, ar if strings.HasPrefix(opt, "description=") { if desc { fmt.Fprintln(os.Stderr, "description can only be set once") - return subcommands.ExitFailure + usage = true + break } d := strings.SplitN(opt, "=", 2) - if len(d) != 2 || (len(d) == 2 && d[1] == "") { + if len(d) != 2 || anyEmpty(d) { fmt.Fprintln(os.Stderr, "description must be of the form description=X") - return subcommands.ExitFailure + usage = true + break } r.req.Description = &wrapperspb.StringValue{ Value: d[1], @@ -770,16 +1122,19 @@ func (r *fdbCLICoordinatorsCmd) Execute(ctx context.Context, f *flag.FlagSet, ar r.req.GetAddresses().Addresses = append(r.req.GetAddresses().Addresses, opt) auto = true } + if usage { + fmt.Fprintln(os.Stderr, "usage: ", r.Usage()) + return subcommands.ExitFailure + } - req.Request = &pb.FDBCLIRequest_Command{ - Command: &pb.FDBCLICommand{ + req.Commands = append(req.Commands, + &pb.FDBCLICommand{ Command: &pb.FDBCLICommand_Coordinators{ Coordinators: r.req, }, - }, - } + }) - return runFDBCLI(ctx, c, state, req, "coordinators") + return subcommands.ExitSuccess } type fdbCLICreatetenantCmd struct { @@ -799,26 +1154,79 @@ func (r *fdbCLICreatetenantCmd) SetFlags(f *flag.FlagSet) { } func (r *fdbCLICreatetenantCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { - state := args[0].(*util.ExecuteState) req := args[1].(*pb.FDBCLIRequest) - c := pb.NewCLIClientProxy(state.Conn) - if f.NArg() != 1 || f.Arg(0) == "" { - fmt.Fprintln(os.Stderr, "must specify a tenant name") + fmt.Fprintln(os.Stderr, "usage: ", r.Usage()) return subcommands.ExitFailure } r.req.Name = f.Arg(0) - req.Request = &pb.FDBCLIRequest_Command{ - Command: &pb.FDBCLICommand{ + req.Commands = append(req.Commands, + &pb.FDBCLICommand{ Command: &pb.FDBCLICommand_Createtenant{ Createtenant: r.req, }, - }, + }) + + return subcommands.ExitSuccess +} + +type fdbCLIDatadistributionCmd struct { + req *pb.FDBCLIDatadistribution +} + +func (*fdbCLIDatadistributionCmd) Name() string { return "datadistribution" } +func (*fdbCLIDatadistributionCmd) Synopsis() string { + return "Set data distribution state/modes" +} +func (p *fdbCLIDatadistributionCmd) Usage() string { + return "datadistribution |enable