You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kudu.apache.org by to...@apache.org on 2016/08/16 06:08:33 UTC

[2/4] kudu git commit: tool: rewrite parser logic

tool: rewrite parser logic

While leaf and non-leaf actions share some common properties, there is much
they don't share. Rather than shoehorn both into the same Action paradigm, I
think it makes more sense to consider them separately.

This patch splits Action into either Mode (non-leaf node) or Action (leaf
node). The common properties are now found in the Label struct.
Additionally, each kind of node is now structured as a class with proper
encapsulation, builders, and other goodies. Overall this simplifies the
command line parsing logic, hides more internal details, and reduces the
boilerplate needed to add a mode or an action.

There's no change to the tool's interface with the outside world.

Change-Id: I794fc527525a57283f0165e262283adf14160def
Reviewed-on: http://gerrit.cloudera.org:8080/3996
Tested-by: Kudu Jenkins
Reviewed-by: Todd Lipcon <to...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/kudu/repo
Commit: http://git-wip-us.apache.org/repos/asf/kudu/commit/5ef37438
Tree: http://git-wip-us.apache.org/repos/asf/kudu/tree/5ef37438
Diff: http://git-wip-us.apache.org/repos/asf/kudu/diff/5ef37438

Branch: refs/heads/master
Commit: 5ef374388cfe88e7efec26121a2fe02e7d32eba5
Parents: 401985e
Author: Adar Dembo <ad...@cloudera.com>
Authored: Sun Aug 14 22:13:04 2016 -0700
Committer: Todd Lipcon <to...@apache.org>
Committed: Tue Aug 16 05:57:18 2016 +0000

----------------------------------------------------------------------
 src/kudu/tools/tool_action.cc        |  89 +++++++++++----
 src/kudu/tools/tool_action.h         | 178 +++++++++++++++++++++++-------
 src/kudu/tools/tool_action_fs.cc     |  56 +++++-----
 src/kudu/tools/tool_action_tablet.cc |  92 ++++++++-------
 src/kudu/tools/tool_main.cc          | 118 ++++++++++----------
 5 files changed, 333 insertions(+), 200 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kudu/blob/5ef37438/src/kudu/tools/tool_action.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action.cc b/src/kudu/tools/tool_action.cc
index 4daf931..317c162 100644
--- a/src/kudu/tools/tool_action.cc
+++ b/src/kudu/tools/tool_action.cc
@@ -18,6 +18,7 @@
 #include "kudu/tools/tool_action.h"
 
 #include <deque>
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -26,35 +27,89 @@
 
 using std::deque;
 using std::string;
