KUDU-1230. Add an admin tool to delete a table

This patch adds 'list_tables' and 'delete_table' to the kudu-admin tool.
Nothing fancy is being done, it's simple enough to use command line
utilities if there's a need to delete tables based on some selection
criteria.

Change-Id: I6d633b54e9cfd8f23c7f5fef6ff504cb04a02f35
Reviewed-on: http://gerrit.cloudera.org:8080/1459
Tested-by: Internal Jenkins
Reviewed-by: Mike Percy <mpercy@cloudera.com>
(cherry picked from commit 61e6cd19f3b350448e6ea649d34a43962fb6f345)
Reviewed-on: http://gerrit.cloudera.org:8080/1496
Reviewed-by: Jean-Daniel Cryans
Tested-by: Jean-Daniel Cryans
diff --git a/src/kudu/tools/kudu-admin-test.cc b/src/kudu/tools/kudu-admin-test.cc
index 80c4638..21ce33a 100644
--- a/src/kudu/tools/kudu-admin-test.cc
+++ b/src/kudu/tools/kudu-admin-test.cc
@@ -18,6 +18,7 @@
 #include <boost/foreach.hpp>
 #include <gtest/gtest.h>
 
+#include "kudu/client/client.h"
 #include "kudu/gutil/map-util.h"
 #include "kudu/gutil/strings/substitute.h"
 #include "kudu/integration-tests/test_workload.h"
@@ -28,10 +29,14 @@
 namespace kudu {
 namespace tools {
 
+using kudu::client::KuduClient;
+using kudu::client::KuduClientBuilder;
 using itest::TabletServerMap;
 using itest::TServerDetails;
 using strings::Substitute;
 
+using std::tr1::shared_ptr;
+
 static const char* const kAdminToolName = "kudu-admin";
 
 class AdminCliTest : public tserver::TabletServerIntegrationTestBase {
@@ -152,5 +157,34 @@
                                                 MonoDelta::FromSeconds(10)));
 }
 
+TEST_F(AdminCliTest, TestDeleteTable) {
+  FLAGS_num_tablet_servers = 1;
+  FLAGS_num_replicas = 1;
+
+  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));
+
+  vector<string> tables;
+  ASSERT_OK(client->ListTables(&tables));
+  ASSERT_TRUE(tables.empty());
+}
+
 } // namespace tools
 } // namespace kudu
diff --git a/src/kudu/tools/kudu-admin.cc b/src/kudu/tools/kudu-admin.cc
index e1ed975..c217489 100644
--- a/src/kudu/tools/kudu-admin.cc
+++ b/src/kudu/tools/kudu-admin.cc
@@ -70,7 +70,7 @@
 using client::KuduTabletServer;
 using consensus::ConsensusServiceProxy;
 using consensus::RaftPeerPB;
-using master::ListTabletServersRequestPB;;
+using master::ListTabletServersRequestPB;
 using master::ListTabletServersResponsePB;
 using master::MasterServiceProxy;
 using master::TabletLocationsPB;
@@ -82,6 +82,8 @@
 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 = NULL;
 
 class ClusterAdminClient {
@@ -100,6 +102,12 @@
                       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,
@@ -120,6 +128,7 @@
   bool initted_;
   shared_ptr<rpc::Messenger> messenger_;
   gscoped_ptr<MasterServiceProxy> master_proxy_;
+  shared_ptr<KuduClient> kudu_client_;
 
   DISALLOW_COPY_AND_ASSIGN(ClusterAdminClient);
 };
@@ -153,6 +162,11 @@
                                << 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();
 }
@@ -309,6 +323,22 @@
                                      "registered with the Master", uuid));
 }
 
+Status ClusterAdminClient::ListTables() {
+  vector<string> tables;
+  RETURN_NOT_OK(kudu_client_->ListTables(&tables));
+  BOOST_FOREACH(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;
 
@@ -316,7 +346,9 @@
       << "<operation> must be one of:\n"
       << "  " << kChangeConfigOp << " <tablet_id> "
               << "<ADD_SERVER|REMOVE_SERVER|CHANGE_ROLE> <peer_uuid> "
-              << "[VOTER|NON_VOTER]";
+              << "[VOTER|NON_VOTER]" << std::endl
+      << "  " << kListTablesOp << std::endl
+      << "  " << kDeleteTableOp << " <table_name>";
   google::SetUsageMessage(str.str());
 }
 
@@ -360,6 +392,23 @@
       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__);