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

kudu git commit: tool: split up action descriptions

Repository: kudu
Updated Branches:
  refs/heads/master f5021e061 -> 25f5c215e


tool: split up action descriptions

With ksck we have a use case for "short" and "long" action descriptions: the
former appears in mode help and action help, while the latter is only in
action help. This patch splits action descriptions into "short" and "extra",
refactoring all actions accordingly. While I was there I got rid of the
label abstraction, which I felt was producing too much unfluent code.

I also added some more tests to kudu-tool-test.

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


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

Branch: refs/heads/master
Commit: 25f5c215e0027f9121f5a16f1b82ff7e5c7f9430
Parents: f5021e0
Author: Adar Dembo <ad...@cloudera.com>
Authored: Sun Aug 28 10:52:15 2016 -0700
Committer: Adar Dembo <ad...@cloudera.com>
Committed: Mon Aug 29 21:42:41 2016 +0000

----------------------------------------------------------------------
 src/kudu/tools/kudu-tool-test.cc      | 102 +++++++++++++++++++++++++++++
 src/kudu/tools/tool_action.cc         |  41 ++++++++++--
 src/kudu/tools/tool_action.h          |  69 ++++++++++++-------
 src/kudu/tools/tool_action_cluster.cc |  39 ++++++-----
 src/kudu/tools/tool_action_fs.cc      |  27 ++++----
 src/kudu/tools/tool_action_pbc.cc     |   8 ++-
 src/kudu/tools/tool_action_tablet.cc  |  68 +++++++++----------
 src/kudu/tools/tool_main.cc           |  14 ++--
 8 files changed, 266 insertions(+), 102 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kudu/blob/25f5c215/src/kudu/tools/kudu-tool-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/kudu-tool-test.cc b/src/kudu/tools/kudu-tool-test.cc
index ba748aa..877439f 100644
--- a/src/kudu/tools/kudu-tool-test.cc
+++ b/src/kudu/tools/kudu-tool-test.cc
@@ -21,9 +21,11 @@
 #include <gtest/gtest.h>
 #include <glog/stl_logging.h>
 
+#include "kudu/fs/fs_manager.h"
 #include "kudu/gutil/strings/split.h"
 #include "kudu/gutil/strings/substitute.h"
 #include "kudu/util/env.h"
+#include "kudu/util/oid_generator.h"
 #include "kudu/util/path_util.h"
 #include "kudu/util/subprocess.h"
 #include "kudu/util/test_macros.h"