+using std::unique_ptr;
 using std::vector;
 using strings::Substitute;
 
 namespace kudu {
 namespace tools {
 
-string BuildActionChainString(const vector<Action>& chain) {
-  return JoinMapped(chain, [](const Action& a){ return a.name; }, " ");
+namespace {
+
+string BuildUsageString(const vector<Mode*>& chain) {
+  string modes = JoinMapped(chain, [](Mode* a){ return a->name(); }, " ");
+  return Substitute("Usage: $0", modes);
+}
+
+} // anonymous namespace
+
+ModeBuilder::ModeBuilder(const Label& label)
+    : label_(label) {
+}
+
+ModeBuilder& ModeBuilder::AddMode(unique_ptr<Mode> mode) {
+  submodes_.push_back(std::move(mode));
+  return *this;
 }
 
-string BuildUsageString(const vector<Action>& chain) {
-  return Substitute("Usage: $0", BuildActionChainString(chain));
+ModeBuilder& ModeBuilder::AddAction(unique_ptr<Action> action) {
+  actions_.push_back(std::move(action));
+  return *this;
 }
 
-string BuildHelpString(const vector<Action>& sub_actions, string usage_str) {
-  string msg = Substitute("$0 <action>\n", usage_str);
+unique_ptr<Mode> ModeBuilder::Build() {
+  unique_ptr<Mode> mode(new Mode());
+  mode->label_ = label_;
+  mode->submodes_ = std::move(submodes_);
+  mode->actions_ = std::move(actions_);
+  return mode;
+}
+
+// Get help for this mode, passing in its parent mode chain.
+string Mode::BuildHelp(const vector<Mode*>& chain) const {
+  string msg = Substitute("$0 <action>\n", BuildUsageString(chain));
   msg += "Action can be one of the following:\n";
-  for (const auto& a : sub_actions) {
-    msg += Substitute("  $0 : $1\n", a.name, a.description);
+  for (const auto& m : modes()) {
+    msg += Substitute("  $0 : $1\n", m->name(), m->description());
+  }
+  for (const auto& a : actions()) {
+    msg += Substitute("  $0 : $1\n", a->name(), a->description());
   }
   return msg;
 }
 
-string BuildLeafActionHelpString(const vector<Action>& chain) {
-  DCHECK(!chain.empty());
-  Action action = chain.back();
-  string msg = Substitute("$0", BuildUsageString(chain));
+Mode::Mode() {
+}
+
+ActionBuilder::ActionBuilder(const Label& label, const ActionRunner& runner)
+    : label_(label),
+      runner_(runner) {
+}
+
+ActionBuilder& ActionBuilder::AddGflag(const string& gflag) {
+  gflags_.push_back(gflag);
+  return *this;
+}
+
+unique_ptr<Action> ActionBuilder::Build() {
+  unique_ptr<Action> action(new Action());
+  action->label_ = label_;
+  action->runner_ = runner_;
+  action->gflags_ = gflags_;
+  return action;
+}
+
+Action::Action() {
+}
+
+Status Action::Run(const vector<Mode*>& chain, deque<string> args) const {
+  return runner_(chain, this, args);
+}
+
+string Action::BuildHelp(const vector<Mode*>& chain) const {
+  string msg = Substitute("$0 $1", BuildUsageString(chain), name());
   string gflags_msg;
-  for (const auto& gflag : action.gflags) {
+  for (const auto& gflag : gflags_) {
     google::CommandLineFlagInfo gflag_info =
         google::GetCommandLineFlagInfoOrDie(gflag.c_str());
     string noun;
@@ -69,17 +124,11 @@ string BuildLeafActionHelpString(const vector<Action>& chain) {
     gflags_msg += google::DescribeOneFlag(gflag_info);
   }
   msg += "\n";
-  msg += Substitute("$0\n", action.description);
+  msg += Substitute("$0\n", label_.description);
   msg += gflags_msg;
   return msg;
 }
 
-string BuildNonLeafActionHelpString(const vector<Action>& chain) {
-  string usage = BuildUsageString(chain);
-  DCHECK(!chain.empty());
-  return BuildHelpString(chain.back().sub_actions, usage);
-}
-
 Status ParseAndRemoveArg(const char* arg_name,
                          deque<string>* remaining_args,
                          string* parsed_arg) {

http://git-wip-us.apache.org/repos/asf/kudu/blob/5ef37438/src/kudu/tools/tool_action.h
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action.h b/src/kudu/tools/tool_action.h
index 6e6cc5a..cea7c7e 100644
--- a/src/kudu/tools/tool_action.h
+++ b/src/kudu/tools/tool_action.h
@@ -19,23 +19,39 @@
 
 #include <deque>
 #include <glog/logging.h>
+#include <memory>
 #include <string>
 #include <vector>
 
+#include "kudu/gutil/strings/join.h"
 #include "kudu/gutil/strings/substitute.h"
 #include "kudu/util/status.h"
 
 namespace kudu {
 namespace tools {
 
-// Encapsulates all knowledge for a particular tool action.
+class Action;
+class Mode;
+
+// The command line tool is structured as a tree with two kinds of nodes: modes
+// and actions. Actions are leaf nodes, each representing a particular
+// operation that the tool can take. Modes are non-leaf nodes that are
+// basically just intuitive groupings of actions.
+//
+// Regardless of type, every node has a name which is used to match it against
+// a command line argument during parsing. Additionally, every node has a
+// description, displayed (along with the name) in help text.
 //
-// All actions are arranged in a tree. Leaf actions are invokable: they will
-// do something meaningful when run() is called. Non-leaf actions do not have
-// a run(); an attempt to invoke them will yield help() instead.
+// Every node (be it action or mode) has pointers to its children, but not to
+// its parent mode. As such, operations that require information from the
+// parent modes expect the caller to provide those modes as a "mode chain".
 //
-// Sample action tree:
+// Sample node tree:
 //
+//         root
+//          |
+//          |
+//          |
 //          fs
 //         |  |
 //      +--+  +--+
@@ -43,50 +59,127 @@ namespace tools {
 //   format   print_uuid
 //
 // Given this tree:
-// - "<program> fs" will print some text explaining all of fs's actions.
+// - "<program> fs" will show all of fs's possible actions.
 // - "<program> fs format" will format a filesystem.
 // - "<program> fs print_uuid" will print a filesystem's UUID.
-struct Action {
-  // The name of the action (e.g. "fs").
+
+// Properties common to all nodes.
+struct Label {
+  // The node's name (e.g. "fs"). Uniquely identifies the node action amongst
+  // its siblings in the tree.
   std::string name;
 
-  // The description of the action (e.g. "Operate on a local Kudu filesystem").
+  // The node's description (e.g. "Operate on a local Kudu filesystem").
   std::string description;
+};
 
-  // Invokes an action, passing in the complete action chain and all remaining
-  // command line arguments. The arguments are passed by value so that the
-  // function can modify them if need be.
-  std::function<Status(const std::vector<Action>&,
-                       std::deque<std::string>)> run;
+// Builds a new mode (non-leaf) node.
+class ModeBuilder {
+ public:
+  // Creates a new ModeBuilder with a specific label.
+  explicit ModeBuilder(const Label& label);
 
-  // Get help for an action, passing in the complete action chain.
-  std::function<std::string(const std::vector<Action>&)> help;
+  // Adds a new mode (non-leaf child node) to this builder.
+  ModeBuilder& AddMode(std::unique_ptr<Mode> mode);
 
-  // This action's children.
-  std::vector<Action> sub_actions;
+  // Adds a new action (leaf child node) to this builder.
+  ModeBuilder& AddAction(std::unique_ptr<Action> action);
 
-  // This action's gflags (if any).
-  std::vector<std::string> gflags;
+  // Creates a mode using builder state.
+  //
+  // May only be called once.
+  std::unique_ptr<Mode> Build();
+
+ private:
+  const Label label_;
+
+  std::vector<std::unique_ptr<Mode>> submodes_;
+
+  std::vector<std::unique_ptr<Action>> actions_;
 };
 
-// Constructs a string with the names of all actions in the chain
-// (e.g. "<program> fs format").
-std::string BuildActionChainString(const std::vector<Action>& chain);
+// A non-leaf node in the tree, representing a logical grouping for actions or
+// more modes.
+class Mode {
+ public:
+
+  // Returns the help for this mode given its parent mode chain.
+  std::string BuildHelp(const std::vector<Mode*>& chain) const;
 
-// Constructs a usage string (e.g. "Usage: <program> fs format").
-std::string BuildUsageString(const std::vector<Action>& chain);
+  const std::string& name() const { return label_.name; }
+
+  const std::string& description() const { return label_.description; }
+
+  const std::vector<std::unique_ptr<Mode>>& modes() const { return submodes_; }
+
+  const std::vector<std::unique_ptr<Action>>& actions() const { return actions_; }
+
+ private:
+  friend class ModeBuilder;
+
+  Mode();
+
+  Label label_;
+
+  std::vector<std::unique_ptr<Mode>> submodes_;
+
+  std::vector<std::unique_ptr<Action>> actions_;
+};
 
-// Constructs a help string suitable for leaf actions.
-std::string BuildLeafActionHelpString(const std::vector<Action>& chain);
+// Function signature for any operation represented by an action. When run, the
+// operation receives the parent mode chain, the current action, and any
+// remaining command line arguments.
+typedef std::function<Status(const std::vector<Mode*>&,
+                             const Action*,
+                             std::deque<std::string>)> ActionRunner;
 
-// Constructs a help string suitable for non-leaf actions.
-std::string BuildNonLeafActionHelpString(const std::vector<Action>& chain);
+// Builds a new action (leaf) node.
+class ActionBuilder {
+ public:
+  // Creates a new ActionBuilder with a specific label and action runner.
+  ActionBuilder(const Label& label, const ActionRunner& runner);
 
-// Constructs a string appropriate for displaying program help, using
-// 'sub_actions' as a list of actions to include and 'usage_str' as a string
-// to prepend.
-std::string BuildHelpString(const std::vector<Action>& sub_actions,
-                            std::string usage_str);
+  // Add a new gflag to this builder. They are used when generating help.
+  ActionBuilder& AddGflag(const std::string& gflag);
+
+  // Creates an action using builder state.
+  std::unique_ptr<Action> Build();
+
+ private:
+  Label label_;
+
+  ActionRunner runner_;
+
+  std::vector<std::string> gflags_;
+};
+
+// A leaf node in the tree, representing a logical operation taken by the tool.
+class Action {
+ public:
+
+  // Returns the help for this action given its parent mode chain.
+  std::string BuildHelp(const std::vector<Mode*>& chain) const;
+
+  // Runs the operation represented by this action, given a parent mode chain
+  // and list of extra command line arguments.
+  Status Run(const std::vector<Mode*>& chain, std::deque<std::string> args) const;
+
+  const std::string& name() const { return label_.name; }
+
+  const std::string& description() const { return label_.description; }
+
+ private:
+  friend class ActionBuilder;
+
+  Action();
+
+  Label label_;
+
+  ActionRunner runner_;
+
+  // This action's gflags (if any).
+  std::vector<std::string> gflags_;
+};
 
 // Removes one argument from 'remaining_args' and stores it in 'parsed_arg'.
 //
@@ -98,22 +191,23 @@ Status ParseAndRemoveArg(const char* arg_name,
 
 // Checks that 'args' is empty. If not, returns a bad status.
 template <typename CONTAINER>
-Status CheckNoMoreArgs(const std::vector<Action>& chain,
+Status CheckNoMoreArgs(const std::vector<Mode*>& chain,
+                       const Action* action,
                        const CONTAINER& args) {
   if (args.empty()) {
     return Status::OK();
   }
   DCHECK(!chain.empty());
-  Action action = chain.back();
-  return Status::InvalidArgument(strings::Substitute(
-      "too many arguments\n$0", action.help(chain)));
+  return Status::InvalidArgument(
+      strings::Substitute("too many arguments: '$0'\n$1",
+                          JoinStrings(args, " "), action->BuildHelp(chain)));
 }
 
-// Returns the "fs" action node.
-Action BuildFsAction();
+// Returns a new "fs" mode node.
+std::unique_ptr<Mode> BuildFsMode();
 
-// Returns the "tablet" action node.
-Action BuildTabletAction();
+// Returns a new "tablet" mode node.
+std::unique_ptr<Mode> BuildTabletMode();
 
 } // namespace tools
 } // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/5ef37438/src/kudu/tools/tool_action_fs.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action_fs.cc b/src/kudu/tools/tool_action_fs.cc
index b0b7c20..a77bc06 100644
--- a/src/kudu/tools/tool_action_fs.cc
+++ b/src/kudu/tools/tool_action_fs.cc
@@ -21,6 +21,7 @@
 #include <deque>
 #include <gflags/gflags.h>
 #include <iostream>
+#include <memory>
 #include <string>
 
 #include "kudu/fs/fs_manager.h"
@@ -30,6 +31,7 @@ using std::cout;
 using std::deque;
 using std::endl;
 using std::string;
+using std::unique_ptr;
 using std::vector;
 
 DEFINE_string(uuid, "",
@@ -40,8 +42,10 @@ namespace tools {
 
 namespace {
 
-Status Format(const vector<Action>& chain, deque<string> args) {
-  RETURN_NOT_OK(CheckNoMoreArgs(chain, args));
+Status Format(const vector<Mode*>& chain,
+              const Action* action,
+              deque<string> args) {
+  RETURN_NOT_OK(CheckNoMoreArgs(chain, action, args));
 
   FsManager fs_manager(Env::Default(), FsManagerOpts());
   boost::optional<string> uuid;
@@ -51,8 +55,10 @@ Status Format(const vector<Action>& chain, deque<string> args) {
   return fs_manager.CreateInitialFileSystemLayout(uuid);
 }
 
-Status PrintUuid(const vector<Action>& chain, deque<string> args) {
-  RETURN_NOT_OK(CheckNoMoreArgs(chain, args));
+Status PrintUuid(const vector<Mode*>& chain,
+                 const Action* action,
+                 deque<string> args) {
+  RETURN_NOT_OK(CheckNoMoreArgs(chain, action, args));
 
   FsManagerOpts opts;
   opts.read_only = true;
@@ -64,30 +70,24 @@ Status PrintUuid(const vector<Action>& chain, deque<string> args) {
 
 } // anonymous namespace
 
-Action BuildFsAction() {
-  Action fs_format;
-  fs_format.name = "format";
-  fs_format.description = "Format a new Kudu filesystem";
-  fs_format.help = &BuildLeafActionHelpString;
-  fs_format.run = &Format;
-  fs_format.gflags = { "fs_wal_dir", "fs_data_dirs", "uuid" };
-
-  Action fs_print_uuid;
-  fs_print_uuid.name = "print_uuid";
-  fs_print_uuid.description = "Print the UUID of a Kudu filesystem";
-  fs_print_uuid.help = &BuildLeafActionHelpString;
-  fs_print_uuid.run = &PrintUuid;
-  fs_print_uuid.gflags = { "fs_wal_dir", "fs_data_dirs" };
-
-  Action fs;
-  fs.name = "fs";
-  fs.description = "Operate on a local Kudu filesystem";
-  fs.help = &BuildNonLeafActionHelpString;
-  fs.sub_actions = {
-      fs_format,
-      fs_print_uuid
-  };
-  return fs;
+unique_ptr<Mode> BuildFsMode() {
+  unique_ptr<Action> format = ActionBuilder(
+      { "format", "Format a new Kudu filesystem" }, &Format)
+    .AddGflag("fs_wal_dir")
+    .AddGflag("fs_data_dirs")
+    .AddGflag("uuid")
+    .Build();
+
+  unique_ptr<Action> print_uuid = ActionBuilder(
+      { "print_uuid", "Print the UUID of a Kudu filesystem" }, &PrintUuid)
+    .AddGflag("fs_wal_dir")
+    .AddGflag("fs_data_dirs")
+    .Build();
+
+  return ModeBuilder({ "fs", "Operate on a local Kudu filesystem" })
+      .AddAction(std::move(format))
+      .AddAction(std::move(print_uuid))
+      .Build();
 }
 
 } // namespace tools

http://git-wip-us.apache.org/repos/asf/kudu/blob/5ef37438/src/kudu/tools/tool_action_tablet.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action_tablet.cc b/src/kudu/tools/tool_action_tablet.cc
index 1dbd253..a34ced7 100644
--- a/src/kudu/tools/tool_action_tablet.cc
+++ b/src/kudu/tools/tool_action_tablet.cc
@@ -95,11 +95,13 @@ Status ParsePeerString(const string& peer_str,
   return Status::OK();
 }
 
-Status PrintReplicaUuids(const vector<Action>& chain, deque<string> args) {
+Status PrintReplicaUuids(const vector<Mode*>& chain,
+                         const Action* action,
+                         deque<string> args) {
   // Parse tablet ID argument.
   string tablet_id;
   RETURN_NOT_OK(ParseAndRemoveArg("tablet ID", &args, &tablet_id));
-  RETURN_NOT_OK(CheckNoMoreArgs(chain, args));
+  RETURN_NOT_OK(CheckNoMoreArgs(chain, action, args));
 
   FsManagerOpts opts;
   opts.read_only = true;
@@ -116,7 +118,9 @@ Status PrintReplicaUuids(const vector<Action>& chain, deque<string> args) {
   return Status::OK();
 }
 
-Status RewriteRaftConfig(const vector<Action>& chain, deque<string> args) {
+Status RewriteRaftConfig(const vector<Mode*>& chain,
+                         const Action* action,
+                         deque<string> args) {
   // Parse tablet ID argument.
   string tablet_id;
   RETURN_NOT_OK(ParseAndRemoveArg("tablet ID", &args, &tablet_id));
@@ -171,14 +175,16 @@ Status RewriteRaftConfig(const vector<Action>& chain, deque<string> args) {
   return cmeta->Flush();
 }
 
-Status Copy(const vector<Action>& chain, deque<string> args) {
+Status Copy(const vector<Mode*>& chain,
+            const Action* action,
+            deque<string> args) {
   // Parse the tablet ID and source arguments.
   string tablet_id;
   RETURN_NOT_OK(ParseAndRemoveArg("tablet ID", &args, &tablet_id));
   string rpc_address;
   RETURN_NOT_OK(ParseAndRemoveArg("source RPC address of form hostname:port",
                                   &args, &rpc_address));
-  RETURN_NOT_OK(CheckNoMoreArgs(chain, args));
+  RETURN_NOT_OK(CheckNoMoreArgs(chain, action, args));
 
   HostPort hp;
   RETURN_NOT_OK(ParseHostPortString(rpc_address, &hp));
@@ -197,52 +203,40 @@ Status Copy(const vector<Action>& chain, deque<string> args) {
 
 } // anonymous namespace
 
-Action BuildTabletAction() {
+unique_ptr<Mode> BuildTabletMode() {
   // TODO: Need to include required arguments in the help for these actions.
 
-  Action tablet_print_replica_uuids;
-  tablet_print_replica_uuids.name = "print_replica_uuids";
-  tablet_print_replica_uuids.description =
-      "Print all replica UUIDs found in a tablet's Raft configuration";
-  tablet_print_replica_uuids.help = &BuildLeafActionHelpString;
-  tablet_print_replica_uuids.run = &PrintReplicaUuids;
-  tablet_print_replica_uuids.gflags = { "fs_wal_dir", "fs_data_dirs" };
-
-
-  Action tablet_rewrite_raft_config;
-  tablet_rewrite_raft_config.name = "rewrite_raft_config";
-  tablet_rewrite_raft_config.description =
-      "Rewrite a replica's Raft configuration";
-  tablet_rewrite_raft_config.help = &BuildLeafActionHelpString;
-  tablet_rewrite_raft_config.run = &RewriteRaftConfig;
-  tablet_rewrite_raft_config.gflags = { "fs_wal_dir", "fs_data_dirs" };
-
-  Action tablet_cmeta;
-  tablet_cmeta.name = "cmeta";
-  tablet_cmeta.description =
-      "Operate on a local Kudu tablet's consensus metadata file";
-  tablet_cmeta.help = &BuildNonLeafActionHelpString;
-  tablet_cmeta.sub_actions = {
-      std::move(tablet_print_replica_uuids),
-      std::move(tablet_rewrite_raft_config),
-  };
-
-  Action tablet_copy;
-  tablet_copy.name = "copy";
-  tablet_copy.description = "Copy a replica from a remote server";
-  tablet_copy.help = &BuildLeafActionHelpString;
-  tablet_copy.run = &Copy;
-  tablet_copy.gflags = { "fs_wal_dir", "fs_data_dirs" };
-
-  Action tablet;
-  tablet.name = "tablet";
-  tablet.description = "Operate on a local Kudu replica";
-  tablet.help = &BuildNonLeafActionHelpString;
-  tablet.sub_actions = {
-      tablet_cmeta,
-      tablet_copy
-  };
-  return tablet;
+  unique_ptr<Action> print_replica_uuids = ActionBuilder(
+      { "print_replica_uuids",
+        "Print all replica UUIDs found in a tablet's Raft configuration" },
+      &PrintReplicaUuids)
+    .AddGflag("fs_wal_dir")
+    .AddGflag("fs_data_dirs")
+    .Build();
+
+  unique_ptr<Action> rewrite_raft_config = ActionBuilder(
+      { "rewrite_raft_config", "Rewrite a replica's Raft configuration" },
+      &RewriteRaftConfig)
+    .AddGflag("fs_wal_dir")
+    .AddGflag("fs_data_dirs")
+    .Build();
+
+  unique_ptr<Mode> cmeta = ModeBuilder(
+      { "cmeta", "Operate on a local Kudu tablet's consensus metadata file" })
+    .AddAction(std::move(print_replica_uuids))
+    .AddAction(std::move(rewrite_raft_config))
+    .Build();
+
+  unique_ptr<Action> copy = ActionBuilder(
+      { "copy", "Copy a replica from a remote server" }, &Copy)
+    .AddGflag("fs_wal_dir")
+    .AddGflag("fs_data_dirs")
+    .Build();
+
+  return ModeBuilder({ "tablet", "Operate on a local Kudu replica" })
+      .AddMode(std::move(cmeta))
+      .AddAction(std::move(copy))
+      .Build();
 }
 
 } // namespace tools

http://git-wip-us.apache.org/repos/asf/kudu/blob/5ef37438/src/kudu/tools/tool_main.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_main.cc b/src/kudu/tools/tool_main.cc
index 5ffc4a7..96a74fa 100644
--- a/src/kudu/tools/tool_main.cc
+++ b/src/kudu/tools/tool_main.cc
@@ -19,6 +19,7 @@
 #include <gflags/gflags.h>
 #include <glog/logging.h>
 #include <iostream>
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -40,16 +41,17 @@ using std::cout;
 using std::deque;
 using std::endl;
 using std::string;
+using std::unique_ptr;
 using std::vector;
 using strings::Substitute;
 
 namespace kudu {
 namespace tools {
 
-int DispatchCommand(const vector<Action>& chain, const deque<string>& args) {
-  DCHECK(!chain.empty());
-  Action action = chain.back();
-  Status s = action.run(chain, args);
+int DispatchCommand(const vector<Mode*>& chain,
+                    Action* action,
+                    const deque<string>& args) {
+  Status s = action->Run(chain, args);
   if (s.ok()) {
     return 0;
   } else {
@@ -58,63 +60,70 @@ int DispatchCommand(const vector<Action>& chain, const deque<string>& args) {
   }
 }
 
-int RunTool(const Action& root, int argc, char** argv, bool show_help) {
+int RunTool(int argc, char** argv, bool show_help) {
+  unique_ptr<Mode> root =
+      ModeBuilder({ argv[0], "" }) // root mode description isn't printed
+      .AddMode(BuildFsMode())
+      .AddMode(BuildTabletMode())
+      .Build();
+
   // Initialize arg parsing state.
-  vector<Action> chain = { root };
+  vector<Mode*> chain = { root.get() };
 
-  // Parse the arguments, matching them up with actions.
+  // Parse the arguments, matching each to a mode or action.
   for (int i = 1; i < argc; i++) {
-    const Action* cur = &chain.back();
-    const auto& sub_actions = cur->sub_actions;
-    if (sub_actions.empty()) {
-      // We've reached an invokable action.
-      if (show_help) {
-        cerr << cur->help(chain) << endl;
-        return 1;
-      } else {
-        // Invoke it with whatever arguments remain.
-        deque<string> remaining_args;
-        for (int j = i; j < argc; j++) {
-          remaining_args.push_back(argv[j]);
-        }
-        return DispatchCommand(chain, remaining_args);
+    Mode* cur = chain.back();
+    Mode* next_mode = nullptr;
+    Action* next_action = nullptr;
+
+    // Match argument with a mode.
+    for (const auto& m : cur->modes()) {
+      if (m->name() == argv[i]) {
+        next_mode = m.get();
+        break;
       }
     }
 
-    // This action is not invokable. Interpret the next command line argument
-    // as a subaction and continue parsing.
-    const Action* next = nullptr;
-    for (const auto& a : sub_actions) {
-      if (a.name == argv[i]) {
-        next = &a;
+    // Match argument with an action.
+    for (const auto& a : cur->actions()) {
+      if (a->name() == argv[i]) {
+        next_action = a.get();
         break;
       }
     }
 
-    if (next == nullptr) {
-      // We couldn't find a subaction for the next argument. Raise an error.
-      string msg = Substitute("$0 $1\n",
-                              BuildActionChainString(chain), argv[i]);
-      msg += BuildHelpString(sub_actions, BuildUsageString(chain));
-      Status s = Status::InvalidArgument(msg);
-      cerr << s.ToString() << endl;
+    // If both matched, there's an error with the tree.
+    DCHECK(!next_mode || !next_action);
+
+    if (next_mode) {
+      // Add the mode and keep parsing.
+      chain.push_back(next_mode);
+    } else if (next_action) {
+      if (show_help) {
+        cerr << next_action->BuildHelp(chain) << endl;
+        return 1;
+      } else {
+        // Invoke the action with whatever arguments remain, skipping this one.
+        deque<string> remaining_args;
+        for (int j = i + 1; j < argc; j++) {
+          remaining_args.push_back(argv[j]);
+        }
+        return DispatchCommand(chain, next_action, remaining_args);
+      }
+    } else {
+      // Couldn't match the argument at all. Print the help.
+      Status s = Status::InvalidArgument(
+          Substitute("unknown command '$0'\n", argv[i]));
+      cerr << s.ToString() << cur->BuildHelp(chain) << endl;
       return 1;
     }
-
-    // We're done parsing this argument. Loop and continue.
-    chain.emplace_back(*next);
   }
 
-  // We made it to a subaction with no arguments left. Run the subaction if
-  // possible, otherwise print its help.
-  const Action* last = &chain.back();
-  if (show_help || !last->run) {
-    cerr << last->help(chain) << endl;
-    return 1;
-  } else {
-    DCHECK(last->run);
-    return DispatchCommand(chain, {});
-  }
+  // Ran out of arguments before reaching an action. Print the last mode's help.
+  DCHECK(!chain.empty());
+  const Mode* last = chain.back();
+  cerr << last->BuildHelp(chain) << endl;
+  return 1;
 }
 
 } // namespace tools
@@ -145,21 +154,8 @@ static bool ParseCommandLineFlags(int* argc, char*** argv) {
 }
 
 int main(int argc, char** argv) {
-  kudu::tools::Action root = {
-      argv[0],
-      "The root action", // doesn't matter, won't get printed
-      nullptr,
-      &kudu::tools::BuildNonLeafActionHelpString,
-      {
-          kudu::tools::BuildFsAction(),
-          kudu::tools::BuildTabletAction()
-      },
-      {} // no gflags
-  };
-  string usage = root.help({ root });
-  google::SetUsageMessage(usage);
   bool show_help = ParseCommandLineFlags(&argc, &argv);
   FLAGS_logtostderr = true;
   kudu::InitGoogleLoggingSafe(argv[0]);
-  return kudu::tools::RunTool(root, argc, argv, show_help);
+  return kudu::tools::RunTool(argc, argv, show_help);
 }