This is an automated email from the ASF dual-hosted git repository.

awong pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kudu.git


The following commit(s) were added to refs/heads/master by this push:
     new 36c251a  KUDU-2612 p4: mechanism to create a transaction status table
36c251a is described below

commit 36c251a99e2243beb6be5173eb9cdad6fb74e4ed
Author: Andrew Wong <[email protected]>
AuthorDate: Fri Jun 26 15:53:09 2020 -0700

    KUDU-2612 p4: mechanism to create a transaction status table
    
    This plumbs table type into the master, and exercises the code paths by
    adding a TxnSystemClient that can set the table type when creating
    tables (as opposed to KuduClient, which cannot send such requests).
    
    This patch adds the ability to create a transaction status table named
    "kudu_system.kudu_transactions", which foregoes fine-grained
    authorization checks and HMS synchronization.
    
    Coarse-grained access to this table is table is granted only to the
    service- or super-user. For the sake of the create-table and
    add-partition functionality (which will likely come from the Kudu
    service rather than a user), the CreateTable and AlterTable master RPC
    endpoints now permit access to the service user.
    
    This TxnSystemClient can be used by leader masters in the future to
    create the initial transaction status table, but for now is useful for
    testing the plumbing of table type.
    
    Change-Id: I29b9009fa228a7749295b50516991613a28d58fa
    Reviewed-on: http://gerrit.cloudera.org:8080/16124
    Reviewed-by: Alexey Serbin <[email protected]>
    Tested-by: Kudu Jenkins
---
 src/kudu/client/client.cc                          |   4 +
 src/kudu/client/client.h                           |   5 +
 src/kudu/client/table_creator-internal.h           |   2 +
 src/kudu/integration-tests/CMakeLists.txt          |   1 +
 src/kudu/integration-tests/master_authz-itest.cc   |  31 ++
 src/kudu/integration-tests/master_hms-itest.cc     |  24 ++
 .../integration-tests/txn_status_table-itest.cc    | 326 +++++++++++++++++++++
 src/kudu/master/CMakeLists.txt                     |   1 +
 src/kudu/master/catalog_manager.cc                 |  75 +++--
 src/kudu/master/catalog_manager.h                  |   2 +-
 src/kudu/master/master.proto                       |  16 +-
 src/kudu/server/server_base.cc                     |   4 +
 src/kudu/server/server_base.h                      |   3 +
 src/kudu/tablet/tablet_metadata.cc                 |  29 +-
 src/kudu/transactions/CMakeLists.txt               |   4 +-
 src/kudu/transactions/txn_status_tablet.cc         |   1 +
 src/kudu/transactions/txn_status_tablet.h          |   1 +
 src/kudu/transactions/txn_system_client.cc         | 101 +++++++
 src/kudu/transactions/txn_system_client.h          |  81 +++++
 src/kudu/tserver/tablet_service.cc                 |  12 +-
 20 files changed, 673 insertions(+), 50 deletions(-)