@@ -34,6 +36,7 @@ namespace tools {
 
 using std::string;
 using std::vector;
+using strings::Substitute;
 
 class ToolTest : public KuduTest {
  public:
@@ -65,6 +68,20 @@ class ToolTest : public KuduTest {
 
   }
 
+  void RunTestActionNoOut(const string& arg_str) const {
+    vector<string> stdout;
+    RunTestAction(arg_str, &stdout);
+    ASSERT_TRUE(stdout.empty());
+  }
+
+  void RunTestAction(const string& arg_str, vector<string>* stdout) const {
+    vector<string> stderr;
+    Status s = RunTool(arg_str, stdout, &stderr);
+    SCOPED_TRACE(*stdout);
+    SCOPED_TRACE(stderr);
+    ASSERT_OK(s);
+  }
+
   void RunTestHelp(const string& arg_str,
                    const vector<string>& regexes,
                    const Status& expected_status = Status::OK()) const {
@@ -103,6 +120,7 @@ class ToolTest : public KuduTest {
 
 TEST_F(ToolTest, TestTopLevelHelp) {
   const vector<string> kTopLevelRegexes = {
+      "cluster.*Kudu cluster",
       "fs.*Kudu filesystem",
       "pbc.*protobuf container",
       "tablet.*Kudu replica"
@@ -143,6 +161,12 @@ TEST_F(ToolTest, TestModeHelp) {
     };
     NO_FATALS(RunTestHelp("cluster", kClusterModeRegexes));
   }
+  {
+    const vector<string> kPbcModeRegexes = {
+        "dump.*Dump a PBC",
+    };
+    NO_FATALS(RunTestHelp("pbc", kPbcModeRegexes));
+  }
 }
 
 TEST_F(ToolTest, TestActionHelp) {
@@ -156,5 +180,83 @@ TEST_F(ToolTest, TestActionHelp) {
       Status::InvalidArgument("too many arguments: 'extra_arg'")));
 }
 
+TEST_F(ToolTest, TestFsFormat) {
+  const string kTestDir = GetTestPath("test");
+  NO_FATALS(RunTestActionNoOut(Substitute("fs format --fs_wal_dir=$0", kTestDir)));
+  FsManager fs(env_.get(), kTestDir);
+  ASSERT_OK(fs.Open());
+
+  ObjectIdGenerator generator;
+  string canonicalized_uuid;
+  ASSERT_OK(generator.Canonicalize(fs.uuid(), &canonicalized_uuid));
+  ASSERT_EQ(fs.uuid(), canonicalized_uuid);
+}
+
+TEST_F(ToolTest, TestFsFormatWithUuid) {
+  const string kTestDir = GetTestPath("test");
+  ObjectIdGenerator generator;
+  string original_uuid = generator.Next();
+  NO_FATALS(RunTestActionNoOut(Substitute(
+      "fs format --fs_wal_dir=$0 --uuid=$1", kTestDir, original_uuid)));
+  FsManager fs(env_.get(), kTestDir);
+  ASSERT_OK(fs.Open());
+
+  string canonicalized_uuid;
+  ASSERT_OK(generator.Canonicalize(fs.uuid(), &canonicalized_uuid));
+  ASSERT_EQ(fs.uuid(), canonicalized_uuid);
+  ASSERT_EQ(fs.uuid(), original_uuid);
+}
+
+TEST_F(ToolTest, TestFsPrintUuid) {
+  const string kTestDir = GetTestPath("test");
+  string uuid;
+  {
+    FsManager fs(env_.get(), kTestDir);
+    ASSERT_OK(fs.CreateInitialFileSystemLayout());
+    ASSERT_OK(fs.Open());
+    uuid = fs.uuid();
+  }
+  vector<string> stdout;
+  NO_FATALS(RunTestAction(Substitute(
+      "fs print_uuid --fs_wal_dir=$0", kTestDir), &stdout));
+  SCOPED_TRACE(stdout);
+  ASSERT_EQ(1, stdout.size());
+  ASSERT_EQ(uuid, stdout[0]);
+}
+
+TEST_F(ToolTest, TestPbcDump) {
+  const string kTestDir = GetTestPath("test");
+  string uuid;
+  string instance_path;
+  {
+    ObjectIdGenerator generator;
+    FsManager fs(env_.get(), kTestDir);
+    ASSERT_OK(fs.CreateInitialFileSystemLayout(generator.Next()));
+    ASSERT_OK(fs.Open());
+    uuid = fs.uuid();
+    instance_path = fs.GetInstanceMetadataPath(kTestDir);
+  }
+  vector<string> stdout;
+  {
+    NO_FATALS(RunTestAction(Substitute(
+        "pbc dump $0", instance_path), &stdout));
+    SCOPED_TRACE(stdout);
+    ASSERT_EQ(4, stdout.size());
+    ASSERT_EQ("Message 0", stdout[0]);
+    ASSERT_EQ("-------", stdout[1]);
+    ASSERT_EQ(Substitute("uuid: \"$0\"", uuid), stdout[2]);
+    ASSERT_STR_MATCHES(stdout[3], "^format_stamp: \"Formatted at .*\"$");
+  }
+  {
+    NO_FATALS(RunTestAction(Substitute(
+        "pbc dump $0/instance --oneline", kTestDir), &stdout));
+    SCOPED_TRACE(stdout);
+    ASSERT_EQ(1, stdout.size());
+    ASSERT_STR_MATCHES(
+        stdout[0], Substitute(
+            "^0\tuuid: \"$0\" format_stamp: \"Formatted at .*\"$$", uuid));
+  }
+}
+
 } // namespace tools
 } // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/25f5c215/src/kudu/tools/tool_action.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action.cc b/src/kudu/tools/tool_action.cc
index 86e3b93..d142465 100644
--- a/src/kudu/tools/tool_action.cc
+++ b/src/kudu/tools/tool_action.cc
@@ -112,8 +112,14 @@ string SpacePad(StringPiece s, int len) {
 
 } // anonymous namespace
 
-ModeBuilder::ModeBuilder(const Label& label)
-    : label_(label) {
+ModeBuilder::ModeBuilder(const string& name)
+    : name_(name) {
+}
+
+ModeBuilder& ModeBuilder::Description(const string& description) {
+  CHECK(description_.empty());
+  description_ = description;
+  return *this;
 }
 
 ModeBuilder& ModeBuilder::AddMode(unique_ptr<Mode> mode) {
@@ -127,8 +133,10 @@ ModeBuilder& ModeBuilder::AddAction(unique_ptr<Action> action) {
 }
 
 unique_ptr<Mode> ModeBuilder::Build() {
+  CHECK(!description_.empty());
   unique_ptr<Mode> mode(new Mode());
-  mode->label_ = label_;
+  mode->name_ = name_;
+  mode->description_ = description_;
   mode->submodes_ = std::move(submodes_);
   mode->actions_ = std::move(actions_);
   return mode;
@@ -164,11 +172,23 @@ string Mode::BuildHelp(const vector<Mode*>& chain) const {
 Mode::Mode() {
 }
 
-ActionBuilder::ActionBuilder(const Label& label, const ActionRunner& runner)
-    : label_(label),
+ActionBuilder::ActionBuilder(const string& name, const ActionRunner& runner)
+    : name_(name),
       runner_(runner) {
 }
 
+ActionBuilder& ActionBuilder::Description(const string& description) {
+  CHECK(description_.empty());
+  description_ = description;
+  return *this;
+}
+
+ActionBuilder& ActionBuilder::ExtraDescription(const string& extra_description) {
+  CHECK(!extra_description_.is_initialized());
+  extra_description_ = extra_description;
+  return *this;
+}
+
 ActionBuilder& ActionBuilder::AddRequiredParameter(
     const ActionArgsDescriptor::Arg& arg) {
   args_.required.push_back(arg);
@@ -193,8 +213,11 @@ ActionBuilder& ActionBuilder::AddOptionalParameter(const string& param) {
 }
 
 unique_ptr<Action> ActionBuilder::Build() {
+  CHECK(!description_.empty());
   unique_ptr<Action> action(new Action());
-  action->label_ = label_;
+  action->name_ = name_;
+  action->description_ = description_;
+  action->extra_description_ = extra_description_;
   action->runner_ = runner_;
   action->args_ = args_;
   return action;
@@ -247,7 +270,11 @@ string Action::BuildHelp(const vector<Mode*>& chain) const {
   string msg;
   AppendHardWrapped(usage_msg, 8, &msg);
   msg += "\n\n";
-  AppendHardWrapped(label_.description, 0, &msg);
+  AppendHardWrapped(description_, 0, &msg);
+  if (extra_description_) {
+    msg += "\n\n";
+    AppendHardWrapped(extra_description_.get(), 0, &msg);
+  }
   msg += "\n\n";
   msg += desc_msg;
   return msg;

http://git-wip-us.apache.org/repos/asf/kudu/blob/25f5c215/src/kudu/tools/tool_action.h
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action.h b/src/kudu/tools/tool_action.h
index 7d79a4f..bac09dd 100644
--- a/src/kudu/tools/tool_action.h
+++ b/src/kudu/tools/tool_action.h
@@ -62,21 +62,18 @@ class Mode;
 // - "<program> fs format" will format a filesystem.
 // - "<program> fs print_uuid" will print a filesystem's UUID.
 
-// 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 node's description (e.g. "Operate on a local Kudu filesystem").
-  std::string description;
-};
-
 // Builds a new mode (non-leaf) node.
 class ModeBuilder {
  public:
-  // Creates a new ModeBuilder with a specific label.
-  explicit ModeBuilder(const Label& label);
+  // Creates a new ModeBuilder with a specific name (e.g. "fs"). The name
+  // uniquely identifies the mode amongst its siblings in the tree.
+  explicit ModeBuilder(const std::string& name);
+
+  // Sets the description of this mode (e.g. "Operate on a local Kudu
+  // filesystem"), to be used when printing help.
+  //
+  // Required.
+  ModeBuilder& Description(const std::string& desc);
 
   // Adds a new mode (non-leaf child node) to this builder.
   ModeBuilder& AddMode(std::unique_ptr<Mode> mode);
@@ -90,7 +87,9 @@ class ModeBuilder {
   std::unique_ptr<Mode> Build();
 
  private:
-  const Label label_;
+  const std::string name_;
+
+  std::string description_;
 
   std::vector<std::unique_ptr<Mode>> submodes_;
 
@@ -105,9 +104,9 @@ class Mode {
   // Returns the help for this mode given its parent mode chain.
   std::string BuildHelp(const std::vector<Mode*>& chain) const;
 
-  const std::string& name() const { return label_.name; }
+  const std::string& name() const { return name_; }
 
-  const std::string& description() const { return label_.description; }
+  const std::string& description() const { return description_; }
 
   const std::vector<std::unique_ptr<Mode>>& modes() const { return submodes_; }
 
@@ -118,7 +117,9 @@ class Mode {
 
   Mode();
 
-  Label label_;
+  std::string name_;
+
+  std::string description_;
 
   std::vector<std::unique_ptr<Mode>> submodes_;
 
@@ -174,8 +175,20 @@ struct ActionArgsDescriptor {
 // 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);
+  // Creates a new ActionBuilder with a specific name (e.g. "format") and
+  // action runner. The name uniquely identifies the action amongst its
+  // siblings in the tree.
+  ActionBuilder(const std::string& name, const ActionRunner& runner);
+
+  // Sets the description of this action (e.g. "Format a new Kudu filesystem"),
+  // to be used when printing the parent mode's help and the action's help.
+  //
+  // Required.
+  ActionBuilder& Description(const std::string& description);
+
+  // Sets the long description of this action. If provided, will added to this
+  // action's help following Description().
+  ActionBuilder& ExtraDescription(const std::string& extra_description);
 
   // Add a new required parameter to this builder.
   //
@@ -208,7 +221,11 @@ class ActionBuilder {
   std::unique_ptr<Action> Build();
 
  private:
-  Label label_;
+  const std::string name_;
+
+  std::string description_;
+
+  boost::optional<std::string> extra_description_;
 
   ActionRunner runner_;
 
@@ -228,9 +245,13 @@ class Action {
              const std::unordered_map<std::string, std::string>& required_args,
              const std::vector<std::string>& variadic_args) const;
 
-  const std::string& name() const { return label_.name; }
+  const std::string& name() const { return name_; }
 
-  const std::string& description() const { return label_.description; }
+  const std::string& description() const { return description_; }
+
+  const boost::optional<std::string>& extra_description() const {
+    return extra_description_;
+  }
 
   const ActionArgsDescriptor& args() const { return args_; }
 
@@ -239,7 +260,11 @@ class Action {
 
   Action();
 
-  Label label_;
+  std::string name_;
+
+  std::string description_;
+
+  boost::optional<std::string> extra_description_;
 
   ActionRunner runner_;
 

http://git-wip-us.apache.org/repos/asf/kudu/blob/25f5c215/src/kudu/tools/tool_action_cluster.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action_cluster.cc b/src/kudu/tools/tool_action_cluster.cc
index d519b93..5f1f875 100644
--- a/src/kudu/tools/tool_action_cluster.cc
+++ b/src/kudu/tools/tool_action_cluster.cc
@@ -117,27 +117,30 @@ Status RunKsck(const RunnerContext& context) {
 } // anonymous namespace
 
 unique_ptr<Mode> BuildClusterMode() {
-  // TODO: the long description looks pretty horrible, need to fix.
-  string desc = "Check the health of a Kudu cluster. By default, ksck checks "
-      "that master and tablet server processes are running, and that table "
-      "metadata is consistent. Use the 'checksum' flag to check that tablet "
-      "data is consistent (also see the 'tables' and 'tablets' flags). Use the "
-      "'checksum_snapshot' along with 'checksum' if the table or tablets are "
-      "actively receiving inserts or updates.";
-  unique_ptr<Action> ksck = ActionBuilder(
-      { "ksck", desc }, &RunKsck)
-    .AddRequiredParameter({
+  string desc = "Check the health of a Kudu cluster";
+  string extra_desc = "By default, ksck checks that master and tablet server "
+      "processes are running, and that table metadata is consistent. Use the "
+      "'checksum' flag to check that tablet data is consistent (also see the "
+      "'tables' and 'tablets' flags). Use the 'checksum_snapshot' along with "
+      "'checksum' if the table or tablets are actively receiving inserts or "
+      "updates.";
+  unique_ptr<Action> ksck =
+      ActionBuilder("ksck", &RunKsck)
+      .Description(desc)
+      .ExtraDescription(extra_desc)
+      .AddRequiredParameter({
         "master_addresses",
         "Comma-separated list of Kudu Master addressess where each address is "
         "of form hostname:port" })
-    .AddOptionalParameter("checksum_scan")
-    .AddOptionalParameter("checksum_snapshot")
-    .AddOptionalParameter("color")
-    .AddOptionalParameter("tables")
-    .AddOptionalParameter("tablets")
-    .Build();
-
-  return ModeBuilder({ "cluster", "Operate on a Kudu cluster" })
+      .AddOptionalParameter("checksum_scan")
+      .AddOptionalParameter("checksum_snapshot")
+      .AddOptionalParameter("color")
+      .AddOptionalParameter("tables")
+      .AddOptionalParameter("tablets")
+      .Build();
+
+  return ModeBuilder("cluster")
+      .Description("Operate on a Kudu cluster")
       .AddAction(std::move(ksck))
       .Build();
 }

http://git-wip-us.apache.org/repos/asf/kudu/blob/25f5c215/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 3ed3efa..3b9d269 100644
--- a/src/kudu/tools/tool_action_fs.cc
+++ b/src/kudu/tools/tool_action_fs.cc
@@ -62,20 +62,23 @@ Status PrintUuid(const RunnerContext& context) {
 } // anonymous namespace
 
 unique_ptr<Mode> BuildFsMode() {
-  unique_ptr<Action> format = ActionBuilder(
-      { "format", "Format a new Kudu filesystem" }, &Format)
-    .AddOptionalParameter("fs_wal_dir")
-    .AddOptionalParameter("fs_data_dirs")
-    .AddOptionalParameter("uuid")
-    .Build();
+  unique_ptr<Action> format =
+      ActionBuilder("format", &Format)
+      .Description("Format a new Kudu filesystem")
+      .AddOptionalParameter("fs_wal_dir")
+      .AddOptionalParameter("fs_data_dirs")
+      .AddOptionalParameter("uuid")
+      .Build();
 
-  unique_ptr<Action> print_uuid = ActionBuilder(
-      { "print_uuid", "Print the UUID of a Kudu filesystem" }, &PrintUuid)
-    .AddOptionalParameter("fs_wal_dir")
-    .AddOptionalParameter("fs_data_dirs")
-    .Build();
+  unique_ptr<Action> print_uuid =
+      ActionBuilder("print_uuid", &PrintUuid)
+      .Description("Print the UUID of a Kudu filesystem")
+      .AddOptionalParameter("fs_wal_dir")
+      .AddOptionalParameter("fs_data_dirs")
+      .Build();
 
-  return ModeBuilder({ "fs", "Operate on a local Kudu filesystem" })
+  return ModeBuilder("fs")
+      .Description("Operate on a local Kudu filesystem")
       .AddAction(std::move(format))
       .AddAction(std::move(print_uuid))
       .Build();

http://git-wip-us.apache.org/repos/asf/kudu/blob/25f5c215/src/kudu/tools/tool_action_pbc.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action_pbc.cc b/src/kudu/tools/tool_action_pbc.cc
index 1411978..a190ed1 100644
--- a/src/kudu/tools/tool_action_pbc.cc
+++ b/src/kudu/tools/tool_action_pbc.cc
@@ -59,13 +59,15 @@ Status DumpPBContainerFile(const RunnerContext& context) {
 } // anonymous namespace
 
 unique_ptr<Mode> BuildPbcMode() {
-  unique_ptr<Action> dump = ActionBuilder(
-      { "dump", "Dump a PBC (protobuf container) file" }, &DumpPBContainerFile)
+  unique_ptr<Action> dump =
+      ActionBuilder("dump", &DumpPBContainerFile)
+      .Description("Dump a PBC (protobuf container) file")
       .AddOptionalParameter("oneline")
       .AddRequiredParameter({kPathArg, "path to PBC file"})
       .Build();
 
-  return ModeBuilder({ "pbc", "Operate on PBC (protobuf container) files" })
+  return ModeBuilder("pbc")
+      .Description("Operate on PBC (protobuf container) files")
       .AddAction(std::move(dump))
       .Build();
 }

http://git-wip-us.apache.org/repos/asf/kudu/blob/25f5c215/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 d019ff5..437c26f 100644
--- a/src/kudu/tools/tool_action_tablet.cc
+++ b/src/kudu/tools/tool_action_tablet.cc
@@ -189,40 +189,42 @@ Status Copy(const RunnerContext& context) {
 } // anonymous namespace
 
 unique_ptr<Mode> BuildTabletMode() {
-  unique_ptr<Action> print_replica_uuids = ActionBuilder(
-      { "print_replica_uuids",
-        "Print all replica UUIDs found in a tablet's Raft configuration" },
-      &PrintReplicaUuids)
-    .AddRequiredParameter({ "tablet_id", "Tablet identifier" })
-    .AddOptionalParameter("fs_wal_dir")
-    .AddOptionalParameter("fs_data_dirs")
-    .Build();
-
-  unique_ptr<Action> rewrite_raft_config = ActionBuilder(
-      { "rewrite_raft_config", "Rewrite a replica's Raft configuration" },
-      &RewriteRaftConfig)
-    .AddRequiredParameter({ "tablet_id", "Tablet identifier" })
-    .AddRequiredVariadicParameter({
+  unique_ptr<Action> print_replica_uuids =
+      ActionBuilder("print_replica_uuids", &PrintReplicaUuids)
+      .Description("Print all replica UUIDs found in a tablet's Raft configuration")
+      .AddRequiredParameter({ "tablet_id", "Tablet identifier" })
+      .AddOptionalParameter("fs_wal_dir")
+      .AddOptionalParameter("fs_data_dirs")
+      .Build();
+
+  unique_ptr<Action> rewrite_raft_config =
+      ActionBuilder("rewrite_raft_config", &RewriteRaftConfig)
+      .Description("Rewrite a replica's Raft configuration")
+      .AddRequiredParameter({ "tablet_id", "Tablet identifier" })
+      .AddRequiredVariadicParameter({
         "peers", "List of peers where each peer is of form uuid:hostname:port" })
-    .AddOptionalParameter("fs_wal_dir")
-    .AddOptionalParameter("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)
-    .AddRequiredParameter({ "tablet_id", "Tablet identifier" })
-    .AddRequiredParameter({ "source", "Source RPC address of form hostname:port" })
-    .AddOptionalParameter("fs_wal_dir")
-    .AddOptionalParameter("fs_data_dirs")
-    .Build();
-
-  return ModeBuilder({ "tablet", "Operate on a local Kudu replica" })
+      .AddOptionalParameter("fs_wal_dir")
+      .AddOptionalParameter("fs_data_dirs")
+      .Build();
+
+  unique_ptr<Mode> cmeta =
+      ModeBuilder("cmeta")
+      .Description("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)
+      .Description("Copy a replica from a remote server")
+      .AddRequiredParameter({ "tablet_id", "Tablet identifier" })
+      .AddRequiredParameter({ "source", "Source RPC address of form hostname:port" })
+      .AddOptionalParameter("fs_wal_dir")
+      .AddOptionalParameter("fs_data_dirs")
+      .Build();
+
+  return ModeBuilder("tablet")
+      .Description("Operate on a local Kudu replica")
       .AddMode(std::move(cmeta))
       .AddAction(std::move(copy))
       .Build();

http://git-wip-us.apache.org/repos/asf/kudu/blob/25f5c215/src/kudu/tools/tool_main.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_main.cc b/src/kudu/tools/tool_main.cc
index 70fdfc0..3a6f4c8 100644
--- a/src/kudu/tools/tool_main.cc
+++ b/src/kudu/tools/tool_main.cc
@@ -109,13 +109,13 @@ int DispatchCommand(const vector<Mode*>& chain,
 }
 
 int RunTool(int argc, char** argv, bool show_help) {
-  unique_ptr<Mode> root =
-      ModeBuilder({ argv[0], "" }) // root mode description isn't printed
-      .AddMode(BuildClusterMode())
-      .AddMode(BuildFsMode())
-      .AddMode(BuildPbcMode())
-      .AddMode(BuildTabletMode())
-      .Build();
+  unique_ptr<Mode> root = ModeBuilder(argv[0])
+    .Description("doesn't matter") // root mode description isn't printed
+    .AddMode(BuildClusterMode())
+    .AddMode(BuildFsMode())
+    .AddMode(BuildPbcMode())
+    .AddMode(BuildTabletMode())
+    .Build();
 
   // Initialize arg parsing state.
   vector<Mode*> chain = { root.get() };