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

[1/2] kudu git commit: tool: port kudu-admin to 'kudu table' and 'kudu tablet'

Repository: kudu
Updated Branches:
  refs/heads/master 61557b327 -> 2ecf034c8


tool: port kudu-admin to 'kudu table' and 'kudu tablet'

The config change operations go to the revived 'tablet' mode, which is now
"operations against remote Kudu tablets". The table operations go to
'kudu table'.

In doing so, the ported functionality can now also work in multi-master
mode; previously, the ClusterAdminClient refused to accept multiple masters.

I opted to group the config change operations as a common mode rather than
an additional parameter. I think this makes them more discoverable.

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

Branch: refs/heads/master
Commit: d46ac5f64c9eb5069a44dbbb6545a3dcc4a6d997
Parents: 61557b3
Author: Adar Dembo <ad...@cloudera.com>
Authored: Tue Aug 30 20:12:46 2016 -0700
Committer: Todd Lipcon <to...@apache.org>
Committed: Sat Sep 10 04:23:12 2016 +0000

----------------------------------------------------------------------
 build-support/dist_test.py                  |   3 +-
 docs/release_notes.adoc                     |   3 +
 src/kudu/tools/CMakeLists.txt               |   8 +-
 src/kudu/tools/kudu-admin-test.cc           | 105 +++---
 src/kudu/tools/kudu-admin.cc                | 426 -----------------------
 src/kudu/tools/kudu-tool-test.cc            |  23 ++
 src/kudu/tools/kudu-ts-cli-test.cc          |   7 +-
 src/kudu/tools/tool_action.h                |   2 +
 src/kudu/tools/tool_action_cluster.cc       |  14 +-
 src/kudu/tools/tool_action_local_replica.cc |   2 +-
 src/kudu/tools/tool_action_table.cc         | 110 ++++++
 src/kudu/tools/tool_action_tablet.cc        | 239 +++++++++++++
 src/kudu/tools/tool_main.cc                 |   2 +
 13 files changed, 459 insertions(+), 485 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kudu/blob/d46ac5f6/build-support/dist_test.py
----------------------------------------------------------------------
diff --git a/build-support/dist_test.py b/build-support/dist_test.py
index d391cf4..44be8bb 100755
--- a/build-support/dist_test.py
+++ b/build-support/dist_test.py
@@ -77,9 +77,8 @@ DEPS_FOR_ALL = \
      #".../example-deletes.txt",
      #".../example-tweets.txt",
 
-     # Tests that require tooling require these.
+     # Tests that require tooling require this.
      "build/latest/bin/kudu",
-     "build/latest/bin/kudu-admin",
      ]
 
 

http://git-wip-us.apache.org/repos/asf/kudu/blob/d46ac5f6/docs/release_notes.adoc
----------------------------------------------------------------------
diff --git a/docs/release_notes.adoc b/docs/release_notes.adoc
index 2388bce..0b8af4a 100644
--- a/docs/release_notes.adoc
+++ b/docs/release_notes.adoc
@@ -61,6 +61,9 @@ Kudu 1.0.0 are not supported.
 - The `log-dump` tool has been removed. The same functionality is now
   implemented as `kudu wal dump` and `kudu local_replica dump_wals`.
 
+- The `kudu-admin` tool has been removed. The same functionality is now
+  implemented within `kudu table` and `kudu tablet`.
+
 - KuduSession methods in the C++ library are no longer advertised as thread-safe
   to have one set of semantics for both C++ and Java Kudu client libraries.
 

http://git-wip-us.apache.org/repos/asf/kudu/blob/d46ac5f6/src/kudu/tools/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/src/kudu/tools/CMakeLists.txt b/src/kudu/tools/CMakeLists.txt
index 9a029fb..8a707bf 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-admin kudu-admin.cc)
-target_link_libraries(kudu-admin
-  ${LINK_LIBS})
-
 add_executable(kudu-ts-cli ts-cli.cc)
 target_link_libraries(kudu-ts-cli
   ${LINK_LIBS})
@@ -91,6 +87,8 @@ add_executable(kudu
   tool_action_fs.cc
   tool_action_local_replica.cc
   tool_action_pbc.cc
+  tool_action_table.cc
+  tool_action_tablet.cc
   tool_action_wal.cc
   tool_main.cc
 )
@@ -117,7 +115,7 @@ ADD_KUDU_TEST(ksck-test)
 ADD_KUDU_TEST(ksck_remote-test RESOURCE_LOCK "master-rpc-ports")
 ADD_KUDU_TEST(kudu-admin-test)
 ADD_KUDU_TEST_DEPENDENCIES(kudu-admin-test
-  kudu-admin)
+  kudu)
 ADD_KUDU_TEST(kudu-tool-test)
 ADD_KUDU_TEST_DEPENDENCIES(kudu-tool-test
   kudu)

http://git-wip-us.apache.org/repos/asf/kudu/blob/d46ac5f6/src/kudu/tools/kudu-admin-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/kudu-admin-test.cc b/src/kudu/tools/kudu-admin-test.cc
index 87cf951..6d72f77 100644
--- a/src/kudu/tools/kudu-admin-test.cc
+++ b/src/kudu/tools/kudu-admin-test.cc
@@ -14,8 +14,9 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-//
-// Tests for the kudu-admin command-line tool.
+
+#include <string>
+#include <vector>
 
 #include <gtest/gtest.h>
 
@@ -35,13 +36,15 @@ using client::KuduClientBuilder;
 using client::sp::shared_ptr;
 using itest::TabletServerMap;
 using itest::TServerDetails;
+using std::string;
+using std::vector;
 using strings::Substitute;
 
-static const char* const kAdminToolName = "kudu-admin";
+static const char* const kAdminToolName = "kudu";
 
 class AdminCliTest : public tserver::TabletServerIntegrationTestBase {
  protected:
-  // Figure out where the admin tool is.
+  // Figure out where the tool is.
   string GetAdminToolPath() const;
 };
 
@@ -50,24 +53,22 @@ string AdminCliTest::GetAdminToolPath() const {
   CHECK_OK(Env::Default()->GetExecutablePath(&exe));
   string binroot = DirName(exe);
   string tool_path = JoinPathSegments(binroot, kAdminToolName);
-  CHECK(Env::Default()->FileExists(tool_path)) << "kudu-admin tool not found at " << tool_path;
+  CHECK(Env::Default()->FileExists(tool_path))
+      << kAdminToolName << " tool not found at " << tool_path;
   return tool_path;
 }
 