diff --git a/src/kudu/client/client.cc b/src/kudu/client/client.cc
index 2612022..f748763 100644
--- a/src/kudu/client/client.cc
+++ b/src/kudu/client/client.cc
@@ -853,6 +853,10 @@ Status KuduTableCreator::Create() {
 
   req.mutable_partition_schema()->CopyFrom(data_->partition_schema_);
 
+  if (data_->table_type_) {
+    req.set_table_type(*data_->table_type_);
+  }
+
   MonoTime deadline = MonoTime::Now();
   if (data_->timeout_.Initialized()) {
     deadline += data_->timeout_;
diff --git a/src/kudu/client/client.h b/src/kudu/client/client.h
index 29b7e6f..c1cde39 100644
--- a/src/kudu/client/client.h
+++ b/src/kudu/client/client.h
@@ -66,6 +66,10 @@ class KuduClient;
 class KuduTable;
 } // namespace client
 
+namespace transactions {
+class TxnSystemClient;
+} // namespace transactions
+
 namespace tools {
 class LeaderMasterProxy;
 class RemoteKsckCluster;
@@ -963,6 +967,7 @@ class KUDU_EXPORT KuduTableCreator {
   class KUDU_NO_EXPORT Data;
 
   friend class KuduClient;
+  friend class transactions::TxnSystemClient;
 
   explicit KuduTableCreator(KuduClient* client);
 
diff --git a/src/kudu/client/table_creator-internal.h 
b/src/kudu/client/table_creator-internal.h
index a8c6cb9..36225ca 100644
--- a/src/kudu/client/table_creator-internal.h
+++ b/src/kudu/client/table_creator-internal.h
@@ -68,6 +68,8 @@ class KuduTableCreator::Data {
 
   boost::optional<std::map<std::string, std::string>> extra_configs_;
 
+  boost::optional<TableTypePB> table_type_;
+
   MonoDelta timeout_;
 
   bool wait_;
diff --git a/src/kudu/integration-tests/CMakeLists.txt 
b/src/kudu/integration-tests/CMakeLists.txt
index 662c641..dbc7eff 100644
--- a/src/kudu/integration-tests/CMakeLists.txt
+++ b/src/kudu/integration-tests/CMakeLists.txt
@@ -131,6 +131,7 @@ ADD_KUDU_TEST(tombstoned_voting-imc-itest)
 ADD_KUDU_TEST(tombstoned_voting-itest)
 ADD_KUDU_TEST(tombstoned_voting-stress-test RUN_SERIAL true)
 ADD_KUDU_TEST(token_signer-itest)
+ADD_KUDU_TEST(txn_status_table-itest)
 ADD_KUDU_TEST(location_assignment-itest
   DATA_FILES ../scripts/assign-location.py)
 ADD_KUDU_TEST(ts_authz-itest NUM_SHARDS 2)
diff --git a/src/kudu/integration-tests/master_authz-itest.cc 
b/src/kudu/integration-tests/master_authz-itest.cc
index 63d6050..2b712ba 100644
--- a/src/kudu/integration-tests/master_authz-itest.cc
+++ b/src/kudu/integration-tests/master_authz-itest.cc
@@ -46,8 +46,10 @@
 #include "kudu/rpc/rpc_controller.h"
 #include "kudu/rpc/user_credentials.h"
 #include "kudu/security/test/mini_kdc.h"
+#include "kudu/transactions/txn_system_client.h"
 #include "kudu/util/decimal_util.h"
 #include "kudu/util/monotime.h"
+#include "kudu/util/net/net_util.h"
 #include "kudu/util/slice.h"
 #include "kudu/util/status.h"
 #include "kudu/util/test_macros.h"
@@ -81,6 +83,7 @@ using kudu::ranger::MiniRanger;
 using kudu::ranger::PolicyItem;
 using kudu::rpc::RpcController;
 using kudu::rpc::UserCredentials;
+using kudu::transactions::TxnSystemClient;
 using std::function;
 using std::move;
 using std::ostream;
@@ -735,6 +738,34 @@ INSTANTIATE_TEST_CASE_P(AuthzProviders, MasterAuthzITest,
       return HarnessEnumToString(info.param);
     });
 
+// Test that creation of the transaction status table foregoes fine-grained
+// authorization.
+TEST_P(MasterAuthzITest, TestCreateTransactionStatusTable) {
+  // Create a transaction status table and add a range. Both requests should
+  // succeed, despite no privileges being granted in Ranger.
+  vector<string> master_addrs;
+  for (const auto& hp : cluster_->master_rpc_addrs()) {
+    master_addrs.emplace_back(hp.ToString());
+  }
+  ASSERT_OK(this->cluster_->kdc()->Kinit(kTestUser));
+  // Attempting to call system client DDL as a non-admin should result in a
+  // NotAuthorized error.
+  {
+    unique_ptr<TxnSystemClient> non_admin_client;
+    ASSERT_OK(TxnSystemClient::Create(master_addrs, &non_admin_client));
+    Status s = non_admin_client->CreateTxnStatusTable(100);
+    ASSERT_TRUE(s.IsNotAuthorized()) << s.ToString();
+    s = non_admin_client->AddTxnStatusTableRange(100, 200);
+    ASSERT_TRUE(s.IsNotAuthorized()) << s.ToString();
+  }
+  // But as service user, we should have no trouble making the calls.
+  ASSERT_OK(this->cluster_->kdc()->Kinit(kAdminUser));
+  unique_ptr<TxnSystemClient> txn_sys_client;
+  ASSERT_OK(TxnSystemClient::Create(master_addrs, &txn_sys_client));
+  ASSERT_OK(txn_sys_client->CreateTxnStatusTable(100));
+  ASSERT_OK(txn_sys_client->AddTxnStatusTableRange(100, 200));
+}
+
 TEST_P(MasterAuthzITest, TestCreateTableAuthorized) {
   ASSERT_OK(cluster_->kdc()->Kinit(kAdminUser));
   ASSERT_OK(cluster_->CreateClient(nullptr, &client_));
diff --git a/src/kudu/integration-tests/master_hms-itest.cc 
b/src/kudu/integration-tests/master_hms-itest.cc
index 8768c7b..8107b0c 100644
--- a/src/kudu/integration-tests/master_hms-itest.cc
+++ b/src/kudu/integration-tests/master_hms-itest.cc
@@ -42,7 +42,9 @@
 #include "kudu/mini-cluster/mini_cluster.h"
 #include "kudu/security/test/mini_kdc.h"
 #include "kudu/thrift/client.h"
+#include "kudu/transactions/txn_system_client.h"
 #include "kudu/util/monotime.h"
+#include "kudu/util/net/net_util.h"
 #include "kudu/util/status.h"
 #include "kudu/util/test_macros.h"
 #include "kudu/util/test_util.h"
@@ -55,6 +57,7 @@ using kudu::client::KuduTableAlterer;
 using kudu::client::sp::shared_ptr;
 using kudu::cluster::ExternalMiniClusterOptions;
 using kudu::hms::HmsClient;
+using kudu::transactions::TxnSystemClient;
 using std::string;
 using std::unique_ptr;
 using std::vector;
@@ -608,6 +611,27 @@ TEST_F(MasterHmsTest, TestUppercaseIdentifiers) {
   NO_FATALS(CheckTableDoesNotExist("default", "abc"));
 }
 
+// Test that we can create a transaction status table that doesn't get
+// synchronized to the HMS.
+TEST_F(MasterHmsTest, TestTransactionStatusTableDoesntSync) {
+  // Create a transaction status table.
+  vector<string> master_addrs;
+  for (const auto& hp : cluster_->master_rpc_addrs()) {
+    master_addrs.emplace_back(hp.ToString());
+  }
+  unique_ptr<TxnSystemClient> txn_sys_client;
+  ASSERT_OK(TxnSystemClient::Create(master_addrs, &txn_sys_client));
+  ASSERT_OK(txn_sys_client->CreateTxnStatusTable(100));
+
+  // We shouldn't see the table in the HMS catalog.
+  vector<string> tables;
+  ASSERT_OK(harness_.hms_client()->GetTableNames("kudu_system", &tables));
+  ASSERT_TRUE(tables.empty());
+
+  // We should still be able to add partitions.
+  ASSERT_OK(txn_sys_client->AddTxnStatusTableRange(100, 200));
+}
+
 class MasterHmsUpgradeTest : public MasterHmsTest {
  public:
   HmsMode GetHmsMode() override {
diff --git a/src/kudu/integration-tests/txn_status_table-itest.cc 
b/src/kudu/integration-tests/txn_status_table-itest.cc
new file mode 100644
index 0000000..cc73101
--- /dev/null
+++ b/src/kudu/integration-tests/txn_status_table-itest.cc
@@ -0,0 +1,326 @@
+// 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 <functional>
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <boost/none_t.hpp>
+#include <boost/optional/optional.hpp>
+#include <gflags/gflags_declare.h>
+#include <glog/logging.h>
+#include <gtest/gtest.h>
+
+#include "kudu/client/client.h"
+#include "kudu/client/client.pb.h"
+#include "kudu/common/common.pb.h"
+#include "kudu/gutil/map-util.h"
+#include "kudu/gutil/ref_counted.h"
+#include "kudu/integration-tests/test_workload.h"
+#include "kudu/master/master.h"
+#include "kudu/master/mini_master.h"
+#include "kudu/master/ts_manager.h"
+#include "kudu/mini-cluster/internal_mini_cluster.h"
+#include "kudu/mini-cluster/mini_cluster.h"
+#include "kudu/tablet/tablet_metadata.h"
+#include "kudu/tablet/tablet_replica.h"
+#include "kudu/transactions/txn_status_tablet.h"
+#include "kudu/transactions/txn_system_client.h"
+#include "kudu/tserver/mini_tablet_server.h"
+#include "kudu/tserver/tablet_server.h"
+#include "kudu/tserver/ts_tablet_manager.h"
+#include "kudu/util/net/net_util.h"
+#include "kudu/util/status.h"
+#include "kudu/util/test_macros.h"
+#include "kudu/util/test_util.h"
+
+DECLARE_string(superuser_acl);
+DECLARE_string(user_acl);
+
+using kudu::client::AuthenticationCredentialsPB;
+using kudu::client::KuduClient;
+using kudu::client::KuduClientBuilder;
+using kudu::client::KuduTable;
+using kudu::cluster::InternalMiniCluster;
+using kudu::tablet::TabletReplica;
+using kudu::transactions::TxnSystemClient;
+using kudu::transactions::TxnStatusTablet;
+using std::map;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace kudu {
+namespace itest {
+
+class TxnStatusTableITest : public KuduTest {
+ public:
+  TxnStatusTableITest() {}
+
+  void SetUp() override {
+    KuduTest::SetUp();
+    cluster_.reset(new InternalMiniCluster(env_, {}));
+    ASSERT_OK(cluster_->Start());
+
+    // Create the txn system client with which to communicate with the cluster.
+    vector<string> master_addrs;
+    for (const auto& hp : cluster_->master_rpc_addrs()) {
+      master_addrs.emplace_back(hp.ToString());
+    }
+    ASSERT_OK(TxnSystemClient::Create(master_addrs, &txn_sys_client_));
+  }
+
+  // Ensures that all replicas have the right table type set.
+  void CheckTableTypes(const map<TableTypePB, int>& expected_counts) {
+    // Check that the tablets of the table all have transaction coordinators.
+    vector<scoped_refptr<TabletReplica>> replicas;
+    for (int i = 0; i < cluster_->num_tablet_servers(); i++) {
+      auto* ts = cluster_->mini_tablet_server(i);
+      const auto& tablets = ts->ListTablets();
+      for (const auto& t : tablets) {
+        scoped_refptr<TabletReplica> r;
+        ASSERT_TRUE(ts->server()->tablet_manager()->LookupTablet(t, &r));
+        replicas.emplace_back(std::move(r));
+      }
+    }
+    map<TableTypePB, int> type_counts;
+    for (const auto& r : replicas) {
+      const auto optional_table_type = r->tablet_metadata()->table_type();
+      if (boost::none == optional_table_type) {
+        LookupOrEmplace(&type_counts, TableTypePB::DEFAULT_TABLE, 0)++;
+      } else {
+        ASSERT_EQ(TableTypePB::TXN_STATUS_TABLE, *optional_table_type);
+        ASSERT_NE(nullptr, r->txn_coordinator());
+        LookupOrEmplace(&type_counts, *optional_table_type, 0)++;
+      }
+    }
+    ASSERT_EQ(expected_counts, type_counts);
+  }
+
+  // Creates and returns a client as the given user.
+  Status CreateClientAs(const string& user, 
client::sp::shared_ptr<KuduClient>* client) {
+    KuduClientBuilder client_builder;
+    string authn_creds;
+    AuthenticationCredentialsPB pb;
+    pb.set_real_user(user);
+    CHECK(pb.SerializeToString(&authn_creds));
+    client_builder.import_authentication_credentials(authn_creds);
+    client::sp::shared_ptr<KuduClient> c;
+    RETURN_NOT_OK(cluster_->CreateClient(&client_builder, &c));
+    *client = std::move(c);
+    return Status::OK();
+  }
+
+  // Sets the superuser and user ACLs, restarts the master, and waits for some
+  // tablet servers to register, yielding a fatal if anything fails.
+  void SetSuperuserAndUser(const string& superuser, const string& user) {
+    FLAGS_superuser_acl = superuser;
+    FLAGS_user_acl = user;
+    cluster_->mini_master()->Shutdown();
+    ASSERT_OK(cluster_->mini_master()->Restart());
+    ASSERT_EVENTUALLY([&] {
+      ASSERT_LT(0, 
cluster_->mini_master()->master()->ts_manager()->GetLiveCount());
+    });
+  }
+
+  client::sp::shared_ptr<KuduClient> client_sp() {
+    return txn_sys_client_->client_;
+  }
+
+ protected:
+  unique_ptr<InternalMiniCluster> cluster_;
+  unique_ptr<TxnSystemClient> txn_sys_client_;
+};
+
+// Test that the transaction status table is not listed. We expect it to be
+// fairly common for a super-user to list tables to backup a cluster, so we
+// hide the transaction status table, which we don't expect to back up.
+TEST_F(TxnStatusTableITest, TestTxnStatusTableNotListed) {
+  ASSERT_OK(txn_sys_client_->CreateTxnStatusTable(100));
+  auto client = client_sp();
+  vector<string> tables_list;
+  ASSERT_OK(client->ListTables(&tables_list));
+  ASSERT_TRUE(tables_list.empty());
+
+  // While we prevent the table from being listed, clients should still be able
+  // to view it if it's explicitly asked for.
+  const string& kTableName = TxnStatusTablet::kTxnStatusTableName;
+  bool exists = false;
+  ASSERT_OK(client->TableExists(kTableName, &exists));
+  ASSERT_TRUE(exists);
+
+  client::sp::shared_ptr<KuduTable> table;
+  ASSERT_OK(client->OpenTable(kTableName, &table));
+  ASSERT_NE(nullptr, table);
+}
+
+// Test that only the service- or super-user can create or alter the
+// transaction status table.
+TEST_F(TxnStatusTableITest, TestProtectCreateAndAlter) {
+  auto service_client = client_sp();
+  // We're both a super user and a service user since the ACLs default to "*".
+  // We should thus have access to the table.
+  ASSERT_OK(TxnSystemClient::CreateTxnStatusTableWithClient(100, 
service_client.get()));
+  ASSERT_OK(TxnSystemClient::AddTxnStatusTableRangeWithClient(100, 200, 
service_client.get()));
+  const string& kTableName = TxnStatusTablet::kTxnStatusTableName;
+  ASSERT_OK(service_client->DeleteTable(kTableName));
+
+  // The service user should be able to create and alter the transaction status
+  // table.
+  NO_FATALS(SetSuperuserAndUser("nobody", "nobody"));
+  ASSERT_OK(TxnSystemClient::CreateTxnStatusTableWithClient(100, 
service_client.get()));
+  ASSERT_OK(TxnSystemClient::AddTxnStatusTableRangeWithClient(100, 200, 
service_client.get()));
+
+  // The service user doesn't have access to the delete table endpoint.
+  Status s = service_client->DeleteTable(kTableName);
+  ASSERT_TRUE(s.IsRemoteError()) << s.ToString();
+  ASSERT_STR_CONTAINS(s.ToString(), "Not authorized");
+
+  // The super user should be able to create, alter, and delete the transaction
+  // status table.
+  NO_FATALS(SetSuperuserAndUser("bob", "nobody"));
+  client::sp::shared_ptr<KuduClient> bob_client;
+  ASSERT_OK(CreateClientAs("bob", &bob_client));
+  ASSERT_OK(bob_client->DeleteTable(kTableName));
+  ASSERT_OK(TxnSystemClient::CreateTxnStatusTableWithClient(100, 
bob_client.get()));
+  ASSERT_OK(TxnSystemClient::AddTxnStatusTableRangeWithClient(100, 200, 
bob_client.get()));
+  ASSERT_OK(bob_client->DeleteTable(kTableName));
+
+  // Regular users shouldn't be able to do anything.
+  NO_FATALS(SetSuperuserAndUser("nobody", "bob"));
+  s = TxnSystemClient::CreateTxnStatusTableWithClient(100, bob_client.get());
+  ASSERT_TRUE(s.IsNotAuthorized()) << s.ToString();
+  ASSERT_OK(TxnSystemClient::CreateTxnStatusTableWithClient(100, 
service_client.get()));
+  s = TxnSystemClient::AddTxnStatusTableRangeWithClient(100, 200, 
bob_client.get());
+  ASSERT_TRUE(s.IsNotAuthorized()) << s.ToString();
+  s = service_client->DeleteTable(kTableName);
+  ASSERT_TRUE(s.IsRemoteError()) << s.ToString();
+  ASSERT_STR_CONTAINS(s.ToString(), "Not authorized");
+}
+
+// Test that accessing the transaction system table only succeeds for service
+// or super users.
+TEST_F(TxnStatusTableITest, TestProtectAccess) {
+  ASSERT_OK(txn_sys_client_->CreateTxnStatusTable(100));
+  auto client_sp = this->client_sp();
+  const string& kTableName = TxnStatusTablet::kTxnStatusTableName;
+#define STORE_AND_PREPEND(s, msg) do { \
+    Status _s = (s); \
+    results.emplace_back(_s.CloneAndPrepend(msg)); \
+  } while (0)
+  const auto attempt_accesses_and_delete = [&] (KuduClient* client) {
+    // Go through various table access methods on the transaction status table.
+    // Collect the results so we can verify whether we were authorized
+    // properly.
+    vector<Status> results;
+    bool unused;
+    STORE_AND_PREPEND(client->TableExists(kTableName, &unused), "failed to 
check existence");
+    client::sp::shared_ptr<KuduTable> unused_table;
+    STORE_AND_PREPEND(client->OpenTable(kTableName, &unused_table), "failed to 
open table");
+    client::KuduTableStatistics* unused_stats = nullptr;
+    STORE_AND_PREPEND(client->GetTableStatistics(kTableName, &unused_stats), 
"failed to get stats");
+    unique_ptr<client::KuduTableStatistics> unused_unique_stats(unused_stats);
+    STORE_AND_PREPEND(client->DeleteTable(kTableName), "failed to delete 
table");
+    return results;
+  };
+  // We're both a super user and a service user since the ACLs default to "*".
+  // We should thus have access to the table.
+  auto results = attempt_accesses_and_delete(client_sp.get());
+  for (const auto& r : results) {
+    ASSERT_OK(r);
+  }
+  ASSERT_OK(txn_sys_client_->CreateTxnStatusTable(100));
+
+  // Even though we're the service user, we shouldn't be able to access
+  // anything because most RPCs require us to be a super-user or user.
+  NO_FATALS(SetSuperuserAndUser("nobody", "nobody"));
+  results = attempt_accesses_and_delete(client_sp.get());
+  for (const auto& r : results) {
+    // NOTE: we don't check the type because authorization errors may surface
+    // as RemoteErrors or NotAuthorized errors.
+    ASSERT_FALSE(r.ok());
+    ASSERT_STR_CONTAINS(r.ToString(), "Not authorized");
+  }
+  // As a sanity check, our last delete have failed, and we should be unable to
+  // create another transaction status table.
+  Status s = txn_sys_client_->CreateTxnStatusTable(100);
+  ASSERT_TRUE(s.IsAlreadyPresent()) << s.ToString();
+
+  // If we're a super user but not a service user, we should have access to the
+  // table.
+  NO_FATALS(SetSuperuserAndUser("bob", "nobody"));
+  // Create a client as 'bob', who is not the service user.
+  client::sp::shared_ptr<KuduClient> bob_client;
+  ASSERT_OK(CreateClientAs("bob", &bob_client));
+  results = attempt_accesses_and_delete(bob_client.get());
+  for (const auto& r : results) {
+    ASSERT_OK(r);
+  }
+  ASSERT_OK(txn_sys_client_->CreateTxnStatusTable(100));
+
+  // If we're a regular user and nothing more, we shouldn't be able to access
+  // the table.
+  NO_FATALS(SetSuperuserAndUser("nobody", "bob"));
+  results = attempt_accesses_and_delete(bob_client.get());
+  for (const auto& r : results) {
+    ASSERT_FALSE(r.ok());
+    ASSERT_STR_CONTAINS(r.ToString(), "Not authorized");
+  }
+  s = txn_sys_client_->CreateTxnStatusTable(100);
+  ASSERT_TRUE(s.IsAlreadyPresent()) << s.ToString();
+}
+
+// Test that DDL for the transaction status table results in the creation of
+// transaction status tablets.
+TEST_F(TxnStatusTableITest, TestCreateTxnStatusTablePartitions) {
+  ASSERT_OK(txn_sys_client_->CreateTxnStatusTable(100));
+  Status s = txn_sys_client_->CreateTxnStatusTable(100);
+  ASSERT_TRUE(s.IsAlreadyPresent()) << s.ToString();
+  NO_FATALS(CheckTableTypes({ { TableTypePB::TXN_STATUS_TABLE, 1 } }));
+
+  // Now add more partitions and try again.
+  ASSERT_OK(txn_sys_client_->AddTxnStatusTableRange(100, 200));
+  s = txn_sys_client_->AddTxnStatusTableRange(100, 200);
+  ASSERT_TRUE(s.IsInvalidArgument()) << s.ToString();
+  NO_FATALS(CheckTableTypes({ { TableTypePB::TXN_STATUS_TABLE, 2 } }));
+
+  // Ensure we still create transaction status tablets even after the master is
+  // restarted.
+  cluster_->ShutdownNodes(cluster::ClusterNodes::MASTERS_ONLY);
+  ASSERT_OK(cluster_->Start());
+  ASSERT_OK(txn_sys_client_->AddTxnStatusTableRange(200, 300));
+  NO_FATALS(CheckTableTypes({ { TableTypePB::TXN_STATUS_TABLE, 3 } }));
+}
+
+// Test that tablet servers can host both transaction status tablets and
+// regular tablets.
+TEST_F(TxnStatusTableITest, TestTxnStatusTableColocatedWithTables) {
+  TestWorkload w(cluster_.get());
+  w.set_num_replicas(1);
+  w.Setup();
+  ASSERT_OK(txn_sys_client_->CreateTxnStatusTable(100));
+  NO_FATALS(CheckTableTypes({
+      { TableTypePB::TXN_STATUS_TABLE, 1 },
+      { TableTypePB::DEFAULT_TABLE, 1 }
+  }));
+}
+
+} // namespace itest
+} // namespace kudu
diff --git a/src/kudu/master/CMakeLists.txt b/src/kudu/master/CMakeLists.txt
index 103abfb..2addf50 100644
--- a/src/kudu/master/CMakeLists.txt
+++ b/src/kudu/master/CMakeLists.txt
@@ -73,6 +73,7 @@ target_link_libraries(master
   server_process
   tablet
   token_proto
+  transactions
   tserver)
 
 # Tests
diff --git a/src/kudu/master/catalog_manager.cc 
b/src/kudu/master/catalog_manager.cc
index 96f687e..6d3617c 100644
--- a/src/kudu/master/catalog_manager.cc
+++ b/src/kudu/master/catalog_manager.cc
@@ -50,7 +50,6 @@
 #include <map>
 #include <memory>
 #include <mutex>
-#include <numeric>
 #include <ostream>
 #include <set>
 #include <string>
@@ -381,9 +380,6 @@ using kudu::tablet::TabletDataState;
 using kudu::tablet::TabletReplica;
 using kudu::tablet::TabletStatePB;
 using kudu::tserver::TabletServerErrorPB;
-using std::accumulate;
-using std::inserter;
-using std::map;
 using std::pair;
 using std::set;
 using std::shared_ptr;
@@ -1542,20 +1538,29 @@ Status CatalogManager::CreateTable(const 
CreateTableRequestPB* orig_req,
     RETURN_NOT_OK(SetupError(ProcessColumnPBDefaults(col), resp, 
MasterErrorPB::INVALID_SCHEMA));
   }
 
-  // a. Validate the user request.
+  bool is_user_table = req.table_type() == TableTypePB::DEFAULT_TABLE;
   const string& normalized_table_name = NormalizeTableName(req.name());
-  if (rpc) {
-    DCHECK_NE(boost::none, user);
-    RETURN_NOT_OK(SetupError(
-        authz_provider_->AuthorizeCreateTable(normalized_table_name, 
user.get(), req.owner()),
-        resp, MasterErrorPB::NOT_AUTHORIZED));
-  }
+  if (is_user_table) {
+    // a. Validate the user request.
+    if (rpc) {
+      DCHECK_NE(boost::none, user);
+      RETURN_NOT_OK(SetupError(
+          authz_provider_->AuthorizeCreateTable(normalized_table_name, 
user.get(), req.owner()),
+          resp, MasterErrorPB::NOT_AUTHORIZED));
+    }
 
-  // If the HMS integration is enabled, wait for the notification log listener
-  // to catch up. This reduces the likelihood of attempting to create a table
-  // with a name that conflicts with a table that has just been deleted or
-  // renamed in the HMS.
-  RETURN_NOT_OK(WaitForNotificationLogListenerCatchUp(resp, rpc));
+    // If the HMS integration is enabled, wait for the notification log 
listener
+    // to catch up. This reduces the likelihood of attempting to create a table
+    // with a name that conflicts with a table that has just been deleted or
+    // renamed in the HMS.
+    RETURN_NOT_OK(WaitForNotificationLogListenerCatchUp(resp, rpc));
+  } else {
+    if (user && !master_->IsServiceUserOrSuperUser(*user)) {
+      return SetupError(
+          Status::NotAuthorized("must be a service user or super user to 
create system tables"),
+          resp, MasterErrorPB::NOT_AUTHORIZED);
+    }
+  }
 
   Schema client_schema;
   RETURN_NOT_OK(SchemaFromPB(req.schema(), &client_schema));
@@ -1771,7 +1776,7 @@ Status CatalogManager::CreateTable(const 
CreateTableRequestPB* orig_req,
   //
   // It is critical that this step happen before writing the table to the sys 
catalog,
   // since this step validates that the table name is available in the HMS 
catalog.
-  if (hms_catalog_) {
+  if (hms_catalog_ && is_user_table) {
     CHECK(rpc);
     Status s = hms_catalog_->CreateTable(table->id(), normalized_table_name, 
req.owner(), schema);
     if (!s.ok()) {
@@ -1785,7 +1790,7 @@ Status CatalogManager::CreateTable(const 
CreateTableRequestPB* orig_req,
   // Delete the new HMS entry if we exit early.
   auto abort_hms = MakeScopedCleanup([&] {
       // TODO(dan): figure out how to test this.
-      if (hms_catalog_) {
+      if (hms_catalog_ && is_user_table) {
         TRACE("Rolling back HMS table creation");
         WARN_NOT_OK(hms_catalog_->DropTable(table->id(), 
normalized_table_name),
                     "an error occurred while attempting to delete orphaned HMS 
table entry");
@@ -1886,6 +1891,9 @@ scoped_refptr<TableInfo> CatalogManager::CreateTableInfo(
   metadata->set_version(0);
   metadata->set_next_column_id(ColumnId(schema.max_col_id() + 1));
   metadata->set_num_replicas(req.num_replicas());
+  if (req.has_table_type()) {
+    metadata->set_table_type(req.table_type());
+  }
   // Use the Schema object passed in, since it has the column IDs already 
assigned,
   // whereas the user request PB does not.
   CHECK_OK(SchemaToPB(schema, metadata->mutable_schema()));
@@ -1998,7 +2006,13 @@ Status CatalogManager::FindLockAndAuthorizeTable(
   // found is authorized.
   TableMetadataLock lock(table.get(), lock_mode);
   string table_name = NormalizeTableName(lock.data().name());
-  RETURN_NOT_OK(authorize(table_name, lock.data().owner()));
+  if (lock.data().pb.table_type() == TableTypePB::DEFAULT_TABLE) {
+    RETURN_NOT_OK(authorize(table_name, lock.data().owner()));
+  } else {
+    if (user && !master_->IsServiceUserOrSuperUser(*user)) {
+      return Status::NotAuthorized("must be a service user or super user to 
access system tables");
+    }
+  }
 
   // If the table name and table ID refer to different tables, for example,
   //   1. the ID maps to table A.
@@ -2463,10 +2477,12 @@ Status CatalogManager::AlterTableRpc(const 
AlterTableRequestPB& req,
                                      rpc::RpcContext* rpc) {
   leader_lock_.AssertAcquiredForReading();
 
-  // If the HMS integration is enabled, wait for the notification log listener
-  // to catch up. This reduces the likelihood of attempting to apply an
-  // alteration to a table which has just been renamed or deleted through the 
HMS.
-  RETURN_NOT_OK(WaitForNotificationLogListenerCatchUp(resp, rpc));
+  if (req.modify_external_catalogs()) {
+    // If the HMS integration is enabled, wait for the notification log 
listener
+    // to catch up. This reduces the likelihood of attempting to apply an
+    // alteration to a table which has just been renamed or deleted through 
the HMS.
+    RETURN_NOT_OK(WaitForNotificationLogListenerCatchUp(resp, rpc));
+  }
 
   LOG(INFO) << Substitute("Servicing AlterTable request from $0:\n$1",
                           RequestorString(rpc), SecureShortDebugString(req));
@@ -3006,10 +3022,16 @@ Status CatalogManager::ListTables(const 
ListTablesRequestPB* req,
   unordered_map<string, bool> table_owner_map;
   for (const auto& table_info : tables_info) {
     TableMetadataLock ltm(table_info.get(), LockMode::READ);
-    if (!ltm.data().is_running()) continue; // implies !is_deleted() too
+    const auto& table_data = ltm.data();
+    // Don't list system tables or tables that aren't running
+    // TODO(awong): consider allow for opting into listing system tables.
+    if (!table_data.is_running() ||
+        table_data.pb.table_type() != TableTypePB::DEFAULT_TABLE) {
+      continue;
+    }
 
-    const string& table_name = ltm.data().name();
-    const string& owner = ltm.data().owner();
+    const string& table_name = table_data.name();
+    const string& owner = table_data.owner();
     if (req->has_name_filter()) {
       size_t found = table_name.find(req->name_filter());
       if (found == string::npos) {
@@ -3539,6 +3561,7 @@ class AsyncCreateReplica : public RetrySpecificTSRpcTask {
     req_.mutable_extra_config()->CopyFrom(
         table_lock.data().pb.extra_config());
     req_.set_dimension_label(tablet_lock.data().pb.dimension_label());
+    req_.set_table_type(table_lock.data().pb.table_type());
   }
 
   string type_name() const override { return "CreateTablet"; }
diff --git a/src/kudu/master/catalog_manager.h 
b/src/kudu/master/catalog_manager.h
index 7991d0d..e9182a7 100644
--- a/src/kudu/master/catalog_manager.h
+++ b/src/kudu/master/catalog_manager.h
@@ -574,7 +574,7 @@ class CatalogManager : public 
tserver::TabletReplicaLookupIf {
 
   // Create a new Table with the specified attributes.
   //
-  // The RPC context is provided for logging/tracing purposes,
+  // The RPC context is provded for logging/tracing purposes,
   // but this function does not itself respond to the RPC.
   Status CreateTable(const CreateTableRequestPB* req,
                      CreateTableResponsePB* resp,
diff --git a/src/kudu/master/master.proto b/src/kudu/master/master.proto
index 0259db4..98e8d16 100644
--- a/src/kudu/master/master.proto
+++ b/src/kudu/master/master.proto
@@ -198,6 +198,10 @@ message SysTablesEntryPB {
 
   // The user that owns the table.
   optional string owner = 13;
+
+  // The table type. If not set, it is assumed this table is a user-defined
+  // table, rather than a system table.
+  optional TableTypePB table_type = 14;
 }
 
 // The on-disk entry in the sys.catalog table ("metadata" column) to represent
@@ -501,6 +505,10 @@ message CreateTableRequestPB {
   // dimension-specific placement of tablet replicas corresponding to the 
partitions of
   // the newly created table.
   optional string dimension_label = 10;
+
+  // The table type. If not set, it is assumed this table is a user-defined
+  // table, rather than a system table.
+  optional TableTypePB table_type = 11;
 }
 
 message CreateTableResponsePB {
@@ -996,20 +1004,20 @@ service MasterService {
   }
 
   rpc CreateTable(CreateTableRequestPB) returns (CreateTableResponsePB) {
-    option (kudu.rpc.authz_method) = "AuthorizeClient";
+    option (kudu.rpc.authz_method) = "AuthorizeClientOrServiceUser";
   }
   rpc IsCreateTableDone(IsCreateTableDoneRequestPB) returns 
(IsCreateTableDoneResponsePB) {
-    option (kudu.rpc.authz_method) = "AuthorizeClient";
+    option (kudu.rpc.authz_method) = "AuthorizeClientOrServiceUser";
   }
   rpc DeleteTable(DeleteTableRequestPB) returns (DeleteTableResponsePB) {
     option (kudu.rpc.authz_method) = "AuthorizeClient";
   }
 
   rpc AlterTable(AlterTableRequestPB) returns (AlterTableResponsePB) {
-    option (kudu.rpc.authz_method) = "AuthorizeClient";
+    option (kudu.rpc.authz_method) = "AuthorizeClientOrServiceUser";
   }
   rpc IsAlterTableDone(IsAlterTableDoneRequestPB) returns 
(IsAlterTableDoneResponsePB) {
-    option (kudu.rpc.authz_method) = "AuthorizeClient";
+    option (kudu.rpc.authz_method) = "AuthorizeClientOrServiceUser";
   }
 
   rpc ListTables(ListTablesRequestPB) returns (ListTablesResponsePB) {
diff --git a/src/kudu/server/server_base.cc b/src/kudu/server/server_base.cc
index 89bb5b8..c030e22 100644
--- a/src/kudu/server/server_base.cc
+++ b/src/kudu/server/server_base.cc
@@ -635,6 +635,10 @@ bool ServerBase::IsFromSuperUser(const rpc::RpcContext* 
rpc) {
   return superuser_acl_.UserAllowed(rpc->remote_user().username());
 }
 
+bool ServerBase::IsServiceUserOrSuperUser(const string& user) {
+  return service_acl_.UserAllowed(user) || superuser_acl_.UserAllowed(user);
+}
+
 bool ServerBase::Authorize(rpc::RpcContext* rpc, uint32_t allowed_roles) {
   if ((allowed_roles & SUPER_USER) && IsFromSuperUser(rpc)) {
     return true;
diff --git a/src/kudu/server/server_base.h b/src/kudu/server/server_base.h
index ebc0a6b..feceb7f 100644
--- a/src/kudu/server/server_base.h
+++ b/src/kudu/server/server_base.h
@@ -123,6 +123,9 @@ class ServerBase {
   // Returns whether or not the rpc is from a super-user.
   bool IsFromSuperUser(const rpc::RpcContext* rpc);
 
+  // Returns true if the given user is a service- or super-user.
+  bool IsServiceUserOrSuperUser(const std::string& user);
+
   // Authorize an RPC. 'allowed_roles' is a bitset of which roles from the 
above
   // enum should be allowed to make hthe RPC.
   //
diff --git a/src/kudu/tablet/tablet_metadata.cc 
b/src/kudu/tablet/tablet_metadata.cc
index c4edd3f..095c681 100644
--- a/src/kudu/tablet/tablet_metadata.cc
+++ b/src/kudu/tablet/tablet_metadata.cc
@@ -22,6 +22,7 @@
 #include <mutex>
 #include <ostream>
 #include <string>
+#include <type_traits>
 
 #include <boost/optional/optional.hpp>
 #include <gflags/gflags.h>
@@ -106,19 +107,21 @@ Status TabletMetadata::CreateNew(FsManager* fs_manager,
   auto dir_group_cleanup = MakeScopedCleanup([&]() {
     fs_manager->dd_manager()->DeleteDataDirGroup(tablet_id);
   });
-  scoped_refptr<TabletMetadata> ret(new TabletMetadata(fs_manager,
-                                                       tablet_id,
-                                                       table_name,
-                                                       table_id,
-                                                       schema,
-                                                       partition_schema,
-                                                       partition,
-                                                       
initial_tablet_data_state,
-                                                       
std::move(tombstone_last_logged_opid),
-                                                       supports_live_row_count,
-                                                       std::move(extra_config),
-                                                       
std::move(dimension_label),
-                                                       std::move(table_type)));
+  scoped_refptr<TabletMetadata> ret(new TabletMetadata(
+      fs_manager,
+      tablet_id,
+      table_name,
+      table_id,
+      schema,
+      partition_schema,
+      partition,
+      initial_tablet_data_state,
+      std::move(tombstone_last_logged_opid),
+      supports_live_row_count,
+      std::move(extra_config),
+      std::move(dimension_label),
+      !table_type || *table_type == TableTypePB::DEFAULT_TABLE ?
+          boost::none : std::move(table_type)));
   RETURN_NOT_OK(ret->Flush());
   dir_group_cleanup.cancel();
 
diff --git a/src/kudu/transactions/CMakeLists.txt 
b/src/kudu/transactions/CMakeLists.txt
index 65a5acd..c86c25b 100644
--- a/src/kudu/transactions/CMakeLists.txt
+++ b/src/kudu/transactions/CMakeLists.txt
@@ -32,10 +32,12 @@ target_link_libraries(transactions_proto
 set(TRANSACTIONS_SRCS
   txn_status_entry.cc
   txn_status_manager.cc
-  txn_status_tablet.cc)
+  txn_status_tablet.cc
+  txn_system_client.cc)
 
 add_library(transactions ${TRANSACTIONS_SRCS})
 target_link_libraries(transactions
+  kudu_client
   kudu_common
   tablet
   transactions_proto
diff --git a/src/kudu/transactions/txn_status_tablet.cc 
b/src/kudu/transactions/txn_status_tablet.cc
index 337c659..877a814 100644
--- a/src/kudu/transactions/txn_status_tablet.cc
+++ b/src/kudu/transactions/txn_status_tablet.cc
@@ -196,6 +196,7 @@ const char* const TxnStatusTablet::kTxnIdColName = "txn_id";
 const char* const TxnStatusTablet::kEntryTypeColName = "entry_type";
 const char* const TxnStatusTablet::kIdentifierColName = "identifier";
 const char* const TxnStatusTablet::kMetadataColName = "metadata";
+const char* const TxnStatusTablet::kTxnStatusTableName = 
"kudu_system.kudu_transactions";
 
 TxnStatusTablet::TxnStatusTablet(tablet::TabletReplica* tablet_replica)
     : tablet_replica_(DCHECK_NOTNULL(tablet_replica)) {
diff --git a/src/kudu/transactions/txn_status_tablet.h 
b/src/kudu/transactions/txn_status_tablet.h
index db39ce2..0d9fb4f 100644
--- a/src/kudu/transactions/txn_status_tablet.h
+++ b/src/kudu/transactions/txn_status_tablet.h
@@ -86,6 +86,7 @@ class TxnStatusTablet {
   static const char* const kEntryTypeColName;
   static const char* const kIdentifierColName;
   static const char* const kMetadataColName;
+  static const char* const kTxnStatusTableName;
   enum TxnStatusEntryType {
     TRANSACTION = 1,
     PARTICIPANT = 2,
diff --git a/src/kudu/transactions/txn_system_client.cc 
b/src/kudu/transactions/txn_system_client.cc
new file mode 100644
index 0000000..dbed9f3
--- /dev/null
+++ b/src/kudu/transactions/txn_system_client.cc
@@ -0,0 +1,101 @@
+// 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/transactions/txn_system_client.h"
+
+#include <memory>
+#include <string>
+
+#include <boost/optional/optional.hpp>
+
+#include "kudu/client/client.h"
+#include "kudu/client/schema.h"
+#include "kudu/client/table_creator-internal.h"
+#include "kudu/common/common.pb.h"
+#include "kudu/common/partial_row.h"
+#include "kudu/transactions/txn_status_tablet.h"
+
+using kudu::client::KuduClient;
+using kudu::client::KuduSchema;
+using kudu::client::KuduClientBuilder;
+using kudu::client::KuduTableAlterer;
+using kudu::client::KuduTableCreator;
+using kudu::client::sp::shared_ptr;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace kudu {
+namespace transactions {
+
+Status TxnSystemClient::Create(const vector<string>& master_addrs,
+                               unique_ptr<TxnSystemClient>* sys_client) {
+  KuduClientBuilder builder;
+  builder.master_server_addrs(master_addrs);
+  shared_ptr<KuduClient> client;
+  RETURN_NOT_OK(builder.Build(&client));
+  sys_client->reset(new TxnSystemClient(std::move(client)));
+  return Status::OK();
+}
+
+Status TxnSystemClient::CreateTxnStatusTableWithClient(int64_t 
initial_upper_bound,
+                                                       KuduClient* client) {
+
+  const auto& schema = TxnStatusTablet::GetSchema();
+  const auto kudu_schema = KuduSchema::FromSchema(schema);
+
+  // Add range partitioning to the transaction status table with an initial
+  // upper bound, allowing us to add and drop ranges in the future.
+  unique_ptr<KuduPartialRow> lb(new KuduPartialRow(&schema));
+  unique_ptr<KuduPartialRow> ub(new KuduPartialRow(&schema));
+  RETURN_NOT_OK(lb->SetInt64(TxnStatusTablet::kTxnIdColName, 0));
+  RETURN_NOT_OK(ub->SetInt64(TxnStatusTablet::kTxnIdColName, 
initial_upper_bound));
+
+  unique_ptr<KuduTableCreator> table_creator(client->NewTableCreator());
+  table_creator->data_->table_type_ = TableTypePB::TXN_STATUS_TABLE;
+  // NOTE: we don't set an owner here because, presumably, we're running as a
+  // part of the Kudu service -- the Kudu master should default ownership to
+  // the currently running user, authorizing us as appropriate in so doing.
+  // TODO(awong): ensure that transaction status managers only accept requests
+  // when their replicas are leader. For now, ensure this is the case by making
+  // them non-replicated.
+  return table_creator->schema(&kudu_schema)
+      .set_range_partition_columns({ TxnStatusTablet::kTxnIdColName })
+      .add_range_partition(lb.release(), ub.release())
+      .table_name(TxnStatusTablet::kTxnStatusTableName)
+      .num_replicas(1)
+      .wait(true)
+      .Create();
+}
+
+Status TxnSystemClient::AddTxnStatusTableRangeWithClient(int64_t lower_bound, 
int64_t upper_bound,
+                                                         KuduClient* client) {
+  const auto& schema = TxnStatusTablet::GetSchema();
+  unique_ptr<KuduPartialRow> lb(new KuduPartialRow(&schema));
+  unique_ptr<KuduPartialRow> ub(new KuduPartialRow(&schema));
+  RETURN_NOT_OK(lb->SetInt64(TxnStatusTablet::kTxnIdColName, lower_bound));
+  RETURN_NOT_OK(ub->SetInt64(TxnStatusTablet::kTxnIdColName, upper_bound));
+  unique_ptr<KuduTableAlterer> alterer(
+      client->NewTableAlterer(TxnStatusTablet::kTxnStatusTableName));
+  return alterer->AddRangePartition(lb.release(), ub.release())
+      ->modify_external_catalogs(false)
+      ->wait(true)
+      ->Alter();
+}
+
+} // namespace transactions
+} // namespace kudu
diff --git a/src/kudu/transactions/txn_system_client.h 
b/src/kudu/transactions/txn_system_client.h
new file mode 100644
index 0000000..4737d8b
--- /dev/null
+++ b/src/kudu/transactions/txn_system_client.h
@@ -0,0 +1,81 @@
+// 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.
+#pragma once
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <gtest/gtest_prod.h>
+
+#include "kudu/client/shared_ptr.h" // IWYU pragma: keep
+#include "kudu/util/status.h"
+
+namespace kudu {
+namespace client {
+class KuduClient;
+} // namespace client
+
+namespace itest {
+class TxnStatusTableITest;
+class TxnStatusTableITest_TestProtectCreateAndAlter_Test;
+} // namespace itest
+
+namespace transactions {
+
+// Wrapper around a KuduClient used by Kudu for making transaction-related
+// calls to various servers.
+class TxnSystemClient {
+ public:
+  static Status Create(const std::vector<std::string>& master_addrs,
+                       std::unique_ptr<TxnSystemClient>* sys_client);
+
+  // Creates the transaction status table with a single range partition of the
+  // given upper bound.
+  Status CreateTxnStatusTable(int64_t initial_upper_bound) {
+    return CreateTxnStatusTableWithClient(initial_upper_bound, client_.get());
+  }
+
+  // Adds a new range to the transaction status table with the given bounds.
+  //
+  // TODO(awong): when we implement cleaning up of fully quiesced (i.e. fully
+  // committed or fully aborted) transaction ID ranges, add an API to drop
+  // entire ranges.
+  Status AddTxnStatusTableRange(int64_t lower_bound, int64_t upper_bound) {
+    return AddTxnStatusTableRangeWithClient(lower_bound, upper_bound, 
client_.get());
+  }
+
+ private:
+  friend class itest::TxnStatusTableITest;
+  FRIEND_TEST(itest::TxnStatusTableITest, TestProtectCreateAndAlter);
+
+  explicit TxnSystemClient(client::sp::shared_ptr<client::KuduClient> client)
+      : client_(std::move(client)) {}
+
+  static Status CreateTxnStatusTableWithClient(int64_t initial_upper_bound,
+                                               client::KuduClient* client);
+  static Status AddTxnStatusTableRangeWithClient(int64_t lower_bound, int64_t 
upper_bound,
+                                                 client::KuduClient* client);
+
+  client::sp::shared_ptr<client::KuduClient> client_;
+};
+
+} // namespace transactions
+} // namespace kudu
+
diff --git a/src/kudu/tserver/tablet_service.cc 
b/src/kudu/tserver/tablet_service.cc
index d22759d..fc7c6b7 100644
--- a/src/kudu/tserver/tablet_service.cc
+++ b/src/kudu/tserver/tablet_service.cc
@@ -1227,10 +1227,11 @@ void TabletServiceAdminImpl::CreateTablet(const 
CreateTabletRequestPB* req,
   Partition partition;
   Partition::FromPB(req->partition(), &partition);
 
-  LOG(INFO) << "Processing CreateTablet for tablet " << req->tablet_id()
-            << " (table=" << req->table_name()
-            << " [id=" << req->table_id() << "]), partition="
-            << partition_schema.PartitionDebugString(partition, schema);
+  LOG(INFO) << Substitute("Processing CreateTablet for tablet $0 ($1table=$2 
[id=$3]), "
+                          "partition=$4", req->tablet_id(),
+                          req->has_table_type() ? 
TableTypePB_Name(req->table_type()) + " ": "",
+                          req->table_name(), req->table_id(),
+                          partition_schema.PartitionDebugString(partition, 
schema));
   VLOG(1) << "Full request: " << SecureDebugString(*req);
 
   s = server_->tablet_manager()->CreateNewTablet(
@@ -1243,7 +1244,8 @@ void TabletServiceAdminImpl::CreateTablet(const 
CreateTabletRequestPB* req,
       req->config(),
       req->has_extra_config() ? boost::make_optional(req->extra_config()) : 
boost::none,
       req->has_dimension_label() ? 
boost::make_optional(req->dimension_label()) : boost::none,
-      req->has_table_type() ? boost::make_optional(req->table_type()) : 
boost::none,
+      req->has_table_type() && req->table_type() != TableTypePB::DEFAULT_TABLE 
?
+          boost::make_optional(req->table_type()) : boost::none,
       nullptr);
   if (PREDICT_FALSE(!s.ok())) {
     TabletServerErrorPB::Code code;

Reply via email to