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/09/13 02:18:29 UTC

[3/3] kudu git commit: tool: port ts-cli

tool: port ts-cli

I chose to expose common server functionality in new 'master' and 'tserver'
modes rather than consolidating them into a shared 'server' mode; I found
this to be more more intuitive.

I also snuck in a change to ksck to use FLAGS_timeout_ms for RPC timeouts
in client-based operations.

Change-Id: Ifb5a59fd690c2dd09e4e76858469d81f9d501371
Reviewed-on: http://gerrit.cloudera.org:8080/4373
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/14cd22a1
Tree: http://git-wip-us.apache.org/repos/asf/kudu/tree/14cd22a1
Diff: http://git-wip-us.apache.org/repos/asf/kudu/diff/14cd22a1

Branch: refs/heads/master
Commit: 14cd22a10b33545c5136ad68ebcf5cc0543ac525
Parents: 9cb1bca
Author: Adar Dembo <ad...@cloudera.com>
Authored: Sun Sep 11 15:13:23 2016 -0700
Committer: Todd Lipcon <to...@apache.org>
Committed: Tue Sep 13 02:14:29 2016 +0000

----------------------------------------------------------------------
 build-support/dist_test.py                   |   1 -
 docs/release_notes.adoc                      |   3 +
 src/kudu/client/scan_batch.h                 |   4 +-
 src/kudu/client/schema.h                     |   6 +-
 src/kudu/tablet/tablet_peer.h                |   4 +-
 src/kudu/tools/CMakeLists.txt                |  10 +-
 src/kudu/tools/ksck_remote.cc                |   1 +
 src/kudu/tools/kudu-tool-test.cc             |  28 ++
 src/kudu/tools/kudu-ts-cli-test.cc           |  14 +-
 src/kudu/tools/tool_action.h                 |   3 +
 src/kudu/tools/tool_action_common.cc         | 132 ++++++
 src/kudu/tools/tool_action_common.h          |  40 ++
 src/kudu/tools/tool_action_master.cc         |  97 +++++
 src/kudu/tools/tool_action_remote_replica.cc | 331 +++++++++++++++
 src/kudu/tools/tool_action_tablet.cc         |  11 +-
 src/kudu/tools/tool_action_tserver.cc        |  98 +++++
 src/kudu/tools/tool_main.cc                  |   3 +
 src/kudu/tools/ts-cli.cc                     | 494 ----------------------
 18 files changed, 758 insertions(+), 522 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kudu/blob/14cd22a1/build-support/dist_test.py
----------------------------------------------------------------------
diff --git a/build-support/dist_test.py b/build-support/dist_test.py
index d19a78f..003a5dd 100755
--- a/build-support/dist_test.py
+++ b/build-support/dist_test.py
@@ -69,7 +69,6 @@ DEPS_FOR_ALL = \
      # TODO: declare these dependencies per-test.
      "build/latest/bin/kudu-tserver",
      "build/latest/bin/kudu-master",
-     "build/latest/bin/kudu-ts-cli",
 
      # parser-test requires these data files.
      # TODO: again, we should do this with some per-test metadata file.

http://git-wip-us.apache.org/repos/asf/kudu/blob/14cd22a1/docs/release_notes.adoc
----------------------------------------------------------------------
diff --git a/docs/release_notes.adoc b/docs/release_notes.adoc
index 9fd83ef..0261d99 100644
--- a/docs/release_notes.adoc
+++ b/docs/release_notes.adoc
@@ -130,6 +130,9 @@ Kudu 1.0.0 are not supported.
 - The `kudu-fs_dump` tool has been removed. The same functionality is now
   implemented as `kudu fs dump`.
 
+- The `kudu-ts-cli` tool has been removed. The same functionality is now
+  implemented within `kudu master`, `kudu remote_replica`, and `kudu tserver`.
+
 - The `kudu-fs_list` tool has been removed and some similar useful
   functionality has been moved under 'kudu local_replica'.
 