-// Test kudu-admin config change while running a workload.
+// Test config change while running a workload.
 // 1. Instantiate external mini cluster with 3 TS.
 // 2. Create table with 2 replicas.
-// 3. Invoke kudu-admin CLI to invoke a config change.
+// 3. Invoke CLI to trigger a config change.
 // 4. Wait until the new server bootstraps.
 // 5. Profit!
 TEST_F(AdminCliTest, TestChangeConfig) {
   FLAGS_num_tablet_servers = 3;
   FLAGS_num_replicas = 2;
-
-  vector<string> ts_flags, master_flags;
-  ts_flags.push_back("--enable_leader_failure_detection=false");
-  master_flags.push_back("--catalog_manager_wait_for_new_tablets_to_elect_leader=false");
-  BuildAndStart(ts_flags, master_flags);
+  BuildAndStart({ "--enable_leader_failure_detection=false" },
+                { "--catalog_manager_wait_for_new_tablets_to_elect_leader=false" });
 
   vector<TServerDetails*> tservers;
   AppendValuesFromMap(tablet_servers_, &tservers);
@@ -110,12 +111,16 @@ TEST_F(AdminCliTest, TestChangeConfig) {
   ASSERT_EQ(leader->uuid(), master_observed_leader->uuid());
 
   LOG(INFO) << "Adding tserver with uuid " << new_node->uuid() << " as VOTER...";
-  string exe_path = GetAdminToolPath();
-  string arg_str = Substitute("$0 -master_addresses $1 change_config $2 ADD_SERVER $3 VOTER",
-                              exe_path,
-                              cluster_->master()->bound_rpc_addr().ToString(),
-                              tablet_id_, new_node->uuid());
-  ASSERT_OK(Subprocess::Call(arg_str));
+  ASSERT_OK(Subprocess::Call({
+    GetAdminToolPath(),
+    "tablet",
+    "change_config",
+    "add_replica",
+    cluster_->master()->bound_rpc_addr().ToString(),
+    tablet_id_,
+    new_node->uuid(),
+    "VOTER"
+  }));
 
   InsertOrDie(&active_tablet_servers, new_node->uuid(), new_node);
   ASSERT_OK(WaitUntilCommittedConfigNumVotersIs(active_tablet_servers.size(),
@@ -144,12 +149,15 @@ TEST_F(AdminCliTest, TestChangeConfig) {
 
   // Now remove the server once again.
   LOG(INFO) << "Removing tserver with uuid " << new_node->uuid() << " from the config...";
-  arg_str = Substitute("$0 -master_addresses $1 change_config $2 REMOVE_SERVER $3",
-                       exe_path,
-                       cluster_->master()->bound_rpc_addr().ToString(),
-                       tablet_id_, new_node->uuid());
-
-  ASSERT_OK(Subprocess::Call(arg_str));
+  ASSERT_OK(Subprocess::Call({
+    GetAdminToolPath(),
+    "tablet",
+    "change_config",
+    "remove_replica",
+    cluster_->master()->bound_rpc_addr().ToString(),
+    tablet_id_,
+    new_node->uuid()
+  }));
 
   ASSERT_EQ(1, active_tablet_servers.erase(new_node->uuid()));
   ASSERT_OK(WaitUntilCommittedConfigNumVotersIs(active_tablet_servers.size(),
@@ -160,31 +168,46 @@ TEST_F(AdminCliTest, TestChangeConfig) {
 TEST_F(AdminCliTest, TestDeleteTable) {
   FLAGS_num_tablet_servers = 1;
   FLAGS_num_replicas = 1;
+  BuildAndStart({}, {});
 
-  vector<string> ts_flags, master_flags;
-  BuildAndStart(ts_flags, master_flags);
   string master_address = cluster_->master()->bound_rpc_addr().ToString();
-
   shared_ptr<KuduClient> client;
-  CHECK_OK(KuduClientBuilder()
-        .add_master_server_addr(master_address)
-        .Build(&client));
-
-  // Default table that gets created;
-  string table_name = "TestTable";
-
-  string exe_path = GetAdminToolPath();
-  string arg_str = Substitute("$0 -master_addresses $1 delete_table $2",
-                              exe_path,
-                              master_address,
-                              table_name);
-
-  ASSERT_OK(Subprocess::Call(arg_str));
+  ASSERT_OK(KuduClientBuilder()
+            .add_master_server_addr(master_address)
+            .Build(&client));
+
+  ASSERT_OK(Subprocess::Call({
+    GetAdminToolPath(),
+    "table",
+    "delete",
+    master_address,
+    kTableId
+  }));
 
   vector<string> tables;
   ASSERT_OK(client->ListTables(&tables));
   ASSERT_TRUE(tables.empty());
 }
 
+TEST_F(AdminCliTest, TestListTables) {
+  FLAGS_num_tablet_servers = 1;
+  FLAGS_num_replicas = 1;
+
+  BuildAndStart({}, {});
+
+  string stdout;
+  ASSERT_OK(Subprocess::Call({
+    GetAdminToolPath(),
+    "table",
+    "list",
+    cluster_->master()->bound_rpc_addr().ToString()
+  }, &stdout, nullptr));
+
+  vector<string> stdout_lines = strings::Split(stdout, ",",
+                                               strings::SkipEmpty());
+  ASSERT_EQ(1, stdout_lines.size());
+  ASSERT_EQ(Substitute("$0\n", kTableId), stdout_lines[0]);
+}
+
 } // namespace tools
 } // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/d46ac5f6/src/kudu/tools/kudu-admin.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/kudu-admin.cc b/src/kudu/tools/kudu-admin.cc
deleted file mode 100644
index 9d81dfd..0000000
--- a/src/kudu/tools/kudu-admin.cc
+++ /dev/null
@@ -1,426 +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 administer a cluster from the CLI.
-
-#include <boost/optional.hpp>
-#include <gflags/gflags.h>
-#include <glog/logging.h>
-#include <iostream>
-#include <memory>
-#include <strstream>
-
-#include "kudu/client/client.h"
-#include "kudu/common/wire_protocol.h"
-#include "kudu/consensus/consensus.pb.h"
-#include "kudu/consensus/consensus.proxy.h"
-#include "kudu/gutil/strings/split.h"
-#include "kudu/gutil/strings/substitute.h"
-#include "kudu/master/master.h"
-#include "kudu/master/master.pb.h"
-#include "kudu/master/master.proxy.h"
-#include "kudu/tserver/tablet_server.h"
-#include "kudu/util/env.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/util/string_case.h"
-#include "kudu/rpc/messenger.h"
-#include "kudu/rpc/rpc_controller.h"
-
-DEFINE_string(master_addresses, "localhost",
-              "Comma-separated list of Kudu Master server addresses");
-DEFINE_int64(timeout_ms, 1000 * 60, "RPC timeout in milliseconds");
-
-#define EXIT_NOT_OK_PREPEND(status, msg) \
-  do { \
-    Status _s = (status); \
-    if (PREDICT_FALSE(!_s.ok())) { \
-      std::cerr << _s.CloneAndPrepend(msg).ToString() << std::endl; \
-      google::ShowUsageWithFlagsRestrict(g_progname, __FILE__); \
-      exit(1); \
-    } \
-  } while (0)
-
-namespace kudu {
-namespace tools {
-
-using std::ostringstream;
-using std::string;
-using std::vector;
-
-using google::protobuf::RepeatedPtrField;
-
-using client::KuduClient;
-using client::KuduClientBuilder;
-using client::KuduTabletServer;
-using consensus::ConsensusServiceProxy;
-using consensus::RaftPeerPB;
-using master::ListTabletServersRequestPB;
-using master::ListTabletServersResponsePB;
-using master::MasterServiceProxy;
-using master::TabletLocationsPB;
-using master::TSInfoPB;
-using rpc::Messenger;
-using rpc::MessengerBuilder;
-using rpc::RpcController;
-using strings::Split;
-using strings::Substitute;
-
-const char* const kChangeConfigOp = "change_config";
-const char* const kListTablesOp = "list_tables";
-const char* const kDeleteTableOp = "delete_table";
-static const char* g_progname = nullptr;
-
-class ClusterAdminClient {
- public:
-  // Creates an admin client for host/port combination e.g.,
-  // "localhost" or "127.0.0.1:7050".
-  ClusterAdminClient(std::string addrs, int64_t timeout_millis);
-
-  // Initialized the client and connects to the specified tablet
-  // server.
-  Status Init();
-
-  // Change the configuration of the specified tablet.
-  Status ChangeConfig(const string& tablet_id,
-                      const string& change_type,
-                      const string& peer_uuid,
-                      const boost::optional<string>& member_type);
-
-  // List all the tables.
-  Status ListTables();
-
-  // Delete a single table by name.
-  Status DeleteTable(const string& table_name);
-
- private:
-  // Fetch the locations of the replicas for a given tablet from the Master.
-  Status GetTabletLocations(const std::string& tablet_id,
-                            TabletLocationsPB* locations);
-
-  // Fetch information about the location of the tablet leader from the Master.
-  Status GetTabletLeader(const std::string& tablet_id, TSInfoPB* ts_info);
-
-  // Fetch the latest list of tablet servers from the Master.
-  Status ListTabletServers(RepeatedPtrField<ListTabletServersResponsePB::Entry>* servers);
-
-  // Look up the RPC address of the server with the specified UUID from the Master.
-  Status GetFirstRpcAddressForTS(const std::string& uuid, HostPort* hp);
-
-  const std::string master_addr_list_;
-  const MonoDelta timeout_;
-
-  bool initted_;
-  std::shared_ptr<rpc::Messenger> messenger_;
-  gscoped_ptr<MasterServiceProxy> master_proxy_;
-  client::sp::shared_ptr<KuduClient> kudu_client_;
-
-  DISALLOW_COPY_AND_ASSIGN(ClusterAdminClient);
-};
-
-ClusterAdminClient::ClusterAdminClient(string addrs, int64_t timeout_millis)
-    : master_addr_list_(std::move(addrs)),
-      timeout_(MonoDelta::FromMilliseconds(timeout_millis)),
-      initted_(false) {}
-
-Status ClusterAdminClient::Init() {
-  CHECK(!initted_);
-
-  // Build master proxy.
-  // TODO: Support multi-master by adding replica lookup support to the client.
-  vector<string> master_addr_strings = Split(master_addr_list_, ",");
-  if (master_addr_strings.size() != 1) {
-    return Status::InvalidArgument("This tool does not yet support multiple masters. "
-                                   "Please specify only the leader master address in "
-                                   "-master_addresses");
-  }
-
-  HostPort master_hostport;
-  RETURN_NOT_OK(master_hostport.ParseString(master_addr_strings[0],
-                                            master::Master::kDefaultPort));
-  MessengerBuilder builder("kudu-admin");
-  RETURN_NOT_OK(builder.Build(&messenger_));
-  vector<Sockaddr> master_addrs;
-  RETURN_NOT_OK(master_hostport.ResolveAddresses(&master_addrs));
-  CHECK(!master_addrs.empty()) << "Unable to resolve IP address for master host: "
-                               << master_hostport.ToString();
-  master_proxy_.reset(new MasterServiceProxy(messenger_, master_addrs[0]));
-
-  CHECK_OK(KuduClientBuilder()
-      .add_master_server_addr(master_addr_list_)
-      .default_admin_operation_timeout(timeout_)
-      .Build(&kudu_client_));
-
-  initted_ = true;
-  return Status::OK();
-}
-
-Status ClusterAdminClient::ChangeConfig(const string& tablet_id,
-                                        const string& change_type,
-                                        const string& peer_uuid,
-                                        const boost::optional<string>& member_type) {
-  CHECK(initted_);
-
-  // Parse the change type.
-  consensus::ChangeConfigType cc_type = consensus::UNKNOWN_CHANGE;
-  string uppercase_change_type;
-  ToUpperCase(change_type, &uppercase_change_type);
-  if (!consensus::ChangeConfigType_Parse(uppercase_change_type, &cc_type) ||
-      cc_type == consensus::UNKNOWN_CHANGE) {
-    return Status::InvalidArgument("Unsupported change_type", change_type);
-  }
-
-  RaftPeerPB peer_pb;
-  peer_pb.set_permanent_uuid(peer_uuid);
-
-  // Parse the optional fields.
-  if (member_type) {
-    RaftPeerPB::MemberType member_type_val;
-    string uppercase_member_type;
-    ToUpperCase(*member_type, &uppercase_member_type);
-    if (!RaftPeerPB::MemberType_Parse(uppercase_member_type, &member_type_val)) {
-      return Status::InvalidArgument("Unrecognized member_type", *member_type);
-    }
-    peer_pb.set_member_type(member_type_val);
-  }
-
-  // Validate the existence of the optional fields.
-  if (!member_type && (cc_type == consensus::ADD_SERVER || cc_type == consensus::CHANGE_ROLE)) {
-    return Status::InvalidArgument("Must specify member_type when adding "
-                                   "a server or changing a role");
-  }
-
-  // Look up RPC address of peer if adding as a new server.
-  if (cc_type == consensus::ADD_SERVER) {
-    HostPort host_port;
-    RETURN_NOT_OK(GetFirstRpcAddressForTS(peer_uuid, &host_port));
-    RETURN_NOT_OK(HostPortToPB(host_port, peer_pb.mutable_last_known_addr()));
-  }
-
-  // Look up the location of the tablet leader from the Master.
-  TSInfoPB leader_ts_info;
-  RETURN_NOT_OK(GetTabletLeader(tablet_id, &leader_ts_info));
-  CHECK_GT(leader_ts_info.rpc_addresses_size(), 0) << leader_ts_info.ShortDebugString();
-
-  HostPort leader_hostport;
-  RETURN_NOT_OK(HostPortFromPB(leader_ts_info.rpc_addresses(0), &leader_hostport));
-  vector<Sockaddr> leader_addrs;
-  RETURN_NOT_OK(leader_hostport.ResolveAddresses(&leader_addrs));
-  CHECK(!leader_addrs.empty()) << "Unable to resolve IP address for tablet leader host: "
-                               << leader_hostport.ToString();
-  gscoped_ptr<ConsensusServiceProxy> consensus_proxy(
-      new ConsensusServiceProxy(messenger_, leader_addrs[0]));
-
-  consensus::ChangeConfigRequestPB req;
-  consensus::ChangeConfigResponsePB resp;
-  RpcController rpc;
-  rpc.set_timeout(timeout_);
-
-  req.set_dest_uuid(leader_ts_info.permanent_uuid());
-  req.set_tablet_id(tablet_id);
-  req.set_type(cc_type);
-  *req.mutable_server() = peer_pb;
-
-  RETURN_NOT_OK(consensus_proxy->ChangeConfig(req, &resp, &rpc));
-  if (resp.has_error()) {
-    return StatusFromPB(resp.error().status());
-  }
-  return Status::OK();
-}
-
-Status ClusterAdminClient::GetTabletLocations(const string& tablet_id,
-                                              TabletLocationsPB* locations) {
-  rpc::RpcController rpc;
-  rpc.set_timeout(timeout_);
-  master::GetTabletLocationsRequestPB req;
-  *req.add_tablet_ids() = tablet_id;
-  master::GetTabletLocationsResponsePB resp;
-  RETURN_NOT_OK(master_proxy_->GetTabletLocations(req, &resp, &rpc));
-
-  if (resp.has_error()) {
-    return StatusFromPB(resp.error().status());
-  }
-
-  if (resp.errors_size() > 0) {
-    // This tool only needs to support one-by-one requests for tablet
-    // locations, so we only look at the first error.
-    return StatusFromPB(resp.errors(0).status());
-  }
-
-  // Same as above, no batching, and we already got past the error checks.
-  CHECK_EQ(1, resp.tablet_locations_size()) << resp.ShortDebugString();
-
-  *locations = resp.tablet_locations(0);
-  return Status::OK();
-}
-
-Status ClusterAdminClient::GetTabletLeader(const string& tablet_id,
-                                           TSInfoPB* ts_info) {
-  TabletLocationsPB locations;
-  RETURN_NOT_OK(GetTabletLocations(tablet_id, &locations));
-  CHECK_EQ(tablet_id, locations.tablet_id()) << locations.ShortDebugString();
-  bool found = false;
-  for (const TabletLocationsPB::ReplicaPB& replica : locations.replicas()) {
-    if (replica.role() == RaftPeerPB::LEADER) {
-      *ts_info = replica.ts_info();
-      found = true;
-      break;
-    }
-  }
-  if (!found) {
-    return Status::NotFound("No leader replica found for tablet", tablet_id);
-  }
-  return Status::OK();
-}
-
-Status ClusterAdminClient::ListTabletServers(
-    RepeatedPtrField<ListTabletServersResponsePB::Entry>* servers) {
-
-  rpc::RpcController rpc;
-  rpc.set_timeout(timeout_);
-  ListTabletServersRequestPB req;
-  ListTabletServersResponsePB resp;
-  RETURN_NOT_OK(master_proxy_->ListTabletServers(req, &resp, &rpc));
-
-  if (resp.has_error()) {
-    return StatusFromPB(resp.error().status());
-  }
-
-  servers->Swap(resp.mutable_servers());
-  return Status::OK();
-}
-
-Status ClusterAdminClient::GetFirstRpcAddressForTS(const std::string& uuid, HostPort* hp) {
-  RepeatedPtrField<ListTabletServersResponsePB::Entry> servers;
-  RETURN_NOT_OK(ListTabletServers(&servers));
-  for (const ListTabletServersResponsePB::Entry& server : servers) {
-    if (server.instance_id().permanent_uuid() == uuid) {
-      if (!server.has_registration() || server.registration().rpc_addresses_size() == 0) {
-        break;
-      }
-      RETURN_NOT_OK(HostPortFromPB(server.registration().rpc_addresses(0), hp));
-      return Status::OK();
-    }
-  }
-
-  return Status::NotFound(Substitute("Server with UUID $0 has no RPC address "
-                                     "registered with the Master", uuid));
-}
-
-Status ClusterAdminClient::ListTables() {
-  vector<string> tables;
-  RETURN_NOT_OK(kudu_client_->ListTables(&tables));
-  for (const string& table : tables) {
-    std::cout << table << std::endl;
-  }
-  return Status::OK();
-}
-
-Status ClusterAdminClient::DeleteTable(const string& table_name) {
-  vector<Sockaddr> tables;
-  RETURN_NOT_OK(kudu_client_->DeleteTable(table_name));
-  std::cout << "Deleted table " << table_name << std::endl;
-  return Status::OK();
-}
-
-static void SetUsage(const char* argv0) {
-  ostringstream str;
-
-  str << argv0 << " [-master_addresses server1,server2,server3] <operation> <operation-arguments>\n"
-      << "<operation> must be one of:\n"
-      << "  " << kChangeConfigOp << " <tablet_id> "
-              << "<ADD_SERVER|REMOVE_SERVER|CHANGE_ROLE> <peer_uuid> "
-              << "[VOTER|NON_VOTER]" << std::endl
-      << "  " << kListTablesOp << std::endl
-      << "  " << kDeleteTableOp << " <table_name>";
-  google::SetUsageMessage(str.str());
-}
-
-static string GetOp(int argc, char** argv) {
-  if (argc < 2) {
-    google::ShowUsageWithFlagsRestrict(argv[0], __FILE__);
-    exit(1);
-  }
-
-  return argv[1];
-}
-
-static int ClusterAdminCliMain(int argc, char** argv) {
-  g_progname = argv[0];
-  FLAGS_logtostderr = 1;
-  SetUsage(argv[0]);
-  ParseCommandLineFlags(&argc, &argv, true);
-  InitGoogleLoggingSafe(argv[0]);
-  const string addrs = FLAGS_master_addresses;
-
-  string op = GetOp(argc, argv);
-
-  ClusterAdminClient client(addrs, FLAGS_timeout_ms);
-
-  EXIT_NOT_OK_PREPEND(client.Init(), "Unable to establish connection to " + addrs);
-
-  if (op == kChangeConfigOp) {
-    if (argc < 5) {
-      google::ShowUsageWithFlagsRestrict(argv[0], __FILE__);
-      exit(1);
-    }
-    string tablet_id = argv[2];
-    string change_type = argv[3];
-    string peer_uuid = argv[4];
-    boost::optional<string> member_type;
-    if (argc > 5) {
-      member_type = argv[5];
-    }
-    Status s = client.ChangeConfig(tablet_id, change_type, peer_uuid, member_type);
-    if (!s.ok()) {
-      std::cerr << "Unable to change config: " << s.ToString() << std::endl;
-      return 1;
-    }
-  } else if (op == kListTablesOp) {
-    Status s = client.ListTables();
-    if (!s.ok()) {
-      std::cerr << "Unable to list tables: " << s.ToString() << std::endl;
-      return 1;
-    }
-  } else if (op == kDeleteTableOp) {
-    if (argc < 3) {
-      google::ShowUsageWithFlagsRestrict(argv[0], __FILE__);
-      exit(1);
-    }
-    string table_name = argv[2];
-    Status s = client.DeleteTable(table_name);
-    if (!s.ok()) {
-      std::cerr << "Unable to delete table " << table_name << ": " << s.ToString() << std::endl;
-      return 1;
-    }
-  } else {
-    std::cerr << "Invalid operation: " << op << std::endl;
-    google::ShowUsageWithFlagsRestrict(argv[0], __FILE__);
-    exit(1);
-  }
-
-  return 0;
-}
-
-} // namespace tools
-} // namespace kudu
-
-int main(int argc, char** argv) {
-  return kudu::tools::ClusterAdminCliMain(argc, argv);
-}

http://git-wip-us.apache.org/repos/asf/kudu/blob/d46ac5f6/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 89a0725..cf66066 100644
--- a/src/kudu/tools/kudu-tool-test.cc
+++ b/src/kudu/tools/kudu-tool-test.cc
@@ -169,6 +169,8 @@ TEST_F(ToolTest, TestTopLevelHelp) {
       "fs.*Kudu filesystem",
       "local_replica.*Kudu replicas",
       "pbc.*protobuf container",
+      "table.*Kudu tables",
+      "tablet.*Kudu tablets",
       "wal.*write-ahead log"
   };
   NO_FATALS(RunTestHelp("", kTopLevelRegexes));
@@ -215,6 +217,27 @@ TEST_F(ToolTest, TestModeHelp) {
     NO_FATALS(RunTestHelp("pbc", kPbcModeRegexes));
   }
   {
+    const vector<string> kTableModeRegexes = {
+        "delete.*Delete a table",
+        "list.*List all tables",
+    };
+    NO_FATALS(RunTestHelp("table", kTableModeRegexes));
+  }
+  {
+    const vector<string> kTabletModeRegexes = {
+        "change_config.*Change.*Raft configuration"
+    };
+    NO_FATALS(RunTestHelp("tablet", kTabletModeRegexes));
+  }
+  {
+    const vector<string> kChangeConfigModeRegexes = {
+        "add_replica.*Add a new replica",
+        "change_replica_type.*Change the type of an existing replica",
+        "remove_replica.*Remove an existing replica"
+    };
+    NO_FATALS(RunTestHelp("tablet change_config", kChangeConfigModeRegexes));
+  }
+  {
     const vector<string> kWalModeRegexes = {
         "dump.*Dump a WAL",
     };

http://git-wip-us.apache.org/repos/asf/kudu/blob/d46ac5f6/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 6d201a6..6300781 100644
--- a/src/kudu/tools/kudu-ts-cli-test.cc
+++ b/src/kudu/tools/kudu-ts-cli-test.cc
@@ -14,8 +14,6 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-//
-// Tests for the kudu-admin command-line tool.
 
 #include <gtest/gtest.h>
 
@@ -42,7 +40,7 @@ static const char* const kTsCliToolName = "kudu-ts-cli";
 
 class KuduTsCliTest : public ExternalMiniClusterITestBase {
  protected:
-  // Figure out where the admin tool is.
+  // Figure out where the tool is.
   string GetTsCliToolPath() const;
 };
 
@@ -51,7 +49,8 @@ string KuduTsCliTest::GetTsCliToolPath() const {
   CHECK_OK(Env::Default()->GetExecutablePath(&exe));
   string binroot = DirName(exe);
   string tool_path = JoinPathSegments(binroot, kTsCliToolName);
-  CHECK(Env::Default()->FileExists(tool_path)) << "kudu-admin tool not found at " << tool_path;
+  CHECK(Env::Default()->FileExists(tool_path))
+      << kTsCliToolName << " tool not found at " << tool_path;
   return tool_path;
 }
 

http://git-wip-us.apache.org/repos/asf/kudu/blob/d46ac5f6/src/kudu/tools/tool_action.h
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action.h b/src/kudu/tools/tool_action.h
index 1730a1d..d9e3acf 100644
--- a/src/kudu/tools/tool_action.h
+++ b/src/kudu/tools/tool_action.h
@@ -276,6 +276,8 @@ std::unique_ptr<Mode> BuildClusterMode();
 std::unique_ptr<Mode> BuildFsMode();
 std::unique_ptr<Mode> BuildLocalReplicaMode();
 std::unique_ptr<Mode> BuildPbcMode();
+std::unique_ptr<Mode> BuildTableMode();
+std::unique_ptr<Mode> BuildTabletMode();
 std::unique_ptr<Mode> BuildWalMode();
 
 } // namespace tools

http://git-wip-us.apache.org/repos/asf/kudu/blob/d46ac5f6/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 b8009eb..e32c9dc 100644
--- a/src/kudu/tools/tool_action_cluster.cc
+++ b/src/kudu/tools/tool_action_cluster.cc
@@ -49,6 +49,9 @@ DEFINE_string(tablets, "",
               "Tablets to check (comma-separated list of IDs) "
               "If not specified, checks all tablets.");
 
+namespace kudu {
+namespace tools {
+
 using std::cerr;
 using std::cout;
 using std::endl;
@@ -57,14 +60,13 @@ using std::string;
 using std::unique_ptr;
 using std::vector;
 
-namespace kudu {
-namespace tools {
-
 namespace {
 
+const char* const kMasterAddressesArg = "master_addresses";
+
 Status RunKsck(const RunnerContext& context) {
   string master_addresses_str = FindOrDie(context.required_args,
-                                          "master_addresses");
+                                          kMasterAddressesArg);
   vector<string> master_addresses = strings::Split(master_addresses_str, ",");
   shared_ptr<KsckMaster> master;
   RETURN_NOT_OK_PREPEND(RemoteKsckMaster::Build(master_addresses, &master),
@@ -129,8 +131,8 @@ unique_ptr<Mode> BuildClusterMode() {
       .Description(desc)
       .ExtraDescription(extra_desc)
       .AddRequiredParameter({
-        "master_addresses",
-        "Comma-separated list of Kudu Master addresses, where each address is "
+        kMasterAddressesArg,
+        "Comma-separated list of Kudu Master addressess where each address is "
         "of form 'hostname:port'" })
       .AddOptionalParameter("checksum_scan")
       .AddOptionalParameter("checksum_scan_concurrency")

http://git-wip-us.apache.org/repos/asf/kudu/blob/d46ac5f6/src/kudu/tools/tool_action_local_replica.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action_local_replica.cc b/src/kudu/tools/tool_action_local_replica.cc
index 17aecaf..d64059b 100644
--- a/src/kudu/tools/tool_action_local_replica.cc
+++ b/src/kudu/tools/tool_action_local_replica.cc
@@ -237,7 +237,7 @@ unique_ptr<Mode> BuildLocalReplicaMode() {
       .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" })
+        "peers", "List of peers where each peer is of form 'uuid:hostname:port'" })
       .AddOptionalParameter("fs_wal_dir")
       .AddOptionalParameter("fs_data_dirs")
       .Build();

http://git-wip-us.apache.org/repos/asf/kudu/blob/d46ac5f6/src/kudu/tools/tool_action_table.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action_table.cc b/src/kudu/tools/tool_action_table.cc
new file mode 100644
index 0000000..ccbeb17
--- /dev/null
+++ b/src/kudu/tools/tool_action_table.cc
@@ -0,0 +1,110 @@
+// 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 <algorithm>
+#include <iterator>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "kudu/client/client.h"
+#include "kudu/gutil/map-util.h"
+#include "kudu/gutil/strings/split.h"
+#include "kudu/util/status.h"
+
+namespace kudu {
+namespace tools {
+
+using client::KuduClient;
+using client::KuduClientBuilder;
+using std::cout;
+using std::endl;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace {
+
+const char* const kMasterAddressesArg = "master_addresses";
+const char* const kTableNameArg = "table_name";
+
+Status DeleteTable(const RunnerContext& context) {
+  string master_addresses_str = FindOrDie(context.required_args,
+                                          kMasterAddressesArg);
+  vector<string> master_addresses = strings::Split(master_addresses_str, ",");
+  string table_name = FindOrDie(context.required_args, kTableNameArg);
+
+  client::sp::shared_ptr<KuduClient> client;
+  RETURN_NOT_OK(KuduClientBuilder()
+                .master_server_addrs(master_addresses)
+                .Build(&client));
+  return client->DeleteTable(table_name);
+}
+
+Status ListTables(const RunnerContext& context) {
+  string master_addresses_str = FindOrDie(context.required_args,
+                                          kMasterAddressesArg);
+  vector<string> master_addresses = strings::Split(master_addresses_str, ",");
+
+  client::sp::shared_ptr<KuduClient> client;
+  RETURN_NOT_OK(KuduClientBuilder()
+                .master_server_addrs(master_addresses)
+                .Build(&client));
+  vector<string> table_names;
+  RETURN_NOT_OK(client->ListTables(&table_names));
+
+  std::copy(table_names.begin(), table_names.end(),
+            std::ostream_iterator<string>(cout, "\n"));
+  return Status::OK();
+}
+
+} // anonymous namespace
+
+unique_ptr<Mode> BuildTableMode() {
+  unique_ptr<Action> delete_table =
+      ActionBuilder("delete", &DeleteTable)
+      .Description("Delete a table")
+      .AddRequiredParameter({
+        kMasterAddressesArg,
+        "Comma-separated list of Kudu Master addresses where each address is "
+        "of form 'hostname:port'" })
+      .AddRequiredParameter({ kTableNameArg, "Name of the table to delete" })
+      .Build();
+
+  unique_ptr<Action> list_tables =
+      ActionBuilder("list", &ListTables)
+      .Description("List all tables")
+      .AddRequiredParameter({
+        kMasterAddressesArg,
+        "Comma-separated list of Kudu Master addresses where each address is "
+        "of form 'hostname:port'" })
+      .Build();
+
+  return ModeBuilder("table")
+      .Description("Operate on Kudu tables")
+      .AddAction(std::move(delete_table))
+      .AddAction(std::move(list_tables))
+      .Build();
+}
+
+} // namespace tools
+} // namespace kudu
+

http://git-wip-us.apache.org/repos/asf/kudu/blob/d46ac5f6/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
new file mode 100644
index 0000000..66ce494
--- /dev/null
+++ b/src/kudu/tools/tool_action_tablet.cc
@@ -0,0 +1,239 @@
+// 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 <algorithm>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "kudu/client/client.h"
+#include "kudu/common/wire_protocol.h"
+#include "kudu/consensus/consensus.proxy.h"
+#include "kudu/consensus/metadata.pb.h"
+#include "kudu/gutil/map-util.h"
+#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/util/net/net_util.h"
+#include "kudu/util/status.h"
+#include "kudu/util/string_case.h"
+
+namespace kudu {
+namespace tools {
+
+using client::KuduClient;
+using client::KuduClientBuilder;
+using client::KuduTablet;
+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;
+using std::unique_ptr;
+using std::vector;
+using strings::Substitute;
+
+namespace {
+
+const char* const kMasterAddressesArg = "master_addresses";
+const char* const kReplicaTypeArg = "replica_type";
+const char* const kReplicaUuidArg = "replica_uuid";
+const char* const kTabletIdArg = "tablet_id";
+
+Status GetRpcAddressForTS(const client::sp::shared_ptr<KuduClient>& client,
+                          const string& uuid,
+                          HostPort* hp) {
+  vector<KuduTabletServer*> servers;
+  ElementDeleter deleter(&servers);
+  RETURN_NOT_OK(client->ListTabletServers(&servers));
+  for (const auto* s : servers) {
+    if (s->uuid() == uuid) {
+      hp->set_host(s->hostname());
+      hp->set_port(s->port());
+      return Status::OK();
+    }
+  }
+
+  return Status::NotFound(Substitute(
+      "server $0 has no RPC address registered with the Master", uuid));
+}
+
+Status GetTabletLeader(const client::sp::shared_ptr<KuduClient>& client,
+                       const string& tablet_id,
+                       string* leader_uuid,
+                       HostPort* leader_hp) {
+  KuduTablet* tablet_raw;
+  unique_ptr<KuduTablet> tablet;
+  RETURN_NOT_OK(client->GetTablet(tablet_id, &tablet_raw));
+  tablet.reset(tablet_raw);
+
+  for (const auto* r : tablet->replicas()) {
+    if (r->is_leader()) {
+      *leader_uuid = r->ts().uuid();
+      leader_hp->set_host(r->ts().hostname());
+      leader_hp->set_port(r->ts().port());
+      return Status::OK();
+    }
+  }
+  return Status::NotFound(Substitute(
+      "No leader replica found for tablet $0", tablet_id));
+}
+
+Status ChangeConfig(const RunnerContext& context, ChangeConfigType cc_type) {
+  // Parse and validate arguments.
+  RaftPeerPB peer_pb;
+  string master_addresses_str = FindOrDie(context.required_args,
+                                          kMasterAddressesArg);
+  vector<string> master_addresses = strings::Split(master_addresses_str, ",");
+  string tablet_id = FindOrDie(context.required_args, kTabletIdArg);
+  string replica_uuid = FindOrDie(context.required_args, kReplicaUuidArg);
+  if (cc_type == consensus::ADD_SERVER || cc_type == consensus::CHANGE_ROLE) {
+    string replica_type = FindOrDie(context.required_args, kReplicaTypeArg);
+    string uppercase_peer_type;
+    ToUpperCase(replica_type, &uppercase_peer_type);
+    RaftPeerPB::MemberType member_type_val;
+    if (!RaftPeerPB::MemberType_Parse(uppercase_peer_type, &member_type_val)) {
+      return Status::InvalidArgument("Unrecognized peer type", replica_type);
+    }
+    peer_pb.set_member_type(member_type_val);
+  }
+  peer_pb.set_permanent_uuid(replica_uuid);
+
+  client::sp::shared_ptr<KuduClient> client;
+  RETURN_NOT_OK(KuduClientBuilder()
+                .master_server_addrs(master_addresses)
+                .Build(&client));
+
+  // When adding a new server, we need to provide the server's RPC address.
+  if (cc_type == consensus::ADD_SERVER) {
+    HostPort hp;
+    RETURN_NOT_OK(GetRpcAddressForTS(client, replica_uuid, &hp));
+    RETURN_NOT_OK(HostPortToPB(hp, peer_pb.mutable_last_known_addr()));
+  }
+
+  // Find this tablet's leader replica. We need its UUID and RPC address.
+  string leader_uuid;
+  HostPort leader_hp;
+  RETURN_NOT_OK(GetTabletLeader(client, tablet_id, &leader_uuid, &leader_hp));
+  vector<Sockaddr> leader_addrs;
+  RETURN_NOT_OK(leader_hp.ResolveAddresses(&leader_addrs));
+  if (leader_addrs.empty()) {
+    return Status::NotFound(
+        "Unable to resolve IP address for tablet leader host",
+        leader_hp.ToString());
+  }
+
+  shared_ptr<Messenger> messenger;
+  RETURN_NOT_OK(MessengerBuilder("kudu").Build(&messenger));
+  ConsensusServiceProxy proxy(messenger, leader_addrs[0]);
+
+  consensus::ChangeConfigRequestPB req;
+  consensus::ChangeConfigResponsePB resp;
+  RpcController rpc;
+  rpc.set_timeout(client->default_admin_operation_timeout());
+  req.set_dest_uuid(leader_uuid);
+  req.set_tablet_id(tablet_id);
+  req.set_type(cc_type);
+  *req.mutable_server() = peer_pb;
+  RETURN_NOT_OK(proxy.ChangeConfig(req, &resp, &rpc));
+  if (resp.has_error()) {
+    return StatusFromPB(resp.error().status());
+  }
+  return Status::OK();
+}
+
+Status AddReplica(const RunnerContext& context) {
+  return ChangeConfig(context, consensus::ADD_SERVER);
+}
+
+Status ChangeReplicaType(const RunnerContext& context) {
+  return ChangeConfig(context, consensus::CHANGE_ROLE);
+}
+
+Status RemoveReplica(const RunnerContext& context) {
+  return ChangeConfig(context, consensus::REMOVE_SERVER);
+}
+
+
+} // anonymous namespace
+
+unique_ptr<Mode> BuildTabletMode() {
+  unique_ptr<Action> add_replica =
+      ActionBuilder("add_replica", &AddReplica)
+      .Description("Add a new replica to a tablet's Raft configuration")
+      .AddRequiredParameter({
+        kMasterAddressesArg,
+        "Comma-separated list of Kudu Master addresses where each address is "
+        "of form 'hostname:port'" })
+      .AddRequiredParameter({ kTabletIdArg, "Tablet Identifier" })
+      .AddRequiredParameter({ kReplicaUuidArg, "New replica's UUID" })
+      .AddRequiredParameter(
+          { kReplicaTypeArg, "New replica's type. Must be VOTER or NON-VOTER."
+          })
+      .Build();
+
+  unique_ptr<Action> change_replica_type =
+      ActionBuilder("change_replica_type", &ChangeReplicaType)
+      .Description(
+          "Change the type of an existing replica in a tablet's Raft configuration")
+      .AddRequiredParameter({
+        kMasterAddressesArg,
+        "Comma-separated list of Kudu Master addresses where each address is "
+        "of 'form hostname:port'" })
+      .AddRequiredParameter({ kTabletIdArg, "Tablet Identifier" })
+      .AddRequiredParameter({ kReplicaUuidArg, "Existing replica's UUID" })
+      .AddRequiredParameter(
+          { kReplicaTypeArg, "Existing replica's new type. Must be VOTER or NON-VOTER."
+          })
+      .Build();
+
+  unique_ptr<Action> remove_replica =
+      ActionBuilder("remove_replica", &RemoveReplica)
+      .Description("Remove an existing replica from a tablet's Raft configuration")
+      .AddRequiredParameter({
+        kMasterAddressesArg,
+        "Comma-separated list of Kudu Master addresses where each address is "
+        "of form 'hostname:port'" })
+      .AddRequiredParameter({ kTabletIdArg, "Tablet Identifier" })
+      .AddRequiredParameter({ kReplicaUuidArg, "Existing replica's UUID" })
+      .Build();
+
+  unique_ptr<Mode> change_config =
+      ModeBuilder("change_config")
+      .Description("Change a tablet's Raft configuration")
+      .AddAction(std::move(add_replica))
+      .AddAction(std::move(change_replica_type))
+      .AddAction(std::move(remove_replica))
+      .Build();
+
+  return ModeBuilder("tablet")
+      .Description("Operate on remote Kudu tablets")
+      .AddMode(std::move(change_config))
+      .Build();
+}
+
+} // namespace tools
+} // namespace kudu
+

http://git-wip-us.apache.org/repos/asf/kudu/blob/d46ac5f6/src/kudu/tools/tool_main.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_main.cc b/src/kudu/tools/tool_main.cc
index c18065d..3c8bb63 100644
--- a/src/kudu/tools/tool_main.cc
+++ b/src/kudu/tools/tool_main.cc
@@ -115,6 +115,8 @@ int RunTool(int argc, char** argv, bool show_help) {
     .AddMode(BuildFsMode())
     .AddMode(BuildLocalReplicaMode())
     .AddMode(BuildPbcMode())
+    .AddMode(BuildTableMode())
+    .AddMode(BuildTabletMode())
     .AddMode(BuildWalMode())
     .Build();
 


[2/2] kudu git commit: Update some comments in Tablet impl and rename a method

Posted by mp...@apache.org.
Update some comments in Tablet impl and rename a method

Rename DoCompactionOrFlush() to DoMergeCompactionOrFlush() to be more
specific about what it does.

Change-Id: I3baf4ce119a37bf92d410c4049e308253e238532
Reviewed-on: http://gerrit.cloudera.org:8080/3981
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/2ecf034c
Tree: http://git-wip-us.apache.org/repos/asf/kudu/tree/2ecf034c
Diff: http://git-wip-us.apache.org/repos/asf/kudu/diff/2ecf034c

Branch: refs/heads/master
Commit: 2ecf034c8dff6d2edb3ea7ee046ea621b2910906
Parents: d46ac5f
Author: Mike Percy <mp...@apache.org>
Authored: Mon Aug 15 02:41:21 2016 -0700
Committer: Mike Percy <mp...@apache.org>
Committed: Sat Sep 10 16:53:38 2016 +0000

----------------------------------------------------------------------
 src/kudu/tablet/tablet.cc | 13 +++++++------
 src/kudu/tablet/tablet.h  |  5 +++--
 2 files changed, 10 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kudu/blob/2ecf034c/src/kudu/tablet/tablet.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tablet/tablet.cc b/src/kudu/tablet/tablet.cc
index d223c1f..089ec28 100644
--- a/src/kudu/tablet/tablet.cc
+++ b/src/kudu/tablet/tablet.cc
@@ -810,7 +810,7 @@ Status Tablet::FlushInternal(const RowSetsInCompaction& input,
   input.DumpToLog();
   LOG_WITH_PREFIX(INFO) << "Memstore in-memory size: " << old_ms->memory_footprint() << " bytes";
 
-  RETURN_NOT_OK(DoCompactionOrFlush(input, mrs_being_flushed));
+  RETURN_NOT_OK(DoMergeCompactionOrFlush(input, mrs_being_flushed));
 
   // Sanity check that no insertions happened during our flush.
   CHECK_EQ(start_insert_count, old_ms->debug_insert_count())
@@ -1270,10 +1270,11 @@ Status Tablet::FlushMetadata(const RowSetVector& to_remove,
   return metadata_->UpdateAndFlush(to_remove_meta, to_add, mrs_being_flushed);
 }
 
-Status Tablet::DoCompactionOrFlush(const RowSetsInCompaction &input, int64_t mrs_being_flushed) {
+Status Tablet::DoMergeCompactionOrFlush(const RowSetsInCompaction &input,
+                                        int64_t mrs_being_flushed) {
   const char *op_name =
         (mrs_being_flushed == TabletMetadata::kNoMrsFlushed) ? "Compaction" : "Flush";
-  TRACE_EVENT2("tablet", "Tablet::DoCompactionOrFlush",
+  TRACE_EVENT2("tablet", "Tablet::DoMergeCompactionOrFlush",
                "tablet_id", tablet_id(),
                "op", op_name);
 
@@ -1355,7 +1356,7 @@ Status Tablet::DoCompactionOrFlush(const RowSetsInCompaction &input, int64_t mrs
   // - Now we run the "ReupdateMissedDeltas", and copy over the first transaction to the output
   //   DMS, which later flushes.
   // The end result would be that redos[0] has timestamp 2, and redos[1] has timestamp 1.
-  // This breaks an invariant that the redo files are time-ordered, and would we would probably
+  // This breaks an invariant that the redo files are time-ordered, and we would probably
   // reapply the deltas in the wrong order on the read path.
   //
   // The way that we avoid this case is that DuplicatingRowSet's FlushDeltas method is a
@@ -1498,8 +1499,8 @@ Status Tablet::Compact(CompactFlags flags) {
 
   input.DumpToLog();
 
-  return DoCompactionOrFlush(input,
-                             TabletMetadata::kNoMrsFlushed);
+  return DoMergeCompactionOrFlush(input,
+                                  TabletMetadata::kNoMrsFlushed);
 }
 
 void Tablet::UpdateCompactionStats(MaintenanceOpStats* stats) {

http://git-wip-us.apache.org/repos/asf/kudu/blob/2ecf034c/src/kudu/tablet/tablet.h
----------------------------------------------------------------------
diff --git a/src/kudu/tablet/tablet.h b/src/kudu/tablet/tablet.h
index f0b8b31..72b08f4 100644
--- a/src/kudu/tablet/tablet.h
+++ b/src/kudu/tablet/tablet.h
@@ -437,8 +437,9 @@ class Tablet {
   Status PickRowSetsToCompact(RowSetsInCompaction *picked,
                               CompactFlags flags) const;
 
-  Status DoCompactionOrFlush(const RowSetsInCompaction &input,
-                             int64_t mrs_being_flushed);
+  // Performs a merge compaction or a flush.
+  Status DoMergeCompactionOrFlush(const RowSetsInCompaction &input,
+                                  int64_t mrs_being_flushed);
 
   // Handle the case in which a compaction or flush yielded no output rows.
   // In this case, we just need to remove the rowsets in 'rowsets' from the