http://git-wip-us.apache.org/repos/asf/kudu/blob/14cd22a1/src/kudu/client/scan_batch.h
----------------------------------------------------------------------
diff --git a/src/kudu/client/scan_batch.h b/src/kudu/client/scan_batch.h
index 9b1433d..b4d91f6 100644
--- a/src/kudu/client/scan_batch.h
+++ b/src/kudu/client/scan_batch.h
@@ -33,7 +33,7 @@ namespace kudu {
 class Schema;
 
 namespace tools {
-class TsAdminClient;
+class ReplicaDumper;
 } // namespace tools
 
 namespace client {
@@ -119,7 +119,7 @@ class KUDU_EXPORT KuduScanBatch {
  private:
   class KUDU_NO_EXPORT Data;
   friend class KuduScanner;
-  friend class kudu::tools::TsAdminClient;
+  friend class tools::ReplicaDumper;
 
   Data* data_;
   DISALLOW_COPY_AND_ASSIGN(KuduScanBatch);

http://git-wip-us.apache.org/repos/asf/kudu/blob/14cd22a1/src/kudu/client/schema.h
----------------------------------------------------------------------
diff --git a/src/kudu/client/schema.h b/src/kudu/client/schema.h
index 70ba676..a09a88d 100644
--- a/src/kudu/client/schema.h
+++ b/src/kudu/client/schema.h
@@ -33,7 +33,7 @@ class TestWorkload;
 
 namespace tools {
 class RemoteKsckMaster;
-class TsAdminClient;
+class ReplicaDumper;
 }
 
 namespace client {
@@ -490,8 +490,8 @@ class KUDU_EXPORT KuduSchema {
   friend class internal::LookupRpc;
   friend class internal::MetaCacheEntry;
   friend class internal::WriteRpc;
-  friend class kudu::tools::RemoteKsckMaster;
-  friend class kudu::tools::TsAdminClient;
+  friend class tools::RemoteKsckMaster;
+  friend class tools::ReplicaDumper;
 
   friend KuduSchema KuduSchemaFromSchema(const Schema& schema);
 

http://git-wip-us.apache.org/repos/asf/kudu/blob/14cd22a1/src/kudu/tablet/tablet_peer.h
----------------------------------------------------------------------
diff --git a/src/kudu/tablet/tablet_peer.h b/src/kudu/tablet/tablet_peer.h
index 8d72e72..c43c83a 100644
--- a/src/kudu/tablet/tablet_peer.h
+++ b/src/kudu/tablet/tablet_peer.h
@@ -196,9 +196,7 @@ class TabletPeer : public RefCountedThreadSafe<TabletPeer>,
   // etc. For use in places like the Web UI.
   std::string HumanReadableState() const;
 
-  // Adds list of transactions in-flight at the time of the call to
-  // 'out'. TransactionStatusPB objects are used to allow this method
-  // to be used by both the web-UI and ts-cli.
+  // Adds list of transactions in-flight at the time of the call to 'out'.
   void GetInFlightTransactions(Transaction::TraceType trace_type,
                                std::vector<consensus::TransactionStatusPB>* out) const;
 

http://git-wip-us.apache.org/repos/asf/kudu/blob/14cd22a1/src/kudu/tools/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/src/kudu/tools/CMakeLists.txt b/src/kudu/tools/CMakeLists.txt
index 0ce2899..54159dc 100644
--- a/src/kudu/tools/CMakeLists.txt
+++ b/src/kudu/tools/CMakeLists.txt
@@ -44,10 +44,6 @@ target_link_libraries(insert-generated-rows
   kudu_tools_util
   ${LINK_LIBS})
 
-add_executable(kudu-ts-cli ts-cli.cc)
-target_link_libraries(kudu-ts-cli
-  ${LINK_LIBS})
-
 add_library(ksck
     ksck.cc
     ksck_remote.cc
@@ -68,9 +64,12 @@ add_executable(kudu
   tool_action_common.cc
   tool_action_fs.cc
   tool_action_local_replica.cc
+  tool_action_master.cc
   tool_action_pbc.cc
+  tool_action_remote_replica.cc
   tool_action_table.cc
   tool_action_tablet.cc
+  tool_action_tserver.cc
   tool_action_wal.cc
   tool_main.cc
 )
@@ -79,6 +78,7 @@ target_link_libraries(kudu
   gutil
   krpc
   ksck
+  kudu_client
   kudu_common
   kudu_fs
   kudu_util
@@ -105,5 +105,5 @@ ADD_KUDU_TEST_DEPENDENCIES(kudu-tool-test
   kudu)
 ADD_KUDU_TEST(kudu-ts-cli-test)
 ADD_KUDU_TEST_DEPENDENCIES(kudu-ts-cli-test
-  kudu-ts-cli)
+  kudu)
 

http://git-wip-us.apache.org/repos/asf/kudu/blob/14cd22a1/src/kudu/tools/ksck_remote.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/ksck_remote.cc b/src/kudu/tools/ksck_remote.cc
index c2cc85c..1dddfec 100644
--- a/src/kudu/tools/ksck_remote.cc
+++ b/src/kudu/tools/ksck_remote.cc
@@ -262,6 +262,7 @@ void RemoteKsckTabletServer::RunTabletChecksumScanAsync(
 Status RemoteKsckMaster::Connect() {
   client::sp::shared_ptr<KuduClient> client;
   KuduClientBuilder builder;
+  builder.default_rpc_timeout(GetDefaultTimeout());
   builder.master_server_addrs(master_addresses_);
   return builder.Build(&client_);
 }

http://git-wip-us.apache.org/repos/asf/kudu/blob/14cd22a1/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 9720d4a..1db59df 100644
--- a/src/kudu/tools/kudu-tool-test.cc
+++ b/src/kudu/tools/kudu-tool-test.cc
@@ -181,9 +181,12 @@ TEST_F(ToolTest, TestTopLevelHelp) {
       "cluster.*Kudu cluster",
       "fs.*Kudu filesystem",
       "local_replica.*Kudu replicas",
+      "master.*Kudu Master",
       "pbc.*protobuf container",
+      "remote_replica.*replicas on a Kudu Tablet Server",
       "table.*Kudu tables",
       "tablet.*Kudu tablets",
+      "tserver.*Kudu Tablet Server",
       "wal.*write-ahead log"
   };
   NO_FATALS(RunTestHelp("", kTopLevelRegexes));
@@ -244,12 +247,29 @@ TEST_F(ToolTest, TestModeHelp) {
     NO_FATALS(RunTestHelp("cluster", kClusterModeRegexes));
   }
   {
+    const vector<string> kMasterModeRegexes = {
+        "set_flag.*Change a gflag value",
+        "status.*Get the status",
+        "timestamp.*Get the current timestamp"
+    };
+    NO_FATALS(RunTestHelp("master", kMasterModeRegexes));
+  }
+  {
     const vector<string> kPbcModeRegexes = {
         "dump.*Dump a PBC",
     };
     NO_FATALS(RunTestHelp("pbc", kPbcModeRegexes));
   }
   {
+    const vector<string> kRemoteReplicaModeRegexes = {
+        "check.*Check if all replicas",
+        "delete.*Delete a replica",
+        "dump.*Dump the data of a replica",
+        "list.*List all replicas"
+    };
+    NO_FATALS(RunTestHelp("remote_replica", kRemoteReplicaModeRegexes));
+  }
+  {
     const vector<string> kTableModeRegexes = {
         "delete.*Delete a table",
         "list.*List all tables",
@@ -271,6 +291,14 @@ TEST_F(ToolTest, TestModeHelp) {
     NO_FATALS(RunTestHelp("tablet change_config", kChangeConfigModeRegexes));
   }
   {
+    const vector<string> kTServerModeRegexes = {
+        "set_flag.*Change a gflag value",
+        "status.*Get the status",
+        "timestamp.*Get the current timestamp"
+    };
+    NO_FATALS(RunTestHelp("tserver", kTServerModeRegexes));
+  }
+  {
     const vector<string> kWalModeRegexes = {
         "dump.*Dump a WAL",
     };

http://git-wip-us.apache.org/repos/asf/kudu/blob/14cd22a1/src/kudu/tools/kudu-ts-cli-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/kudu-ts-cli-test.cc b/src/kudu/tools/kudu-ts-cli-test.cc
index 6300781..cbcf190 100644
--- a/src/kudu/tools/kudu-ts-cli-test.cc
+++ b/src/kudu/tools/kudu-ts-cli-test.cc
@@ -36,7 +36,7 @@ using strings::Substitute;
 namespace kudu {
 namespace tools {
 
-static const char* const kTsCliToolName = "kudu-ts-cli";
+static const char* const kTsCliToolName = "kudu";
 
 class KuduTsCliTest : public ExternalMiniClusterITestBase {
  protected:
@@ -78,9 +78,9 @@ TEST_F(KuduTsCliTest, TestDeleteTablet) {
   string out;
   ASSERT_OK(Subprocess::Call({
     GetTsCliToolPath(),
-    "--server_address",
+    "remote_replica",
+    "delete",
     cluster_->tablet_server(0)->bound_rpc_addr().ToString(),
-    "delete_tablet",
     tablet_id,
     "Deleting for kudu-ts-cli-test"
   }, &out));
@@ -117,9 +117,9 @@ TEST_F(KuduTsCliTest, TestDumpTablet) {
   // Test for dump_tablet when there is no data in tablet.
   ASSERT_OK(Subprocess::Call({
     GetTsCliToolPath(),
-    "--server_address",
+    "remote_replica",
+    "dump",
     cluster_->tablet_server(0)->bound_rpc_addr().ToString(),
-    "dump_tablet",
     tablet_id
   }, &out));
   ASSERT_EQ("", out);
@@ -133,9 +133,9 @@ TEST_F(KuduTsCliTest, TestDumpTablet) {
   ASSERT_OK(WaitForServersToAgree(timeout, ts_map_, tablet_id, workload.batches_completed()));
   ASSERT_OK(Subprocess::Call({
     GetTsCliToolPath(),
-    "--server_address",
+    "remote_replica",
+    "dump",
     cluster_->tablet_server(0)->bound_rpc_addr().ToString(),
-    "dump_tablet",
     tablet_id
   }, &out));
 

http://git-wip-us.apache.org/repos/asf/kudu/blob/14cd22a1/src/kudu/tools/tool_action.h
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action.h b/src/kudu/tools/tool_action.h
index c03e5d4..95aa162 100644
--- a/src/kudu/tools/tool_action.h
+++ b/src/kudu/tools/tool_action.h
@@ -278,9 +278,12 @@ class Action {
 std::unique_ptr<Mode> BuildClusterMode();
 std::unique_ptr<Mode> BuildFsMode();
 std::unique_ptr<Mode> BuildLocalReplicaMode();
+std::unique_ptr<Mode> BuildMasterMode();
 std::unique_ptr<Mode> BuildPbcMode();
+std::unique_ptr<Mode> BuildRemoteReplicaMode();
 std::unique_ptr<Mode> BuildTableMode();
 std::unique_ptr<Mode> BuildTabletMode();
+std::unique_ptr<Mode> BuildTServerMode();
 std::unique_ptr<Mode> BuildWalMode();
 
 } // namespace tools

http://git-wip-us.apache.org/repos/asf/kudu/blob/14cd22a1/src/kudu/tools/tool_action_common.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action_common.cc b/src/kudu/tools/tool_action_common.cc
index 1daa12d..bbcf981 100644
--- a/src/kudu/tools/tool_action_common.cc
+++ b/src/kudu/tools/tool_action_common.cc
@@ -18,6 +18,7 @@
 #include "kudu/tools/tool_action_common.h"
 
 #include <iostream>
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -29,16 +30,32 @@
 #include "kudu/common/row_operations.h"
 #include "kudu/common/wire_protocol.h"
 #include "kudu/consensus/consensus.pb.h"
+#include "kudu/consensus/consensus.proxy.h"
 #include "kudu/consensus/log.pb.h"
 #include "kudu/consensus/log_util.h"
 #include "kudu/gutil/ref_counted.h"
 #include "kudu/gutil/strings/numbers.h"
+#include "kudu/rpc/messenger.h"
+#include "kudu/rpc/rpc_controller.h"
 #include "kudu/rpc/rpc_header.pb.h"
+#include "kudu/server/server_base.pb.h"
+#include "kudu/server/server_base.proxy.h"
 #include "kudu/tserver/tserver.pb.h"
+#include "kudu/tserver/tserver_admin.proxy.h"
+#include "kudu/tserver/tserver_service.proxy.h"
 #include "kudu/util/memory/arena.h"
+#include "kudu/util/monotime.h"
+#include "kudu/util/net/net_util.h"
+#include "kudu/util/net/sockaddr.h"
 #include "kudu/util/pb_util.h"
 #include "kudu/util/status.h"
 
+DECLARE_int64(timeout_ms); // defined in ksck
+
+DEFINE_bool(force, false, "If true, allows the set_flag command to set a flag "
+            "which is not explicitly marked as runtime-settable. Such flag "
+            "changes may be simply ignored on the server, or may cause the "
+            "server to crash.");
 DEFINE_bool(print_meta, true, "Include metadata in output");
 DEFINE_string(print_entries, "decoded",
               "How to print entries:\n"
@@ -53,16 +70,32 @@ DEFINE_int32(truncate_data, 100,
 namespace kudu {
 namespace tools {
 
+using consensus::ConsensusServiceProxy;
 using consensus::ReplicateMsg;
 using log::LogEntryPB;
 using log::LogEntryReader;
 using log::ReadableLogSegment;
+using rpc::Messenger;
+using rpc::MessengerBuilder;
 using rpc::RequestIdPB;
+using rpc::RpcController;
+using server::GenericServiceProxy;
+using server::GetStatusRequestPB;
+using server::GetStatusResponsePB;
+using server::ServerClockRequestPB;
+using server::ServerClockResponsePB;
+using server::ServerStatusPB;
+using server::SetFlagRequestPB;
+using server::SetFlagResponsePB;
 using std::cout;
 using std::cerr;
 using std::endl;
+using std::shared_ptr;
 using std::string;
+using std::unique_ptr;
 using std::vector;
+using tserver::TabletServerAdminServiceProxy;
+using tserver::TabletServerServiceProxy;
 using tserver::WriteRequestPB;
 
 namespace {
@@ -170,6 +203,55 @@ Status PrintDecoded(const LogEntryPB& entry, const Schema& tablet_schema) {
 
 } // anonymous namespace
 
+template<class ProxyClass>
+Status BuildProxy(const string& address,
+                  uint16_t default_port,
+                  unique_ptr<ProxyClass>* proxy) {
+  HostPort hp;
+  RETURN_NOT_OK(hp.ParseString(address, default_port));
+  shared_ptr<Messenger> messenger;
+  RETURN_NOT_OK(MessengerBuilder("tool").Build(&messenger));
+
+  vector<Sockaddr> resolved;
+  RETURN_NOT_OK(hp.ResolveAddresses(&resolved));
+
+  proxy->reset(new ProxyClass(messenger, resolved[0]));
+  return Status::OK();
+}
+
+// Explicit specialization for callers outside this compilation unit.
+template
+Status BuildProxy(const string& address,
+                  uint16_t default_port,
+                  unique_ptr<ConsensusServiceProxy>* proxy);
+template
+Status BuildProxy(const string& address,
+                  uint16_t default_port,
+                  unique_ptr<TabletServerServiceProxy>* proxy);
+template
+Status BuildProxy(const string& address,
+                  uint16_t default_port,
+                  unique_ptr<TabletServerAdminServiceProxy>* proxy);
+
+Status GetServerStatus(const string& address, uint16_t default_port,
+                       ServerStatusPB* status) {
+  unique_ptr<GenericServiceProxy> proxy;
+  RETURN_NOT_OK(BuildProxy(address, default_port, &proxy));
+
+  GetStatusRequestPB req;
+  GetStatusResponsePB resp;
+  RpcController rpc;
+  rpc.set_timeout(MonoDelta::FromMilliseconds(FLAGS_timeout_ms));
+
+  RETURN_NOT_OK(proxy->GetStatus(req, &resp, &rpc));
+  if (!resp.has_status()) {
+    return Status::Incomplete("Server response did not contain status",
+                              proxy->ToString());
+  }
+  *status = resp.status();
+  return Status::OK();
+}
+
 Status PrintSegment(const scoped_refptr<ReadableLogSegment>& segment) {
   PrintEntryType print_type = ParsePrintType();
   if (FLAGS_print_meta) {
@@ -206,5 +288,55 @@ Status PrintSegment(const scoped_refptr<ReadableLogSegment>& segment) {
   return Status::OK();
 }
 
+Status SetServerFlag(const string& address, uint16_t default_port,
+                     const string& flag, const string& value) {
+  unique_ptr<GenericServiceProxy> proxy;
+  RETURN_NOT_OK(BuildProxy(address, default_port, &proxy));
+
+  SetFlagRequestPB req;
+  SetFlagResponsePB resp;
+  RpcController rpc;
+  rpc.set_timeout(MonoDelta::FromMilliseconds(FLAGS_timeout_ms));
+
+  req.set_flag(flag);
+  req.set_value(value);
+  req.set_force(FLAGS_force);
+
+  RETURN_NOT_OK(proxy->SetFlag(req, &resp, &rpc));
+  switch (resp.result()) {
+    case server::SetFlagResponsePB::SUCCESS:
+      return Status::OK();
+    case server::SetFlagResponsePB::NOT_SAFE:
+      return Status::RemoteError(resp.msg() +
+                                 " (use --force flag to allow anyway)");
+    default:
+      return Status::RemoteError(resp.ShortDebugString());
+  }
+}
+
+Status PrintServerStatus(const string& address, uint16_t default_port) {
+  ServerStatusPB status;
+  RETURN_NOT_OK(GetServerStatus(address, default_port, &status));
+  cout << status.DebugString() << endl;
+  return Status::OK();
+}
+
+Status PrintServerTimestamp(const string& address, uint16_t default_port) {
+  unique_ptr<GenericServiceProxy> proxy;
+  RETURN_NOT_OK(BuildProxy(address, default_port, &proxy));
+
+  ServerClockRequestPB req;
+  ServerClockResponsePB resp;
+  RpcController rpc;
+  rpc.set_timeout(MonoDelta::FromMilliseconds(FLAGS_timeout_ms));
+  RETURN_NOT_OK(proxy->ServerClock(req, &resp, &rpc));
+  if (!resp.has_timestamp()) {
+    return Status::Incomplete("Server response did not contain timestamp",
+                              proxy->ToString());
+  }
+  cout << resp.timestamp() << endl;
+  return Status::OK();
+}
+
 } // namespace tools
 } // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/14cd22a1/src/kudu/tools/tool_action_common.h
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action_common.h b/src/kudu/tools/tool_action_common.h
index 0391bb3..d51f656 100644
--- a/src/kudu/tools/tool_action_common.h
+++ b/src/kudu/tools/tool_action_common.h
@@ -17,6 +17,9 @@
 
 #pragma once
 
+#include <memory>
+#include <string>
+
 #include "kudu/gutil/ref_counted.h"
 #include "kudu/util/status.h"
 
@@ -26,10 +29,30 @@ namespace log {
 class ReadableLogSegment;
 } // namespace log
 
+namespace server {
+class ServerStatusPB;
+} // namespace server
+
 namespace tools {
 
 // Utility methods used by multiple actions across different modes.
 
+// Builds a proxy to a Kudu server running at 'address', returning it in
+// 'proxy'.
+//
+// If 'address' does not contain a port, 'default_port' is used instead.
+template<class ProxyClass>
+Status BuildProxy(const std::string& address,
+                  uint16_t default_port,
+                  std::unique_ptr<ProxyClass>* proxy);
+
+// Get the current status of the Kudu server running at 'address', storing it
+// in 'status'.
+//
+// If 'address' does not contain a port, 'default_port' is used instead.
+Status GetServerStatus(const std::string& address, uint16_t default_port,
+                       server::ServerStatusPB* status);
+
 // Prints the contents of a WAL segment to stdout.
 //
 // The following gflags affect the output:
@@ -38,5 +61,22 @@ namespace tools {
 // - truncate_data: how many bytes to print for each data field.
 Status PrintSegment(const scoped_refptr<log::ReadableLogSegment>& segment);
 
+// Print the current status of the Kudu server running at 'address'.
+//
+// If 'address' does not contain a port, 'default_port' is used instead.
+Status PrintServerStatus(const std::string& address, uint16_t default_port);
+
+// Print the current timestamp of the Kudu server running at 'address'.
+//
+// If 'address' does not contain a port, 'default_port' is used instead.
+Status PrintServerTimestamp(const std::string& address, uint16_t default_port);
+
+// Changes the value of the gflag given by 'flag' to the value in 'value' on
+// the Kudu server running at 'address'.
+//
+// If 'address' does not contain a port, 'default_port' is used instead.
+Status SetServerFlag(const std::string& address, uint16_t default_port,
+                     const std::string& flag, const std::string& value);
+
 } // namespace tools
 } // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/14cd22a1/src/kudu/tools/tool_action_master.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action_master.cc b/src/kudu/tools/tool_action_master.cc
new file mode 100644
index 0000000..aea53d1
--- /dev/null
+++ b/src/kudu/tools/tool_action_master.cc
@@ -0,0 +1,97 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "kudu/tools/tool_action.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include <gflags/gflags.h>
+
+#include "kudu/gutil/map-util.h"
+#include "kudu/master/master.h"
+#include "kudu/tools/tool_action_common.h"
+#include "kudu/util/status.h"
+
+namespace kudu {
+namespace tools {
+
+using std::string;
+using std::unique_ptr;
+
+namespace {
+
+const char* const kMasterAddressArg = "master_address";
+const char* const kMasterAddressDesc = "Address of a Kudu Master of form "
+    "'hostname:port'. Port may be omitted if the Master is bound to the "
+    "default port.";
+const char* const kFlagArg = "flag";
+const char* const kValueArg = "value";
+
+Status MasterSetFlag(const RunnerContext& context) {
+  string address = FindOrDie(context.required_args, kMasterAddressArg);
+  string flag = FindOrDie(context.required_args, kFlagArg);
+  string value = FindOrDie(context.required_args, kValueArg);
+  return SetServerFlag(address, master::Master::kDefaultPort, flag, value);
+}
+
+Status MasterStatus(const RunnerContext& context) {
+  string address = FindOrDie(context.required_args, kMasterAddressArg);
+  return PrintServerStatus(address, master::Master::kDefaultPort);
+}
+
+Status MasterTimestamp(const RunnerContext& context) {
+  string address = FindOrDie(context.required_args, kMasterAddressArg);
+  return PrintServerTimestamp(address, master::Master::kDefaultPort);
+}
+
+} // anonymous namespace
+
+unique_ptr<Mode> BuildMasterMode() {
+  unique_ptr<Action> set_flag =
+      ActionBuilder("set_flag", &MasterSetFlag)
+      .Description("Change a gflag value on a Kudu Master")
+      .AddRequiredParameter({ kMasterAddressArg, kMasterAddressDesc })
+      .AddRequiredParameter({ kFlagArg, "Name of the gflag" })
+      .AddRequiredParameter({ kValueArg, "New value for the gflag" })
+      .AddOptionalParameter("force")
+      .Build();
+
+  unique_ptr<Action> status =
+      ActionBuilder("status", &MasterStatus)
+      .Description("Get the status of a Kudu Master")
+      .AddRequiredParameter({ kMasterAddressArg, kMasterAddressDesc })
+      .Build();
+
+  unique_ptr<Action> timestamp =
+      ActionBuilder("timestamp", &MasterTimestamp)
+      .Description("Get the current timestamp of a Kudu Master")
+      .AddRequiredParameter({ kMasterAddressArg, kMasterAddressDesc })
+      .Build();
+
+  return ModeBuilder("master")
+      .Description("Operate on a Kudu Master")
+      .AddAction(std::move(set_flag))
+      .AddAction(std::move(status))
+      .AddAction(std::move(timestamp))
+      .Build();
+}
+
+} // namespace tools
+} // namespace kudu
+

http://git-wip-us.apache.org/repos/asf/kudu/blob/14cd22a1/src/kudu/tools/tool_action_remote_replica.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action_remote_replica.cc b/src/kudu/tools/tool_action_remote_replica.cc
new file mode 100644
index 0000000..8ee7cb9
--- /dev/null
+++ b/src/kudu/tools/tool_action_remote_replica.cc
@@ -0,0 +1,331 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "kudu/tools/tool_action.h"
+
+#include <iostream>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <gflags/gflags.h>
+
+#include "kudu/client/client.h"
+#include "kudu/client/row_result.h"
+#include "kudu/client/scan_batch.h"
+#include "kudu/client/scanner-internal.h"
+#include "kudu/common/partition.h"
+#include "kudu/common/schema.h"
+#include "kudu/common/wire_protocol.h"
+#include "kudu/gutil/gscoped_ptr.h"
+#include "kudu/gutil/map-util.h"
+#include "kudu/gutil/strings/human_readable.h"
+#include "kudu/server/server_base.pb.h"
+#include "kudu/tablet/tablet.pb.h"
+#include "kudu/tools/tool_action_common.h"
+#include "kudu/tserver/tablet_server.h"
+#include "kudu/tserver/tserver.pb.h"
+#include "kudu/tserver/tserver_admin.proxy.h"
+#include "kudu/tserver/tserver_service.proxy.h"
+#include "kudu/rpc/messenger.h"
+#include "kudu/rpc/rpc_controller.h"
+#include "kudu/util/net/net_util.h"
+#include "kudu/util/net/sockaddr.h"
+#include "kudu/util/status.h"
+
+DECLARE_int64(timeout_ms); // defined in ksck
+
+namespace kudu {
+namespace tools {
+
+using client::KuduRowResult;
+using client::KuduScanBatch;
+using client::KuduSchema;
+using rpc::Messenger;
+using rpc::MessengerBuilder;
+using rpc::RpcController;
+using server::ServerStatusPB;
+using std::cerr;
+using std::cout;
+using std::endl;
+using std::shared_ptr;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+using tablet::TabletStatusPB;
+using tserver::DeleteTabletRequestPB;
+using tserver::DeleteTabletResponsePB;
+using tserver::ListTabletsRequestPB;
+using tserver::ListTabletsResponsePB;
+using tserver::NewScanRequestPB;
+using tserver::ScanRequestPB;
+using tserver::ScanResponsePB;
+using tserver::TabletServerAdminServiceProxy;
+using tserver::TabletServerServiceProxy;
+
+// This class only exists so that Dump() can easily be friended with
+// KuduSchema and KuduScanBatch.
+class ReplicaDumper {
+ public:
+  static Status Dump(const Schema& schema,
+                     const string& tablet_id,
+                     TabletServerServiceProxy* proxy) {
+    KuduSchema client_schema(schema);
+
+    ScanRequestPB req;
+    ScanResponsePB resp;
+
+    NewScanRequestPB* new_req = req.mutable_new_scan_request();
+    RETURN_NOT_OK(SchemaToColumnPBs(
+        schema, new_req->mutable_projected_columns(),
+        SCHEMA_PB_WITHOUT_IDS | SCHEMA_PB_WITHOUT_STORAGE_ATTRIBUTES));
+    new_req->set_tablet_id(tablet_id);
+    new_req->set_cache_blocks(false);
+    new_req->set_order_mode(ORDERED);
+    new_req->set_read_mode(READ_AT_SNAPSHOT);
+
+    do {
+      RpcController rpc;
+      rpc.set_timeout(MonoDelta::FromMilliseconds(FLAGS_timeout_ms));
+      RETURN_NOT_OK_PREPEND(proxy->Scan(req, &resp, &rpc), "Scan() failed");
+
+      if (resp.has_error()) {
+        return Status::IOError("Failed to read: ",
+                               resp.error().ShortDebugString());
+      }
+
+      // The first response has a scanner ID. We use this for all subsequent
+      // responses.
+      if (resp.has_scanner_id()) {
+        req.set_scanner_id(resp.scanner_id());
+        req.clear_new_scan_request();
+      }
+      req.set_call_seq_id(req.call_seq_id() + 1);
+
+      // Nothing to process from this scan result.
+      if (!resp.has_data()) {
+        continue;
+      }
+
+      KuduScanBatch::Data results;
+      RETURN_NOT_OK(results.Reset(&rpc,
+                                  &schema,
+                                  &client_schema,
+                                  make_gscoped_ptr(resp.release_data())));
+      vector<KuduRowResult> rows;
+      results.ExtractRows(&rows);
+      for (const auto& r : rows) {
+        cout << r.ToString() << endl;
+      }
+    } while (resp.has_more_results());
+    return Status::OK();
+  }
+};
+
+namespace {
+
+const char* const kReasonArg = "reason";
+const char* const kTabletArg = "tablet_id";
+const char* const kTabletDesc = "Tablet identifier";
+const char* const kTServerAddressArg = "tserver_address";
+const char* const kTServerAddressDesc = "Address of a Kudu Tablet Server of "
+    "form 'hostname:port'. Port may be omitted if the Tablet Server is bound "
+    "to the default port.";
+
+Status GetReplicas(TabletServerServiceProxy* proxy,
+                   vector<ListTabletsResponsePB::StatusAndSchemaPB>* replicas) {
+  ListTabletsRequestPB req;
+  ListTabletsResponsePB resp;
+  RpcController rpc;
+  rpc.set_timeout(MonoDelta::FromMilliseconds(FLAGS_timeout_ms));
+
+  RETURN_NOT_OK(proxy->ListTablets(req, &resp, &rpc));
+  if (resp.has_error()) {
+    return StatusFromPB(resp.error().status());
+  }
+
+  replicas->assign(resp.status_and_schema().begin(),
+                   resp.status_and_schema().end());
+  return Status::OK();
+}
+
+Status CheckReplicas(const RunnerContext& context) {
+  string address = FindOrDie(context.required_args, kTServerAddressArg);
+
+  unique_ptr<TabletServerServiceProxy> proxy;
+  RETURN_NOT_OK(BuildProxy(address, tserver::TabletServer::kDefaultPort,
+                           &proxy));
+
+  vector<ListTabletsResponsePB::StatusAndSchemaPB> replicas;
+  RETURN_NOT_OK(GetReplicas(proxy.get(), &replicas));
+
+  bool all_running = true;
+  for (const auto& r : replicas) {
+    TabletStatusPB rs = r.tablet_status();
+    if (rs.state() != tablet::RUNNING) {
+      cerr << "Tablet id: " << rs.tablet_id() << " is "
+           << tablet::TabletStatePB_Name(rs.state()) << endl;
+      all_running = false;
+    }
+  }
+
+  if (all_running) {
+    cout << "All tablets are running" << endl;
+    return Status::OK();
+  } else {
+    return Status::IllegalState("Not all tablets are running");
+  }
+}
+
+Status DeleteReplica(const RunnerContext& context) {
+  string address = FindOrDie(context.required_args, kTServerAddressArg);
+  string tablet_id = FindOrDie(context.required_args, kTabletArg);
+  string reason = FindOrDie(context.required_args, kReasonArg);
+
+  ServerStatusPB status;
+  RETURN_NOT_OK(GetServerStatus(address, tserver::TabletServer::kDefaultPort,
+                                &status));
+
+  unique_ptr<TabletServerAdminServiceProxy> proxy;
+  RETURN_NOT_OK(BuildProxy(address, tserver::TabletServer::kDefaultPort,
+                           &proxy));
+
+  DeleteTabletRequestPB req;
+  DeleteTabletResponsePB resp;
+  RpcController rpc;
+  rpc.set_timeout(MonoDelta::FromMilliseconds(FLAGS_timeout_ms));
+
+  req.set_tablet_id(tablet_id);
+  req.set_dest_uuid(status.node_instance().permanent_uuid());
+  req.set_reason(reason);
+  req.set_delete_type(tablet::TABLET_DATA_TOMBSTONED);
+  RETURN_NOT_OK_PREPEND(proxy->DeleteTablet(req, &resp, &rpc),
+                        "DeleteTablet() failed");
+  if (resp.has_error()) {
+    return Status::IOError("Failed to delete tablet: ",
+                           resp.error().ShortDebugString());
+  }
+  return Status::OK();
+}
+
+Status DumpReplica(const RunnerContext& context) {
+  string address = FindOrDie(context.required_args, kTServerAddressArg);
+  string tablet_id = FindOrDie(context.required_args, kTabletArg);
+
+  unique_ptr<TabletServerServiceProxy> proxy;
+  RETURN_NOT_OK(BuildProxy(address, tserver::TabletServer::kDefaultPort,
+                           &proxy));
+
+  vector<ListTabletsResponsePB::StatusAndSchemaPB> replicas;
+  RETURN_NOT_OK(GetReplicas(proxy.get(), &replicas));
+
+  Schema schema;
+  for (const auto& r : replicas) {
+    if (r.tablet_status().tablet_id() == tablet_id) {
+      RETURN_NOT_OK(SchemaFromPB(r.schema(), &schema));
+      break;
+    }
+  }
+  if (!schema.initialized()) {
+    return Status::NotFound("cannot find replica", tablet_id);
+  }
+  return ReplicaDumper::Dump(schema, tablet_id, proxy.get());
+}
+
+Status ListReplicas(const RunnerContext& context) {
+  string address = FindOrDie(context.required_args, kTServerAddressArg);
+  unique_ptr<TabletServerServiceProxy> proxy;
+  RETURN_NOT_OK(BuildProxy(address, tserver::TabletServer::kDefaultPort,
+                           &proxy));
+
+  vector<ListTabletsResponsePB::StatusAndSchemaPB> replicas;
+  RETURN_NOT_OK(GetReplicas(proxy.get(), &replicas));
+
+  for (const auto& r : replicas) {
+    Schema schema;
+    RETURN_NOT_OK_PREPEND(
+        SchemaFromPB(r.schema(), &schema),
+        "Unable to deserialize schema from " + address);
+    PartitionSchema partition_schema;
+    RETURN_NOT_OK_PREPEND(
+        PartitionSchema::FromPB(r.partition_schema(), schema, &partition_schema),
+        "Unable to deserialize partition schema from " + address);
+
+    const TabletStatusPB& rs = r.tablet_status();
+
+    Partition partition;
+    Partition::FromPB(rs.partition(), &partition);
+
+    string state = tablet::TabletStatePB_Name(rs.state());
+    cout << "Tablet id: " << rs.tablet_id() << endl;
+    cout << "State: " << state << endl;
+    cout << "Table name: " << rs.table_name() << endl;
+    cout << "Partition: "
+         << partition_schema.PartitionDebugString(partition, schema) << endl;
+    if (rs.has_estimated_on_disk_size()) {
+      cout << "Estimated on disk size: "
+           << HumanReadableNumBytes::ToString(rs.estimated_on_disk_size()) << endl;
+    }
+    cout << "Schema: " << schema.ToString() << endl;
+  }
+
+  return Status::OK();
+}
+
+} // anonymous namespace
+
+unique_ptr<Mode> BuildRemoteReplicaMode() {
+  unique_ptr<Action> check_replicas =
+      ActionBuilder("check", &CheckReplicas)
+      .Description("Check if all replicas on a Kudu Tablet Server are running")
+      .AddRequiredParameter({ kTServerAddressArg, kTServerAddressDesc })
+      .Build();
+
+  unique_ptr<Action> delete_replica =
+      ActionBuilder("delete", &DeleteReplica)
+      .Description("Delete a replica from a Kudu Tablet Server")
+      .AddRequiredParameter({ kTServerAddressArg, kTServerAddressDesc })
+      .AddRequiredParameter({ kTabletArg, kTabletDesc })
+      .AddRequiredParameter({ kReasonArg, "Reason for deleting the replica" })
+      .Build();
+
+  unique_ptr<Action> dump_replica =
+      ActionBuilder("dump", &DumpReplica)
+      .Description("Dump the data of a replica on a Kudu Tablet Server")
+      .AddRequiredParameter({ kTServerAddressArg, kTServerAddressDesc })
+      .AddRequiredParameter({ kTabletArg, kTabletDesc })
+      .Build();
+
+  unique_ptr<Action> list =
+      ActionBuilder("list", &ListReplicas)
+      .Description("List all replicas on a Kudu Tablet Server")
+      .AddRequiredParameter({ kTServerAddressArg, kTServerAddressDesc })
+      .Build();
+
+  return ModeBuilder("remote_replica")
+      .Description("Operate on replicas on a Kudu Tablet Server")
+      .AddAction(std::move(check_replicas))
+      .AddAction(std::move(delete_replica))
+      .AddAction(std::move(dump_replica))
+      .AddAction(std::move(list))
+      .Build();
+}
+
+} // namespace tools
+} // namespace kudu
+

http://git-wip-us.apache.org/repos/asf/kudu/blob/14cd22a1/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 8ed0039..86e3df7 100644
--- a/src/kudu/tools/tool_action_tablet.cc
+++ b/src/kudu/tools/tool_action_tablet.cc
@@ -30,8 +30,8 @@
 #include "kudu/gutil/stl_util.h"
 #include "kudu/gutil/strings/split.h"
 #include "kudu/gutil/strings/substitute.h"
-#include "kudu/rpc/messenger.h"
 #include "kudu/rpc/rpc_controller.h"
+#include "kudu/tools/tool_action_common.h"
 #include "kudu/util/net/net_util.h"
 #include "kudu/util/status.h"
 #include "kudu/util/string_case.h"
@@ -46,8 +46,6 @@ using client::KuduTabletServer;
 using consensus::ChangeConfigType;
 using consensus::ConsensusServiceProxy;
 using consensus::RaftPeerPB;
-using rpc::Messenger;
-using rpc::MessengerBuilder;
 using rpc::RpcController;
 using std::shared_ptr;
 using std::string;
@@ -145,9 +143,8 @@ Status ChangeConfig(const RunnerContext& context, ChangeConfigType cc_type) {
         leader_hp.ToString());
   }
 
-  shared_ptr<Messenger> messenger;
-  RETURN_NOT_OK(MessengerBuilder("kudu").Build(&messenger));
-  ConsensusServiceProxy proxy(messenger, leader_addrs[0]);
+  unique_ptr<ConsensusServiceProxy> proxy;
+  RETURN_NOT_OK(BuildProxy(leader_hp.host(), leader_hp.port(), &proxy));
 
   consensus::ChangeConfigRequestPB req;
   consensus::ChangeConfigResponsePB resp;
@@ -157,7 +154,7 @@ Status ChangeConfig(const RunnerContext& context, ChangeConfigType cc_type) {
   req.set_tablet_id(tablet_id);
   req.set_type(cc_type);
   *req.mutable_server() = peer_pb;
-  RETURN_NOT_OK(proxy.ChangeConfig(req, &resp, &rpc));
+  RETURN_NOT_OK(proxy->ChangeConfig(req, &resp, &rpc));
   if (resp.has_error()) {
     return StatusFromPB(resp.error().status());
   }

http://git-wip-us.apache.org/repos/asf/kudu/blob/14cd22a1/src/kudu/tools/tool_action_tserver.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action_tserver.cc b/src/kudu/tools/tool_action_tserver.cc
new file mode 100644
index 0000000..145ec1b
--- /dev/null
+++ b/src/kudu/tools/tool_action_tserver.cc
@@ -0,0 +1,98 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "kudu/tools/tool_action.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include <gflags/gflags.h>
+
+#include "kudu/gutil/map-util.h"
+#include "kudu/tools/tool_action_common.h"
+#include "kudu/tserver/tablet_server.h"
+#include "kudu/util/status.h"
+
+namespace kudu {
+namespace tools {
+
+using std::string;
+using std::unique_ptr;
+
+namespace {
+
+const char* const kTServerAddressArg = "tserver_address";
+const char* const kTServerAddressDesc = "Address of a Kudu Tablet Server of "
+    "form 'hostname:port'. Port may be omitted if the Tablet Server is bound "
+    "to the default port.";
+const char* const kFlagArg = "flag";
+const char* const kValueArg = "value";
+
+Status TServerSetFlag(const RunnerContext& context) {
+  string address = FindOrDie(context.required_args, kTServerAddressArg);
+  string flag = FindOrDie(context.required_args, kFlagArg);
+  string value = FindOrDie(context.required_args, kValueArg);
+  return SetServerFlag(address, tserver::TabletServer::kDefaultPort,
+                       flag, value);
+}
+
+Status TServerStatus(const RunnerContext& context) {
+  string address = FindOrDie(context.required_args, kTServerAddressArg);
+  return PrintServerStatus(address, tserver::TabletServer::kDefaultPort);
+}
+
+Status TServerTimestamp(const RunnerContext& context) {
+  string address = FindOrDie(context.required_args, kTServerAddressArg);
+  return PrintServerTimestamp(address, tserver::TabletServer::kDefaultPort);
+}
+
+} // anonymous namespace
+
+unique_ptr<Mode> BuildTServerMode() {
+  unique_ptr<Action> set_flag =
+      ActionBuilder("set_flag", &TServerSetFlag)
+      .Description("Change a gflag value on a Kudu Tablet Server")
+      .AddRequiredParameter({ kTServerAddressArg, kTServerAddressDesc })
+      .AddRequiredParameter({ kFlagArg, "Name of the gflag" })
+      .AddRequiredParameter({ kValueArg, "New value for the gflag" })
+      .AddOptionalParameter("force")
+      .Build();
+
+  unique_ptr<Action> status =
+      ActionBuilder("status", &TServerStatus)
+      .Description("Get the status of a Kudu Tablet Server")
+      .AddRequiredParameter({ kTServerAddressArg, kTServerAddressDesc })
+      .Build();
+
+  unique_ptr<Action> timestamp =
+      ActionBuilder("timestamp", &TServerTimestamp)
+      .Description("Get the current timestamp of a Kudu Tablet Server")
+      .AddRequiredParameter({ kTServerAddressArg, kTServerAddressDesc })
+      .Build();
+
+  return ModeBuilder("tserver")
+      .Description("Operate on a Kudu Tablet Server")
+      .AddAction(std::move(set_flag))
+      .AddAction(std::move(status))
+      .AddAction(std::move(timestamp))
+      .Build();
+}
+
+} // namespace tools
+} // namespace kudu
+

http://git-wip-us.apache.org/repos/asf/kudu/blob/14cd22a1/src/kudu/tools/tool_main.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_main.cc b/src/kudu/tools/tool_main.cc
index 3c8bb63..caf3bde 100644
--- a/src/kudu/tools/tool_main.cc
+++ b/src/kudu/tools/tool_main.cc
@@ -114,9 +114,12 @@ int RunTool(int argc, char** argv, bool show_help) {
     .AddMode(BuildClusterMode())
     .AddMode(BuildFsMode())
     .AddMode(BuildLocalReplicaMode())
+    .AddMode(BuildMasterMode())
     .AddMode(BuildPbcMode())
+    .AddMode(BuildRemoteReplicaMode())
     .AddMode(BuildTableMode())
     .AddMode(BuildTabletMode())
+    .AddMode(BuildTServerMode())
     .AddMode(BuildWalMode())
     .Build();
 

http://git-wip-us.apache.org/repos/asf/kudu/blob/14cd22a1/src/kudu/tools/ts-cli.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/ts-cli.cc b/src/kudu/tools/ts-cli.cc
deleted file mode 100644
index f34adbf..0000000
--- a/src/kudu/tools/ts-cli.cc
+++ /dev/null
@@ -1,494 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-//
-// Tool to query tablet server operational data
-
-#include <gflags/gflags.h>
-#include <glog/logging.h>
-#include <iostream>
-#include <memory>
-#include <strstream>
-
-#include "kudu/client/row_result.h"
-#include "kudu/client/scanner-internal.h"
-#include "kudu/common/partition.h"
-#include "kudu/common/schema.h"
-#include "kudu/common/wire_protocol.h"
-#include "kudu/gutil/strings/human_readable.h"
-#include "kudu/server/server_base.proxy.h"
-#include "kudu/tserver/tserver.pb.h"
-#include "kudu/tserver/tserver_admin.proxy.h"
-#include "kudu/tserver/tserver_service.proxy.h"
-#include "kudu/tserver/tablet_server.h"
-#include "kudu/util/env.h"
-#include "kudu/util/faststring.h"
-#include "kudu/util/flags.h"
-#include "kudu/util/logging.h"
-#include "kudu/util/net/net_util.h"
-#include "kudu/util/net/sockaddr.h"
-#include "kudu/rpc/messenger.h"
-#include "kudu/rpc/rpc_controller.h"
-
-using kudu::client::KuduRowResult;
-using kudu::HostPort;
-using kudu::rpc::Messenger;
-using kudu::rpc::MessengerBuilder;
-using kudu::rpc::RpcController;
-using kudu::server::ServerStatusPB;
-using kudu::Sockaddr;
-using kudu::client::KuduScanBatch;
-using kudu::tablet::TabletStatusPB;
-using kudu::tserver::DeleteTabletRequestPB;
-using kudu::tserver::DeleteTabletResponsePB;
-using kudu::tserver::ListTabletsRequestPB;
-using kudu::tserver::ListTabletsResponsePB;
-using kudu::tserver::NewScanRequestPB;
-using kudu::tserver::ScanRequestPB;
-using kudu::tserver::ScanResponsePB;
-using kudu::tserver::TabletServerAdminServiceProxy;
-using kudu::tserver::TabletServerServiceProxy;
-using std::ostringstream;
-using std::shared_ptr;
-using std::string;
-using std::vector;
-
-const char* const kListTabletsOp = "list_tablets";
-const char* const kAreTabletsRunningOp = "are_tablets_running";
-const char* const kSetFlagOp = "set_flag";
-const char* const kDumpTabletOp = "dump_tablet";
-const char* const kDeleteTabletOp = "delete_tablet";
-const char* const kCurrentTimestamp = "current_timestamp";
-const char* const kStatus = "status";
-
-DEFINE_string(server_address, "localhost",
-              "Address of server to run against");
-DEFINE_int64(timeout_ms, 1000 * 60, "RPC timeout in milliseconds");
-
-DEFINE_bool(force, false, "If true, allows the set_flag command to set a flag "
-            "which is not explicitly marked as runtime-settable. Such flag changes may be "
-            "simply ignored on the server, or may cause the server to crash.");
-
-// Check that the value of argc matches what's expected, otherwise return a
-// non-zero exit code. Should be used in main().
-#define CHECK_ARGC_OR_RETURN_WITH_USAGE(op, expected) \
-  do { \
-    const string& _op = (op); \
-    const int _expected = (expected); \
-    if (argc != _expected) { \
-      /* We substract 2 from _expected because we don't want to count argv[0] or [1]. */ \
-      std::cerr << "Invalid number of arguments for " << _op \
-                << ": expected " << (_expected - 2) << " arguments" << std::endl; \
-      google::ShowUsageWithFlagsRestrict(argv[0], __FILE__); \
-      return 2; \
-    } \
-  } while (0);
-
-// Invoke 'to_call' and check its result. If it failed, print 'to_prepend' and
-// the error to cerr and return a non-zero exit code. Should be used in main().
-#define RETURN_NOT_OK_PREPEND_FROM_MAIN(to_call, to_prepend) \
-  do { \
-    ::kudu::Status s = (to_call); \
-    if (!s.ok()) { \
-      std::cerr << (to_prepend) << ": " << s.ToString() << std::endl; \
-      return 1; \
-    } \
-  } while (0);
-
-namespace kudu {
-namespace tools {
-
-typedef ListTabletsResponsePB::StatusAndSchemaPB StatusAndSchemaPB;
-
-class TsAdminClient {
- public:
-  // Creates an admin client for host/port combination e.g.,
-  // "localhost" or "127.0.0.1:7050".
-  TsAdminClient(std::string addr, int64_t timeout_millis);
-
-  // Initialized the client and connects to the specified tablet
-  // server.
-  Status Init();
-
-  // Sets 'tablets' a list of status information for all tablets on a
-  // given tablet server.
-  Status ListTablets(std::vector<StatusAndSchemaPB>* tablets);
-
-
-  // Sets the gflag 'flag' to 'val' on the remote server via RPC.
-  // If 'force' is true, allows setting flags even if they're not marked as
-  // safe to change at runtime.
-  Status SetFlag(const string& flag, const string& val,
-                 bool force);
-
-  // Get the schema for the given tablet.
-  Status GetTabletSchema(const std::string& tablet_id, SchemaPB* schema);
-
-  // Dump the contents of the given tablet, in key order, to the console.
-  Status DumpTablet(const std::string& tablet_id);
-
-  // Delete a tablet replica from the specified peer.
-  // The 'reason' string is passed to the tablet server, used for logging.
-  Status DeleteTablet(const std::string& tablet_id,
-                      const std::string& reason);
-
-  // Sets timestamp to the value of the tablet server's current timestamp.
-  Status CurrentTimestamp(uint64_t* timestamp);
-
-  // Get the server status
-  Status GetStatus(ServerStatusPB* pb);
- private:
-  std::string addr_;
-  vector<Sockaddr> addrs_;
-  MonoDelta timeout_;
-  bool initted_;
-  shared_ptr<server::GenericServiceProxy> generic_proxy_;
-  gscoped_ptr<tserver::TabletServerServiceProxy> ts_proxy_;
-  gscoped_ptr<tserver::TabletServerAdminServiceProxy> ts_admin_proxy_;
-  shared_ptr<rpc::Messenger> messenger_;
-
-  DISALLOW_COPY_AND_ASSIGN(TsAdminClient);
-};
-
-TsAdminClient::TsAdminClient(string addr, int64_t timeout_millis)
-    : addr_(std::move(addr)),
-      timeout_(MonoDelta::FromMilliseconds(timeout_millis)),
-      initted_(false) {}
-
-Status TsAdminClient::Init() {
-  CHECK(!initted_);
-
-  HostPort host_port;
-  RETURN_NOT_OK(host_port.ParseString(addr_, tserver::TabletServer::kDefaultPort));
-  MessengerBuilder builder("ts-cli");
-  RETURN_NOT_OK(builder.Build(&messenger_));
-
-  RETURN_NOT_OK(host_port.ResolveAddresses(&addrs_))
-
-  generic_proxy_.reset(new server::GenericServiceProxy(messenger_, addrs_[0]));
-  ts_proxy_.reset(new TabletServerServiceProxy(messenger_, addrs_[0]));
-  ts_admin_proxy_.reset(new TabletServerAdminServiceProxy(messenger_, addrs_[0]));
-
-  initted_ = true;
-
-  VLOG(1) << "Connected to " << addr_;
-
-  return Status::OK();
-}
-
-Status TsAdminClient::ListTablets(vector<StatusAndSchemaPB>* tablets) {
-  CHECK(initted_);
-
-  ListTabletsRequestPB req;
-  ListTabletsResponsePB resp;
-  RpcController rpc;
-
-  rpc.set_timeout(timeout_);
-  RETURN_NOT_OK(ts_proxy_->ListTablets(req, &resp, &rpc));
-  if (resp.has_error()) {
-    return StatusFromPB(resp.error().status());
-  }
-
-  tablets->assign(resp.status_and_schema().begin(), resp.status_and_schema().end());
-
-  return Status::OK();
-}
-
-Status TsAdminClient::SetFlag(const string& flag, const string& val,
-                              bool force) {
-  server::SetFlagRequestPB req;
-  server::SetFlagResponsePB resp;
-  RpcController rpc;
-
-  rpc.set_timeout(timeout_);
-  req.set_flag(flag);
-  req.set_value(val);
-  req.set_force(force);
-
-  RETURN_NOT_OK(generic_proxy_->SetFlag(req, &resp, &rpc));
-  switch (resp.result()) {
-    case server::SetFlagResponsePB::SUCCESS:
-      return Status::OK();
-    case server::SetFlagResponsePB::NOT_SAFE:
-      return Status::RemoteError(resp.msg() + " (use --force flag to allow anyway)");
-    default:
-      return Status::RemoteError(resp.ShortDebugString());
-  }
-}
-
-Status TsAdminClient::GetTabletSchema(const std::string& tablet_id,
-                                      SchemaPB* schema) {
-  VLOG(1) << "Fetching schema for tablet " << tablet_id;
-  vector<StatusAndSchemaPB> tablets;
-  RETURN_NOT_OK(ListTablets(&tablets));
-  for (const StatusAndSchemaPB& pair : tablets) {
-    if (pair.tablet_status().tablet_id() == tablet_id) {
-      *schema = pair.schema();
-      return Status::OK();
-    }
-  }
-  return Status::NotFound("Cannot find tablet", tablet_id);
-}
-
-Status TsAdminClient::DumpTablet(const std::string& tablet_id) {
-  SchemaPB schema_pb;
-  RETURN_NOT_OK(GetTabletSchema(tablet_id, &schema_pb));
-  Schema schema;
-  RETURN_NOT_OK(SchemaFromPB(schema_pb, &schema));
-  kudu::client::KuduSchema client_schema(schema);
-
-  ScanRequestPB req;
-  ScanResponsePB resp;
-
-  NewScanRequestPB* new_req = req.mutable_new_scan_request();
-  RETURN_NOT_OK(SchemaToColumnPBs(
-      schema, new_req->mutable_projected_columns(),
-      SCHEMA_PB_WITHOUT_IDS | SCHEMA_PB_WITHOUT_STORAGE_ATTRIBUTES));
-  new_req->set_tablet_id(tablet_id);
-  new_req->set_cache_blocks(false);
-  new_req->set_order_mode(ORDERED);
-  new_req->set_read_mode(READ_AT_SNAPSHOT);
-
-  do {
-    RpcController rpc;
-    rpc.set_timeout(timeout_);
-    RETURN_NOT_OK_PREPEND(ts_proxy_->Scan(req, &resp, &rpc),
-                          "Scan() failed");
-
-    if (resp.has_error()) {
-      return Status::IOError("Failed to read: ", resp.error().ShortDebugString());
-    }
-
-    // The first response has a scanner ID. We use this for all subsequent
-    // responses.
-    if (resp.has_scanner_id()) {
-      req.set_scanner_id(resp.scanner_id());
-      req.clear_new_scan_request();
-    }
-    req.set_call_seq_id(req.call_seq_id() + 1);
-
-    // Nothing to process from this scan result.
-    if (!resp.has_data()) {
-      continue;
-    }
-
-    KuduScanBatch::Data results;
-    RETURN_NOT_OK(results.Reset(&rpc,
-                                &schema,
-                                &client_schema,
-                                make_gscoped_ptr(resp.release_data())));
-    vector<KuduRowResult> rows;
-    results.ExtractRows(&rows);
-    for (const KuduRowResult& r : rows) {
-      std::cout << r.ToString() << std::endl;
-    }
-  } while (resp.has_more_results());
-  return Status::OK();
-}
-
-Status TsAdminClient::DeleteTablet(const string& tablet_id,
-                                   const string& reason) {
-  ServerStatusPB status_pb;
-  RETURN_NOT_OK(GetStatus(&status_pb));
-
-  DeleteTabletRequestPB req;
-  DeleteTabletResponsePB resp;
-  RpcController rpc;
-
-  req.set_tablet_id(tablet_id);
-  req.set_dest_uuid(status_pb.node_instance().permanent_uuid());
-  req.set_reason(reason);
-  req.set_delete_type(tablet::TABLET_DATA_TOMBSTONED);
-  rpc.set_timeout(timeout_);
-  RETURN_NOT_OK_PREPEND(ts_admin_proxy_->DeleteTablet(req, &resp, &rpc),
-                        "DeleteTablet() failed");
-
-  if (resp.has_error()) {
-    return Status::IOError("Failed to delete tablet: ",
-                           resp.error().ShortDebugString());
-  }
-  return Status::OK();
-}
-
-Status TsAdminClient::CurrentTimestamp(uint64_t* timestamp) {
-  server::ServerClockRequestPB req;
-  server::ServerClockResponsePB resp;
-  RpcController rpc;
-  rpc.set_timeout(timeout_);
-  RETURN_NOT_OK(generic_proxy_->ServerClock(req, &resp, &rpc));
-  CHECK(resp.has_timestamp()) << resp.DebugString();
-  *timestamp = resp.timestamp();
-  return Status::OK();
-}
-
-Status TsAdminClient::GetStatus(ServerStatusPB* pb) {
-  server::GetStatusRequestPB req;
-  server::GetStatusResponsePB resp;
-  RpcController rpc;
-  rpc.set_timeout(timeout_);
-  RETURN_NOT_OK(generic_proxy_->GetStatus(req, &resp, &rpc));
-  CHECK(resp.has_status()) << resp.DebugString();
-  pb->Swap(resp.mutable_status());
-  return Status::OK();
-}
-
-namespace {
-
-void SetUsage(const char* argv0) {
-  ostringstream str;
-
-  str << argv0 << " [--server_address=<addr>] <operation> <flags>\n"
-      << "<operation> must be one of:\n"
-      << "  " << kListTabletsOp << "\n"
-      << "  " << kAreTabletsRunningOp << "\n"
-      << "  " << kSetFlagOp << " [-force] <flag> <value>\n"
-      << "  " << kDumpTabletOp << " <tablet_id>\n"
-      << "  " << kDeleteTabletOp << " <tablet_id> <reason string>\n"
-      << "  " << kCurrentTimestamp << "\n"
-      << "  " << kStatus;
-  google::SetUsageMessage(str.str());
-}
-
-string GetOp(int argc, char** argv) {
-  if (argc < 2) {
-    google::ShowUsageWithFlagsRestrict(argv[0], __FILE__);
-    exit(1);
-  }
-
-  return argv[1];
-}
-
-} // anonymous namespace
-
-static int TsCliMain(int argc, char** argv) {
-  FLAGS_logtostderr = 1;
-  SetUsage(argv[0]);
-  ParseCommandLineFlags(&argc, &argv, true);
-  InitGoogleLoggingSafe(argv[0]);
-  const string addr = FLAGS_server_address;
-
-  string op = GetOp(argc, argv);
-
-  TsAdminClient client(addr, FLAGS_timeout_ms);
-
-  RETURN_NOT_OK_PREPEND_FROM_MAIN(client.Init(),
-                                  "Unable to establish connection to " + addr);
-
-  // TODO add other operations here...
-  if (op == kListTabletsOp) {
-    CHECK_ARGC_OR_RETURN_WITH_USAGE(op, 2);
-
-    vector<StatusAndSchemaPB> tablets;
-    RETURN_NOT_OK_PREPEND_FROM_MAIN(client.ListTablets(&tablets),
-                                    "Unable to list tablets on " + addr);
-    for (const StatusAndSchemaPB& status_and_schema : tablets) {
-      Schema schema;
-      RETURN_NOT_OK_PREPEND_FROM_MAIN(SchemaFromPB(status_and_schema.schema(), &schema),
-                                      "Unable to deserialize schema from " + addr);
-      PartitionSchema partition_schema;
-      RETURN_NOT_OK_PREPEND_FROM_MAIN(PartitionSchema::FromPB(status_and_schema.partition_schema(),
-                                                              schema, &partition_schema),
-                                      "Unable to deserialize partition schema from " + addr);
-
-
-      TabletStatusPB ts = status_and_schema.tablet_status();
-
-      Partition partition;
-      Partition::FromPB(ts.partition(), &partition);
-
-      string state = tablet::TabletStatePB_Name(ts.state());
-      std::cout << "Tablet id: " << ts.tablet_id() << std::endl;
-      std::cout << "State: " << state << std::endl;
-      std::cout << "Table name: " << ts.table_name() << std::endl;
-      std::cout << "Partition: " << partition_schema.PartitionDebugString(partition, schema)
-                << std::endl;
-      if (ts.has_estimated_on_disk_size()) {
-        std::cout << "Estimated on disk size: " <<
-            HumanReadableNumBytes::ToString(ts.estimated_on_disk_size()) << std::endl;
-      }
-      std::cout << "Schema: " << schema.ToString() << std::endl;
-    }
-  } else if (op == kAreTabletsRunningOp) {
-    CHECK_ARGC_OR_RETURN_WITH_USAGE(op, 2);
-
-    vector<StatusAndSchemaPB> tablets;
-    RETURN_NOT_OK_PREPEND_FROM_MAIN(client.ListTablets(&tablets),
-                                    "Unable to list tablets on " + addr);
-    bool all_running = true;
-    for (const StatusAndSchemaPB& status_and_schema : tablets) {
-      TabletStatusPB ts = status_and_schema.tablet_status();
-      if (ts.state() != tablet::RUNNING) {
-        std::cout << "Tablet id: " << ts.tablet_id() << " is "
-                  << tablet::TabletStatePB_Name(ts.state()) << std::endl;
-        all_running = false;
-      }
-    }
-
-    if (all_running) {
-      std::cout << "All tablets are running" << std::endl;
-    } else {
-      std::cout << "Not all tablets are running" << std::endl;
-      return 1;
-    }
-  } else if (op == kSetFlagOp) {
-    CHECK_ARGC_OR_RETURN_WITH_USAGE(op, 4);
-
-    RETURN_NOT_OK_PREPEND_FROM_MAIN(client.SetFlag(argv[2], argv[3], FLAGS_force),
-                                    "Unable to set flag");
-
-  } else if (op == kDumpTabletOp) {
-    CHECK_ARGC_OR_RETURN_WITH_USAGE(op, 3);
-
-    string tablet_id = argv[2];
-    RETURN_NOT_OK_PREPEND_FROM_MAIN(client.DumpTablet(tablet_id),
-                                    "Unable to dump tablet");
-  } else if (op == kDeleteTabletOp) {
-    CHECK_ARGC_OR_RETURN_WITH_USAGE(op, 4);
-
-    string tablet_id = argv[2];
-    string reason = argv[3];
-
-    RETURN_NOT_OK_PREPEND_FROM_MAIN(client.DeleteTablet(tablet_id, reason),
-                                    "Unable to delete tablet");
-  } else if (op == kCurrentTimestamp) {
-    CHECK_ARGC_OR_RETURN_WITH_USAGE(op, 2);
-
-    uint64_t timestamp;
-    RETURN_NOT_OK_PREPEND_FROM_MAIN(client.CurrentTimestamp(&timestamp),
-                                    "Unable to get timestamp");
-    std::cout << timestamp << std::endl;
-  } else if (op == kStatus) {
-    CHECK_ARGC_OR_RETURN_WITH_USAGE(op, 2);
-
-    ServerStatusPB status;
-    RETURN_NOT_OK_PREPEND_FROM_MAIN(client.GetStatus(&status),
-                                    "Unable to get status");
-    std::cout << status.DebugString() << std::endl;
-  } else {
-    std::cerr << "Invalid operation: " << op << std::endl;
-    google::ShowUsageWithFlagsRestrict(argv[0], __FILE__);
-    return 2;
-  }
-
-  return 0;
-}
-
-} // namespace tools
-} // namespace kudu
-
-int main(int argc, char** argv) {
-  return kudu::tools::TsCliMain(argc, argv);
-}