You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kudu.apache.org by gr...@apache.org on 2020/12/08 21:16:06 UTC

[kudu] branch master updated (3e6d061 -> 57c757d)

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

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


    from 3e6d061  [txn] update not-a-leader retry logic in TxnSystemClient
     new 3e02cce  [docs] Fix a typo in the installation docs
     new 70bce76  [tserver] KUDU-2612: participant op RPC endpoint
     new be4fb83  Replace boost::iequals with our own implementation
     new 42e3ae3  [build] CMake fixes for Apple ARM builds
     new 57c757d  txn_participant: return TXN_ILLEGAL_STATE code on illegal state errors

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 CMakeLists.txt                                     |  14 +-
 docs/installation.adoc                             |   2 +-
 src/kudu/clock/hybrid_clock.cc                     |  23 +-
 src/kudu/gutil/CMakeLists.txt                      |   2 +-
 src/kudu/hms/hms_client.cc                         |   9 +-
 src/kudu/integration-tests/hms_itest-base.cc       |   4 +-
 .../integration-tests/txn_participant-itest.cc     | 282 +++++++++++++++++++++
 src/kudu/rpc/sasl_common.cc                        |   6 +-
 src/kudu/security/tls_context.cc                   |   8 +-
 src/kudu/server/default_path_handlers.cc           |  15 +-
 src/kudu/server/server_base.cc                     |   6 +-
 src/kudu/tablet/ops/participant_op.cc              |  27 +-
 src/kudu/tablet/ops/participant_op.h               |   5 +-
 src/kudu/tablet/txn_participant-test.cc            |   3 +
 src/kudu/tablet/txn_participant.h                  |  24 +-
 src/kudu/tools/ksck.cc                             |  12 +-
 src/kudu/tools/ksck.h                              |   2 +-
 src/kudu/tools/kudu-tool-test.cc                   |   5 +-
 src/kudu/tools/table_scanner.cc                    |   4 +-
 src/kudu/tools/tool_action_cluster.cc              |  11 +-
 src/kudu/tools/tool_action_common.cc               |  20 +-
 src/kudu/tools/tool_action_hms.cc                  |  20 +-
 src/kudu/tools/tool_action_master.cc               |  24 +-
 src/kudu/tools/tool_action_table.cc                |   7 +-
 src/kudu/tools/tool_action_test.cc                 |  10 +-
 src/kudu/tools/tool_action_tserver.cc              |  26 +-
 src/kudu/tserver/tablet_service.cc                 |  32 +++
 src/kudu/tserver/tablet_service.h                  |   6 +
 src/kudu/tserver/tserver_admin.proto               |   7 +-
 src/kudu/util/flags.cc                             |   7 +-
 src/kudu/util/string_case-test.cc                  |  21 ++
 src/kudu/util/string_case.cc                       |  29 +++
 src/kudu/util/string_case.h                        |   3 +
 33 files changed, 546 insertions(+), 130 deletions(-)


[kudu] 03/05: Replace boost::iequals with our own implementation

Posted by gr...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit be4fb8383eeda269234e99de07316b5ec422d403
Author: Grant Henke <gr...@apache.org>
AuthorDate: Thu Dec 3 15:27:18 2020 -0600

    Replace boost::iequals with our own implementation
    
    This patch replaces uses of boost::iequals with our own implementation.
    This is a step in our ongoing work to reduce dependence on boost.
    
    Note that we don’t need to worry about locale awareness or performance
    given all usage today is against ASCII strings (not locale specific) and are
    not in performance sensitive code paths.
    
    Change-Id: I6d2fdd30b739cee53e1551f784dbebfbe76e9233
    Reviewed-on: http://gerrit.cloudera.org:8080/16826
    Reviewed-by: Alexey Serbin <as...@cloudera.com>
    Tested-by: Grant Henke <gr...@apache.org>
---
 src/kudu/clock/hybrid_clock.cc               | 23 +++++++++++-----------
 src/kudu/hms/hms_client.cc                   |  9 +++++----
 src/kudu/integration-tests/hms_itest-base.cc |  4 ++--
 src/kudu/rpc/sasl_common.cc                  |  6 +++---
 src/kudu/security/tls_context.cc             |  8 ++++----
 src/kudu/server/default_path_handlers.cc     | 15 +++++++-------
 src/kudu/server/server_base.cc               |  6 +++---
 src/kudu/tools/ksck.cc                       | 12 ++++++------
 src/kudu/tools/ksck.h                        |  2 +-
 src/kudu/tools/kudu-tool-test.cc             |  5 +++--
 src/kudu/tools/table_scanner.cc              |  4 ++--
 src/kudu/tools/tool_action_cluster.cc        | 11 ++++++-----
 src/kudu/tools/tool_action_common.cc         | 20 +++++++++----------
 src/kudu/tools/tool_action_hms.cc            | 20 +++++++++----------
 src/kudu/tools/tool_action_master.cc         | 24 +++++++++++------------
 src/kudu/tools/tool_action_table.cc          |  7 +++----
 src/kudu/tools/tool_action_test.cc           | 10 +++++-----
 src/kudu/tools/tool_action_tserver.cc        | 26 ++++++++++++-------------
 src/kudu/util/flags.cc                       |  7 +++----
 src/kudu/util/string_case-test.cc            | 21 ++++++++++++++++++++
 src/kudu/util/string_case.cc                 | 29 ++++++++++++++++++++++++++++
 src/kudu/util/string_case.h                  |  3 +++
 22 files changed, 164 insertions(+), 108 deletions(-)

diff --git a/src/kudu/clock/hybrid_clock.cc b/src/kudu/clock/hybrid_clock.cc
index 9721d41..ff4fc45 100644
--- a/src/kudu/clock/hybrid_clock.cc
+++ b/src/kudu/clock/hybrid_clock.cc
@@ -26,7 +26,6 @@
 #include <string>
 #include <vector>
 
-#include <boost/algorithm/string/predicate.hpp>
 #include <gflags/gflags.h>
 #include <glog/logging.h>
 
@@ -47,12 +46,14 @@
 #include "kudu/util/monotime.h"
 #include "kudu/util/net/net_util.h"
 #include "kudu/util/status.h"
+#include "kudu/util/string_case.h"
 
 using google::SetCommandLineOptionWithMode;
 using google::FlagSettingMode;
 using kudu::clock::BuiltInNtp;
 using kudu::cloud::InstanceDetector;
 using kudu::cloud::InstanceMetadata;
+using kudu::iequals;
 using kudu::Status;
 using std::string;
 using std::unique_ptr;
@@ -111,13 +112,13 @@ DEFINE_string(time_source,
               "environment.");
 TAG_FLAG(time_source, stable);
 DEFINE_validator(time_source, [](const char* flag_name, const string& value) {
-  if (boost::iequals(value, TIME_SOURCE_AUTO) ||
-      boost::iequals(value, TIME_SOURCE_NTP_SYNC_BUILTIN) ||
+  if (iequals(value, TIME_SOURCE_AUTO) ||
+      iequals(value, TIME_SOURCE_NTP_SYNC_BUILTIN) ||
 #if defined(KUDU_HAS_SYSTEM_TIME_SOURCE)
-      boost::iequals(value, TIME_SOURCE_NTP_SYNC_SYSTEM) ||
+      iequals(value, TIME_SOURCE_NTP_SYNC_SYSTEM) ||
 #endif
-      boost::iequals(value, TIME_SOURCE_UNSYNC_SYSTEM) ||
-      boost::iequals(value, TIME_SOURCE_MOCK)) {
+      iequals(value, TIME_SOURCE_UNSYNC_SYSTEM) ||
+      iequals(value, TIME_SOURCE_MOCK)) {
     return true;
   }
   LOG(ERROR) << Substitute("unknown value for --$0 flag: '$1' "
@@ -452,7 +453,7 @@ Status HybridClock::SelectTimeSource(const string& time_source_str,
                                      vector<HostPort>* builtin_ntp_servers) {
   TimeSource result_time_source = TimeSource::UNKNOWN;
   vector<HostPort> result_builtin_ntp_servers;
-  if (boost::iequals(time_source_str, TIME_SOURCE_AUTO)) {
+  if (iequals(time_source_str, TIME_SOURCE_AUTO)) {
     InstanceDetector detector;
     unique_ptr<InstanceMetadata> md;
     const auto s = detector.Detect(&md);
@@ -499,15 +500,15 @@ Status HybridClock::SelectTimeSource(const string& time_source_str,
     SetCommandLineOptionWithMode("time_source",
                                  TimeSourceToString(result_time_source),
                                  FlagSettingMode::SET_FLAGS_DEFAULT);
-  } else if (boost::iequals(time_source_str, TIME_SOURCE_MOCK)) {
+  } else if (iequals(time_source_str, TIME_SOURCE_MOCK)) {
     result_time_source = TimeSource::MOCK;
 #if defined(KUDU_HAS_SYSTEM_TIME_SOURCE)
-  } else if (boost::iequals(time_source_str, TIME_SOURCE_NTP_SYNC_SYSTEM)) {
+  } else if (iequals(time_source_str, TIME_SOURCE_NTP_SYNC_SYSTEM)) {
     result_time_source = TimeSource::NTP_SYNC_SYSTEM;
 #endif
-  } else if (boost::iequals(time_source_str, TIME_SOURCE_UNSYNC_SYSTEM)) {
+  } else if (iequals(time_source_str, TIME_SOURCE_UNSYNC_SYSTEM)) {
     result_time_source = TimeSource::UNSYNC_SYSTEM;
-  } else if (boost::iequals(time_source_str, TIME_SOURCE_NTP_SYNC_BUILTIN)) {
+  } else if (iequals(time_source_str, TIME_SOURCE_NTP_SYNC_BUILTIN)) {
     result_time_source = TimeSource::NTP_SYNC_BUILTIN;
   } else {
     return Status::InvalidArgument("invalid time source", time_source_str);
diff --git a/src/kudu/hms/hms_client.cc b/src/kudu/hms/hms_client.cc
index 84eb50f..440dde4 100644
--- a/src/kudu/hms/hms_client.cc
+++ b/src/kudu/hms/hms_client.cc
@@ -25,7 +25,6 @@
 #include <string>
 #include <vector>
 
-#include <boost/algorithm/string/predicate.hpp>
 #include <glog/logging.h>
 #include <thrift/TApplicationException.h>
 #include <thrift/Thrift.h>
@@ -45,12 +44,14 @@
 #include "kudu/thrift/sasl_client_transport.h"
 #include "kudu/util/status.h"
 #include "kudu/util/stopwatch.h"
+#include "kudu/util/string_case.h"
 
 using apache::thrift::TApplicationException;
 using apache::thrift::TException;
 using apache::thrift::protocol::TJSONProtocol;
 using apache::thrift::transport::TMemoryBuffer;
 using apache::thrift::transport::TTransportException;
+using kudu::iequals;
 using kudu::thrift::ClientOptions;
 using kudu::thrift::CreateClientProtocol;
 using kudu::thrift::SaslException;
@@ -196,7 +197,7 @@ Status HmsClient::Start() {
                  Substitute("failed to get Hive Metastore $0 configuration",
                             kDisallowIncompatibleColTypeChanges));
 
-  if (boost::iequals(disallow_incompatible_column_type_changes, "true")) {
+  if (iequals(disallow_incompatible_column_type_changes, "true")) {
     return Status::IllegalState(Substitute(
         "Hive Metastore configuration is invalid: $0 must be set to false",
         kDisallowIncompatibleColTypeChanges));
@@ -212,7 +213,7 @@ Status HmsClient::Start() {
                                           "true"),
                  Substitute("failed to get Hive Metastore $0 configuration",
                             kNotificationAddThriftObjects));
-  if (boost::iequals(thrift_objects_config, "false")) {
+  if (iequals(thrift_objects_config, "false")) {
     return Status::IllegalState(Substitute(
         "Hive Metastore configuration is invalid: $0 must be set to true",
         kNotificationAddThriftObjects));
@@ -413,7 +414,7 @@ bool HmsClient::IsSynchronized(const hive::Table& table) {
       FindWithDefault(table.parameters, hms::HmsClient::kExternalPurgeKey, "false");
   return table.tableType == hms::HmsClient::kManagedTable ||
          (table.tableType == hms::HmsClient::kExternalTable &&
-          boost::iequals(externalPurge, "true"));
+          iequals(externalPurge, "true"));
 }
 
 } // namespace hms
diff --git a/src/kudu/integration-tests/hms_itest-base.cc b/src/kudu/integration-tests/hms_itest-base.cc
index 5cc9e49..cc875c5 100644
--- a/src/kudu/integration-tests/hms_itest-base.cc
+++ b/src/kudu/integration-tests/hms_itest-base.cc
@@ -23,7 +23,6 @@
 #include <utility>
 #include <vector>
 
-#include <boost/algorithm/string/predicate.hpp>
 #include <gtest/gtest.h>
 
 #include "kudu/client/client.h"
@@ -38,6 +37,7 @@
 #include "kudu/util/monotime.h"
 #include "kudu/util/net/net_util.h"
 #include "kudu/util/status.h"
+#include "kudu/util/string_case.h"
 #include "kudu/util/test_macros.h"
 #include "kudu/util/user.h"
 
@@ -222,7 +222,7 @@ void HmsITestHarness::CheckTable(const string& database_name,
   }
   ASSERT_EQ(table->id(), hms_table.parameters[hms::HmsClient::kKuduTableIdKey]);
   ASSERT_EQ(table->client()->cluster_id(), hms_table.parameters[hms::HmsClient::kKuduClusterIdKey]);
-  ASSERT_TRUE(boost::iequals(table->name(),
+  ASSERT_TRUE(iequals(table->name(),
       hms_table.parameters[hms::HmsClient::kKuduTableNameKey]));
   ASSERT_EQ(HostPort::ToCommaSeparatedString(cluster->master_rpc_addrs()),
             hms_table.parameters[hms::HmsClient::kKuduMasterAddrsKey]);
diff --git a/src/kudu/rpc/sasl_common.cc b/src/kudu/rpc/sasl_common.cc
index 0107fba..cb5ef63 100644
--- a/src/kudu/rpc/sasl_common.cc
+++ b/src/kudu/rpc/sasl_common.cc
@@ -24,7 +24,6 @@
 #include <ostream>
 #include <string>
 
-#include <boost/algorithm/string/predicate.hpp>
 #include <glog/logging.h>
 #include <regex.h>
 #include <sasl/sasl.h>
@@ -36,6 +35,7 @@
 #include "kudu/util/mutex.h"
 #include "kudu/util/net/sockaddr.h"
 #include "kudu/util/rw_mutex.h"
+#include "kudu/util/string_case.h"
 
 #if defined(__APPLE__)
 // Almost all functions in the SASL API are marked as deprecated
@@ -446,10 +446,10 @@ Status EnableProtection(sasl_conn_t* sasl_conn,
 }
 
 SaslMechanism::Type SaslMechanism::value_of(const string& mech) {
-  if (boost::iequals(mech, "PLAIN")) {
+  if (iequals(mech, "PLAIN")) {
     return PLAIN;
   }
-  if (boost::iequals(mech, "GSSAPI")) {
+  if (iequals(mech, "GSSAPI")) {
     return GSSAPI;
   }
   return INVALID;
diff --git a/src/kudu/security/tls_context.cc b/src/kudu/security/tls_context.cc
index 6f4d19c..dcc1ee4 100644
--- a/src/kudu/security/tls_context.cc
+++ b/src/kudu/security/tls_context.cc
@@ -34,7 +34,6 @@
 #include <type_traits>
 #include <vector>
 
-#include <boost/algorithm/string/predicate.hpp>
 #include <gflags/gflags.h>
 #include <glog/logging.h>
 
@@ -52,6 +51,7 @@
 #include "kudu/util/net/net_util.h"
 #include "kudu/util/scoped_cleanup.h"
 #include "kudu/util/status.h"
+#include "kudu/util/string_case.h"
 #include "kudu/util/user.h"
 
 // Hard code OpenSSL flag values from OpenSSL 1.0.1e[1][2] when compiling
@@ -172,13 +172,13 @@ Status TlsContext::Init() {
   //   https://tools.ietf.org/html/rfc7525#section-3.3
   auto options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION;
 
-  if (boost::iequals(tls_min_protocol_, "TLSv1.2")) {
+  if (iequals(tls_min_protocol_, "TLSv1.2")) {
     RETURN_NOT_OK(CheckMaxSupportedTlsVersion(TLS1_2_VERSION, "TLSv1.2"));
     options |= SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1;
-  } else if (boost::iequals(tls_min_protocol_, "TLSv1.1")) {
+  } else if (iequals(tls_min_protocol_, "TLSv1.1")) {
     RETURN_NOT_OK(CheckMaxSupportedTlsVersion(TLS1_1_VERSION, "TLSv1.1"));
     options |= SSL_OP_NO_TLSv1;
-  } else if (!boost::iequals(tls_min_protocol_, "TLSv1")) {
+  } else if (!iequals(tls_min_protocol_, "TLSv1")) {
     return Status::InvalidArgument("unknown value provided for --rpc_tls_min_protocol flag",
                                    tls_min_protocol_);
   }
diff --git a/src/kudu/server/default_path_handlers.cc b/src/kudu/server/default_path_handlers.cc
index 694ec63..460c957 100644
--- a/src/kudu/server/default_path_handlers.cc
+++ b/src/kudu/server/default_path_handlers.cc
@@ -28,7 +28,6 @@
 #include <unordered_map>
 #include <vector>
 
-#include <boost/algorithm/string/predicate.hpp>
 #include <gflags/gflags.h>
 #include <glog/logging.h>
 
@@ -59,6 +58,7 @@
 #include "kudu/util/monotime.h"
 #include "kudu/util/process_memory.h"
 #include "kudu/util/status.h"
+#include "kudu/util/string_case.h"
 #include "kudu/util/web_callback_registry.h"
 
 #ifdef TCMALLOC_ENABLED
@@ -67,6 +67,7 @@
 
 using google::CommandLineFlagInfo;
 using google::GetCommandLineFlagInfo;
+using kudu::iequals;
 using std::ifstream;
 using std::ostringstream;
 using std::shared_ptr;
@@ -89,9 +90,9 @@ TAG_FLAG(metrics_default_level, advanced);
 TAG_FLAG(metrics_default_level, runtime);
 TAG_FLAG(metrics_default_level, evolving);
 DEFINE_validator(metrics_default_level, [](const char* flag_name, const string& value) {
-  if (boost::iequals(value, "debug") ||
-      boost::iequals(value, "info") ||
-      boost::iequals(value, "warn")) {
+  if (iequals(value, "debug") ||
+      iequals(value, "info") ||
+      iequals(value, "warn")) {
     return true;
   }
   LOG(ERROR) << Substitute("unknown value for --$0 flag: '$1' "
@@ -302,7 +303,7 @@ static void FillSecurityConfigs(EasyJson* output) {
   EasyJson rpc_encryption = configs.PushBack(EasyJson::kObject);
   rpc_encryption[kName] = "RPC Encryption";
   rpc_encryption[kValue] = FLAGS_rpc_encryption;
-  rpc_encryption[kSecure] = boost::iequals(FLAGS_rpc_encryption, "required");
+  rpc_encryption[kSecure] = iequals(FLAGS_rpc_encryption, "required");
   rpc_encryption[kId] = "rpc_encryption";
   rpc_encryption[kComment] =
       "Configure with --rpc_encryption. Most secure value is 'required'.";
@@ -310,7 +311,7 @@ static void FillSecurityConfigs(EasyJson* output) {
   EasyJson rpc_authentication = configs.PushBack(EasyJson::kObject);
   rpc_authentication[kName] = "RPC Authentication";
   rpc_authentication[kValue] = FLAGS_rpc_authentication;
-  rpc_authentication[kSecure] = boost::iequals(FLAGS_rpc_authentication, "required");
+  rpc_authentication[kSecure] = iequals(FLAGS_rpc_authentication, "required");
   rpc_authentication[kId] = "rpc_authentication";
   rpc_authentication[kComment] =
       "Configure with --rpc_authentication. Most secure value is 'required'.";
@@ -326,7 +327,7 @@ static void FillSecurityConfigs(EasyJson* output) {
   EasyJson webserver_redaction = configs.PushBack(EasyJson::kObject);
   webserver_redaction[kName] = "Webserver Redaction";
   webserver_redaction[kValue] = FLAGS_redact;
-  webserver_redaction[kSecure] = boost::iequals(FLAGS_redact, "all");
+  webserver_redaction[kSecure] = iequals(FLAGS_redact, "all");
   webserver_redaction[kId] = "webserver_redaction";
   webserver_redaction[kComment] =
       "Configure with --redact. Most secure value is 'all'.";
diff --git a/src/kudu/server/server_base.cc b/src/kudu/server/server_base.cc
index 9dd988f..645d46f 100644
--- a/src/kudu/server/server_base.cc
+++ b/src/kudu/server/server_base.cc
@@ -25,7 +25,6 @@
 #include <string>
 #include <vector>
 
-#include <boost/algorithm/string/predicate.hpp>
 #include <boost/optional/optional.hpp>
 #include <gflags/gflags.h>
 #include <glog/logging.h>
@@ -87,6 +86,7 @@
 #endif
 #include "kudu/util/slice.h"
 #include "kudu/util/spinlock_profiling.h"
+#include "kudu/util/string_case.h"
 #include "kudu/util/thread.h"
 #include "kudu/util/user.h"
 #include "kudu/util/version_info.h"
@@ -669,10 +669,10 @@ Status ServerBase::DumpServerInfo(const string& path,
   ServerStatusPB status;
   RETURN_NOT_OK_PREPEND(GetStatusPB(&status), "could not get server status");
 
-  if (boost::iequals(format, "json")) {
+  if (iequals(format, "json")) {
     string json = JsonWriter::ToJson(status, JsonWriter::PRETTY);
     RETURN_NOT_OK(WriteStringToFile(options_.env, Slice(json), path));
-  } else if (boost::iequals(format, "pb")) {
+  } else if (iequals(format, "pb")) {
     // TODO: Use PB container format?
     RETURN_NOT_OK(pb_util::WritePBToPath(options_.env, path, status,
                                          pb_util::NO_SYNC)); // durability doesn't matter
diff --git a/src/kudu/tools/ksck.cc b/src/kudu/tools/ksck.cc
index 57c38c6..6cd5b9f 100644
--- a/src/kudu/tools/ksck.cc
+++ b/src/kudu/tools/ksck.cc
@@ -20,19 +20,19 @@
 #include <algorithm>
 #include <atomic>
 #include <cstddef>
-#include <cstdint>
 #include <functional>
 #include <iostream>
 #include <iterator>
 #include <map>
 #include <mutex>
 #include <set>
+#include <type_traits>
 #include <vector>
 
-#include <boost/algorithm/string/predicate.hpp>
 #include <boost/optional.hpp> // IWYU pragma: keep
 #include <gflags/gflags.h>
 #include <glog/logging.h>
+#include <google/protobuf/stubs/port.h>
 
 #include "kudu/consensus/quorum_util.h"
 #include "kudu/gutil/basictypes.h"
@@ -159,8 +159,8 @@ bool IsNotAuthorizedMethodAccess(const Status& s) {
 
 // Return whether the format of the ksck results is non-JSON.
 bool IsNonJSONFormat() {
-  return boost::iequals(FLAGS_ksck_format, "plain_full") ||
-         boost::iequals(FLAGS_ksck_format, "plain_concise");
+  return iequals(FLAGS_ksck_format, "plain_full") ||
+         iequals(FLAGS_ksck_format, "plain_concise");
 }
 
 } // anonymous namespace
@@ -224,13 +224,13 @@ const char* FlagsCategoryToString(FlagsCategory category) {
 }
 
 Status StringToFlagsCategory(const string& str, FlagsCategory* category) {
-  if (boost::iequals(str, STR_FLAGS_CATEGORY_TIME_SOURCE)) {
+  if (iequals(str, STR_FLAGS_CATEGORY_TIME_SOURCE)) {
     if (category) {
       *category = FlagsCategory::TIME_SOURCE;
     }
     return Status::OK();
   }
-  if (boost::iequals(str, STR_FLAGS_CATEGORY_UNUSUAL)) {
+  if (iequals(str, STR_FLAGS_CATEGORY_UNUSUAL)) {
     if (category) {
       *category = FlagsCategory::UNUSUAL;
     }
diff --git a/src/kudu/tools/ksck.h b/src/kudu/tools/ksck.h
index aa95d0a..d9f1223 100644
--- a/src/kudu/tools/ksck.h
+++ b/src/kudu/tools/ksck.h
@@ -38,7 +38,7 @@
 #include "kudu/gutil/macros.h"
 #include "kudu/gutil/strings/substitute.h"
 #include "kudu/rebalance/cluster_status.h" // IWYU pragma: keep
-#include "kudu/server/server_base.pb.h"
+#include "kudu/server/server_base.pb.h" // IWYU pragma: keep
 #include "kudu/tablet/metadata.pb.h"
 #include "kudu/tablet/tablet.pb.h"  // IWYU pragma: keep
 #include "kudu/tools/ksck_results.h"
diff --git a/src/kudu/tools/kudu-tool-test.cc b/src/kudu/tools/kudu-tool-test.cc
index 7c9aa04..6a9bfb1 100644
--- a/src/kudu/tools/kudu-tool-test.cc
+++ b/src/kudu/tools/kudu-tool-test.cc
@@ -35,7 +35,6 @@
 #include <utility>
 #include <vector>
 
-#include <boost/algorithm/string/predicate.hpp>
 #include <boost/optional/optional.hpp>
 #include <gflags/gflags_declare.h>
 #include <glog/logging.h>
@@ -132,6 +131,7 @@
 #include "kudu/util/scoped_cleanup.h"
 #include "kudu/util/slice.h"
 #include "kudu/util/status.h"
+#include "kudu/util/string_case.h"
 #include "kudu/util/subprocess.h"
 #include "kudu/util/test_macros.h"
 #include "kudu/util/test_util.h"
@@ -180,6 +180,7 @@ using kudu::fs::FsReport;
 using kudu::fs::WritableBlock;
 using kudu::hms::HmsCatalog;
 using kudu::hms::HmsClient;
+using kudu::iequals;
 using kudu::itest::MiniClusterFsInspector;
 using kudu::itest::TServerDetails;
 using kudu::log::Log;
@@ -4002,7 +4003,7 @@ void ValidateHmsEntries(HmsClient* hms_client,
   if (HmsClient::IsSynchronized(hms_table)) {
     shared_ptr<KuduTable> kudu_table;
     ASSERT_OK(kudu_client->OpenTable(Substitute("$0.$1", database_name, table_name), &kudu_table));
-    ASSERT_TRUE(boost::iequals(kudu_table->name(),
+    ASSERT_TRUE(iequals(kudu_table->name(),
                                hms_table.parameters[hms::HmsClient::kKuduTableNameKey]));
     ASSERT_EQ(kudu_table->id(), hms_table.parameters[HmsClient::kKuduTableIdKey]);
     ASSERT_EQ(kudu_table->client()->cluster_id(),
diff --git a/src/kudu/tools/table_scanner.cc b/src/kudu/tools/table_scanner.cc
index 4242415..3fa89cd 100644
--- a/src/kudu/tools/table_scanner.cc
+++ b/src/kudu/tools/table_scanner.cc
@@ -27,7 +27,6 @@
 #include <memory>
 #include <set>
 
-#include <boost/algorithm/string/predicate.hpp>
 #include <boost/optional/optional.hpp>
 #include <gflags/gflags.h>
 #include <glog/logging.h>
@@ -72,6 +71,7 @@ using kudu::client::KuduTable;
 using kudu::client::KuduTableCreator;
 using kudu::client::KuduValue;
 using kudu::client::KuduWriteOperation;
+using kudu::iequals;
 using std::endl;
 using std::map;
 using std::ostream;
@@ -120,7 +120,7 @@ static bool ValidateWriteType(const char* flag_name,
   const vector<string> allowed_values = { "insert", "upsert", "" };
   if (std::find_if(allowed_values.begin(), allowed_values.end(),
                    [&](const string& allowed_value) {
-                     return boost::iequals(allowed_value, flag_value);
+                     return iequals(allowed_value, flag_value);
                    }) != allowed_values.end()) {
     return true;
   }
diff --git a/src/kudu/tools/tool_action_cluster.cc b/src/kudu/tools/tool_action_cluster.cc
index cf7c4aa..0e4d629 100644
--- a/src/kudu/tools/tool_action_cluster.cc
+++ b/src/kudu/tools/tool_action_cluster.cc
@@ -26,7 +26,6 @@
 #include <tuple>
 #include <vector>
 
-#include <boost/algorithm/string/predicate.hpp>
 #include <boost/optional/optional.hpp>
 #include <gflags/gflags.h>
 #include <glog/logging.h>
@@ -44,9 +43,11 @@
 #include "kudu/tools/tool_action_common.h"
 #include "kudu/tools/tool_replica_util.h"
 #include "kudu/util/status.h"
+#include "kudu/util/string_case.h"
 #include "kudu/util/version_util.h"
 
 using kudu::rebalance::Rebalancer;
+using kudu::iequals;
 using std::cout;
 using std::endl;
 using std::make_shared;
@@ -157,7 +158,7 @@ static bool ValidateMoveSingleReplicas(const char* flag_name,
   const vector<string> allowed_values = { "auto", "enabled", "disabled" };
   if (std::find_if(allowed_values.begin(), allowed_values.end(),
                    [&](const string& allowed_value) {
-                     return boost::iequals(allowed_value, flag_value);
+                     return iequals(allowed_value, flag_value);
                    }) != allowed_values.end()) {
     return true;
   }
@@ -218,11 +219,11 @@ bool VersionSupportsRF1Movement(const string& version_str) {
 Status EvaluateMoveSingleReplicasFlag(const vector<string>& master_addresses,
                                       bool* move_single_replicas) {
   DCHECK(move_single_replicas);
-  if (!boost::iequals(FLAGS_move_single_replicas, "auto")) {
-    if (boost::iequals(FLAGS_move_single_replicas, "enabled")) {
+  if (!iequals(FLAGS_move_single_replicas, "auto")) {
+    if (iequals(FLAGS_move_single_replicas, "enabled")) {
       *move_single_replicas = true;
     } else {
-      DCHECK(boost::iequals(FLAGS_move_single_replicas, "disabled"));
+      DCHECK(iequals(FLAGS_move_single_replicas, "disabled"));
       *move_single_replicas = false;
     }
     return Status::OK();
diff --git a/src/kudu/tools/tool_action_common.cc b/src/kudu/tools/tool_action_common.cc
index b45fba5..2b45263 100644
--- a/src/kudu/tools/tool_action_common.cc
+++ b/src/kudu/tools/tool_action_common.cc
@@ -28,12 +28,11 @@
 #include <numeric>
 #include <stack>
 #include <string>
-#include <type_traits>
 #include <unordered_map>
+#include <unordered_set>
 #include <utility>
 #include <vector>
 
-#include <boost/algorithm/string/predicate.hpp>
 #include <gflags/gflags.h>
 #include <glog/logging.h>
 // IWYU pragma: no_include <yaml-cpp/node/impl.h>
@@ -87,6 +86,7 @@
 #include "kudu/util/path_util.h"
 #include "kudu/util/pb_util.h"
 #include "kudu/util/status.h"
+#include "kudu/util/string_case.h"
 #include "kudu/util/yamlreader.h"
 
 DEFINE_bool(force, false, "If true, allows the set_flag command to set a flag "
@@ -712,13 +712,13 @@ Status DumpMemTrackers(const string& address, uint16_t default_port) {
   rpc.set_timeout(MonoDelta::FromMilliseconds(FLAGS_timeout_ms));
   RETURN_NOT_OK(proxy->DumpMemTrackers(req, &resp, &rpc));
 
-  if (boost::iequals(FLAGS_memtracker_output, "json")) {
+  if (iequals(FLAGS_memtracker_output, "json")) {
     cout << JsonWriter::ToJson(resp.root_tracker(), JsonWriter::Mode::PRETTY)
          << endl;
-  } else if (boost::iequals(FLAGS_memtracker_output, "json_compact")) {
+  } else if (iequals(FLAGS_memtracker_output, "json_compact")) {
     cout << JsonWriter::ToJson(resp.root_tracker(), JsonWriter::Mode::COMPACT)
          << endl;
-  } else if (boost::iequals(FLAGS_memtracker_output, "table")) {
+  } else if (iequals(FLAGS_memtracker_output, "table")) {
     DataTable table({ "id", "parent_id", "limit",
                       "current consumption", "peak_consumption" });
     const auto& root = resp.root_tracker();
@@ -875,15 +875,15 @@ void DataTable::AddColumn(string name, vector<string> column) {
 }
 
 Status DataTable::PrintTo(ostream& out) const {
-  if (boost::iequals(FLAGS_format, "pretty")) {
+  if (iequals(FLAGS_format, "pretty")) {
     PrettyPrintTable(column_names_, columns_, out);
-  } else if (boost::iequals(FLAGS_format, "space")) {
+  } else if (iequals(FLAGS_format, "space")) {
     PrintTable(columns_, " ", out);
-  } else if (boost::iequals(FLAGS_format, "tsv")) {
+  } else if (iequals(FLAGS_format, "tsv")) {
     PrintTable(columns_, "	", out);
-  } else if (boost::iequals(FLAGS_format, "csv")) {
+  } else if (iequals(FLAGS_format, "csv")) {
     PrintTable(columns_, ",", out);
-  } else if (boost::iequals(FLAGS_format, "json")) {
+  } else if (iequals(FLAGS_format, "json")) {
     JsonPrintTable(column_names_, columns_, out);
   } else {
     return Status::InvalidArgument("unknown format (--format)", FLAGS_format);
diff --git a/src/kudu/tools/tool_action_hms.cc b/src/kudu/tools/tool_action_hms.cc
index 77bf078..1286f4f 100644
--- a/src/kudu/tools/tool_action_hms.cc
+++ b/src/kudu/tools/tool_action_hms.cc
@@ -29,7 +29,6 @@
 #include <utility>
 #include <vector>
 
-#include <boost/algorithm/string/predicate.hpp>
 #include <boost/optional/optional.hpp>
 #include <gflags/gflags.h>
 #include <glog/logging.h>
@@ -50,6 +49,7 @@
 #include "kudu/util/net/net_util.h"
 #include "kudu/util/slice.h"
 #include "kudu/util/status.h"
+#include "kudu/util/string_case.h"
 
 DECLARE_bool(force);
 DECLARE_bool(hive_metastore_sasl_enabled);
@@ -271,39 +271,39 @@ Status PrintHMSTables(vector<hive::Table> tables, ostream& out) {
   DataTable table({});
   for (const auto& column : strings::Split(FLAGS_columns, ",", strings::SkipEmpty())) {
     vector<string> values;
-    if (boost::iequals(column, "database")) {
+    if (iequals(column.ToString(), "database")) {
       for (auto& hms_table : tables) {
         values.emplace_back(hms_table.dbName);
       }
-    } else if (boost::iequals(column, "table")) {
+    } else if (iequals(column.ToString(), "table")) {
       for (auto& hms_table : tables) {
         values.emplace_back(hms_table.tableName);
       }
-    } else if (boost::iequals(column, "type")) {
+    } else if (iequals(column.ToString(), "type")) {
       for (auto& hms_table : tables) {
         values.emplace_back(hms_table.tableType);
       }
-    } else if (boost::iequals(column, "owner")) {
+    } else if (iequals(column.ToString(), "owner")) {
       for (auto& hms_table : tables) {
         values.emplace_back(hms_table.owner);
       }
-    } else if (boost::iequals(column, HmsClient::kKuduTableNameKey)) {
+    } else if (iequals(column.ToString(), HmsClient::kKuduTableNameKey)) {
       for (auto& hms_table : tables) {
         values.emplace_back(hms_table.parameters[HmsClient::kKuduTableNameKey]);
       }
-    } else if (boost::iequals(column, HmsClient::kKuduTableIdKey)) {
+    } else if (iequals(column.ToString(), HmsClient::kKuduTableIdKey)) {
       for (auto& hms_table : tables) {
         values.emplace_back(hms_table.parameters[HmsClient::kKuduTableIdKey]);
       }
-    } else if (boost::iequals(column, HmsClient::kKuduClusterIdKey)) {
+    } else if (iequals(column.ToString(), HmsClient::kKuduClusterIdKey)) {
       for (auto& hms_table : tables) {
         values.emplace_back(hms_table.parameters[HmsClient::kKuduClusterIdKey]);
       }
-    } else if (boost::iequals(column, HmsClient::kKuduMasterAddrsKey)) {
+    } else if (iequals(column.ToString(), HmsClient::kKuduMasterAddrsKey)) {
       for (auto& hms_table : tables) {
         values.emplace_back(hms_table.parameters[HmsClient::kKuduMasterAddrsKey]);
       }
-    } else if (boost::iequals(column, HmsClient::kStorageHandlerKey)) {
+    } else if (iequals(column.ToString(), HmsClient::kStorageHandlerKey)) {
       for (auto& hms_table : tables) {
         values.emplace_back(hms_table.parameters[HmsClient::kStorageHandlerKey]);
       }
diff --git a/src/kudu/tools/tool_action_master.cc b/src/kudu/tools/tool_action_master.cc
index dff2ac1..b0fdeb9 100644
--- a/src/kudu/tools/tool_action_master.cc
+++ b/src/kudu/tools/tool_action_master.cc
@@ -27,7 +27,6 @@
 #include <utility>
 #include <vector>
 
-#include <boost/algorithm/string/predicate.hpp>
 #include <boost/optional/optional.hpp>
 #include <gflags/gflags.h>
 #include <glog/logging.h>
@@ -52,6 +51,7 @@
 #include "kudu/util/init.h"
 #include "kudu/util/monotime.h"
 #include "kudu/util/status.h"
+#include "kudu/util/string_case.h"
 
 DECLARE_bool(force);
 DECLARE_int64(timeout_ms);
@@ -149,43 +149,43 @@ Status ListMasters(const RunnerContext& context) {
 
   for (const auto& column : strings::Split(FLAGS_columns, ",", strings::SkipEmpty())) {
     vector<string> values;
-    if (boost::iequals(column, "uuid")) {
+    if (iequals(column.ToString(), "uuid")) {
       for (const auto& master : masters) {
         values.push_back(master.instance_id().permanent_uuid());
       }
-    } else if (boost::iequals(column, "cluster_id")) {
+    } else if (iequals(column.ToString(), "cluster_id")) {
       for (const auto& master : masters) {
         values.emplace_back(master.has_cluster_id() ? master.cluster_id() : "");
       }
-    } else if (boost::iequals(column, "seqno")) {
+    } else if (iequals(column.ToString(), "seqno")) {
       for (const auto& master : masters) {
         values.push_back(std::to_string(master.instance_id().instance_seqno()));
       }
-    } else if (boost::iequals(column, "rpc-addresses") ||
-               boost::iequals(column, "rpc_addresses")) {
+    } else if (iequals(column.ToString(), "rpc-addresses") ||
+               iequals(column.ToString(), "rpc_addresses")) {
       for (const auto& master : masters) {
         values.push_back(JoinMapped(master.registration().rpc_addresses(),
                          hostport_to_string, ","));
       }
-    } else if (boost::iequals(column, "http-addresses") ||
-               boost::iequals(column, "http_addresses")) {
+    } else if (iequals(column.ToString(), "http-addresses") ||
+               iequals(column.ToString(), "http_addresses")) {
       for (const auto& master : masters) {
         values.push_back(JoinMapped(master.registration().http_addresses(),
                                     hostport_to_string, ","));
       }
-    } else if (boost::iequals(column, "version")) {
+    } else if (iequals(column.ToString(), "version")) {
       for (const auto& master : masters) {
         values.push_back(master.registration().software_version());
       }
-    } else if (boost::iequals(column, "start_time")) {
+    } else if (iequals(column.ToString(), "start_time")) {
       for (const auto& master : masters) {
         values.emplace_back(StartTimeToString(master.registration()));
       }
-    } else if (boost::iequals(column, "role")) {
+    } else if (iequals(column.ToString(), "role")) {
       for (const auto& master : masters) {
         values.emplace_back(RaftPeerPB::Role_Name(master.role()));
       }
-    } else if (boost::iequals(column, "member_type")) {
+    } else if (iequals(column.ToString(), "member_type")) {
       for (const auto& master : masters) {
         values.emplace_back(RaftPeerPB::MemberType_Name(master.member_type()));
       }
diff --git a/src/kudu/tools/tool_action_table.cc b/src/kudu/tools/tool_action_table.cc
index 59b7a94..333dcd6 100644
--- a/src/kudu/tools/tool_action_table.cc
+++ b/src/kudu/tools/tool_action_table.cc
@@ -23,12 +23,10 @@
 #include <memory>
 #include <set>
 #include <string>
-#include <type_traits>
 #include <unordered_map>
 #include <utility>
 #include <vector>
 
-#include <boost/algorithm/string/predicate.hpp>
 #include <boost/optional/optional.hpp>
 #include <gflags/gflags.h>
 #include <glog/logging.h>
@@ -60,6 +58,7 @@
 #include "kudu/tools/tool_action_common.h"
 #include "kudu/util/jsonreader.h"
 #include "kudu/util/status.h"
+#include "kudu/util/string_case.h"
 
 using google::protobuf::util::JsonStringToMessage;
 using google::protobuf::util::JsonParseOptions;
@@ -634,9 +633,9 @@ Status ModifyRangePartition(const RunnerContext& context, PartitionAction action
   const auto convert_bounds_type = [&] (const string& range_bound,
                                         const string& flags_range_bound_type,
                                         KuduTableCreator::RangePartitionBound* range_bound_type) {
-    string inclusive_bound = boost::iequals(flags_range_bound_type, "INCLUSIVE_BOUND") ?
+    string inclusive_bound = iequals(flags_range_bound_type, "INCLUSIVE_BOUND") ?
         "INCLUSIVE_BOUND" : "";
-    string exclusive_bound = boost::iequals(flags_range_bound_type, "EXCLUSIVE_BOUND") ?
+    string exclusive_bound = iequals(flags_range_bound_type, "EXCLUSIVE_BOUND") ?
         "EXCLUSIVE_BOUND" : "";
 
     if (inclusive_bound.empty() && exclusive_bound.empty()) {
diff --git a/src/kudu/tools/tool_action_test.cc b/src/kudu/tools/tool_action_test.cc
index 2c3202a..08d8680 100644
--- a/src/kudu/tools/tool_action_test.cc
+++ b/src/kudu/tools/tool_action_test.cc
@@ -26,7 +26,6 @@
 #include <string>
 #include <utility>
 
-#include <boost/algorithm/string/predicate.hpp>
 #include <gflags/gflags.h>
 #include <glog/logging.h>
 #include <google/protobuf/stubs/common.h>
@@ -47,6 +46,7 @@
 #include "kudu/util/path_util.h"
 #include "kudu/util/pb_util.h"
 #include "kudu/util/status.h"
+#include "kudu/util/string_case.h"
 
 DEFINE_string(serialization, "json", "Serialization method to be used by the "
               "control shell. Valid values are 'json' (protobuf serialized "
@@ -54,8 +54,8 @@ DEFINE_string(serialization, "json", "Serialization method to be used by the "
               "(four byte protobuf message length in big endian followed by "
               "the protobuf message itself).");
 DEFINE_validator(serialization, [](const char* /*n*/, const std::string& v) {
-  return boost::iequals(v, "pb") ||
-         boost::iequals(v, "json");
+  return kudu::iequals(v, "pb") ||
+         kudu::iequals(v, "json");
 });
 
 using kudu::cluster::ExternalDaemon;
@@ -324,10 +324,10 @@ Status RunControlShell(const RunnerContext& /*context*/) {
   RETRY_ON_EINTR(ret, dup2(STDERR_FILENO, STDOUT_FILENO));
   PCHECK(ret == STDOUT_FILENO);
   SubprocessProtocol::SerializationMode serde_mode;
-  if (boost::iequals(FLAGS_serialization, "json")) {
+  if (iequals(FLAGS_serialization, "json")) {
     serde_mode = SubprocessProtocol::SerializationMode::JSON;
   } else {
-    DCHECK(boost::iequals(FLAGS_serialization, "pb"));
+    DCHECK(iequals(FLAGS_serialization, "pb"));
     serde_mode = SubprocessProtocol::SerializationMode::PB;
   }
   SubprocessProtocol protocol(serde_mode,
diff --git a/src/kudu/tools/tool_action_tserver.cc b/src/kudu/tools/tool_action_tserver.cc
index 840b1d3..1b3ab8e 100644
--- a/src/kudu/tools/tool_action_tserver.cc
+++ b/src/kudu/tools/tool_action_tserver.cc
@@ -23,7 +23,6 @@
 #include <utility>
 #include <vector>
 
-#include <boost/algorithm/string/predicate.hpp>
 #include <gflags/gflags.h>
 #include <glog/logging.h>
 
@@ -48,6 +47,7 @@
 #include "kudu/tserver/tserver_admin.proxy.h"
 #include "kudu/util/init.h"
 #include "kudu/util/status.h"
+#include "kudu/util/string_case.h"
 
 DEFINE_bool(allow_missing_tserver, false, "If true, performs the action on the "
     "tserver even if it has not been registered with the master and has no "
@@ -129,7 +129,7 @@ Status ListTServers(const RunnerContext& context) {
   const vector<string> cols = strings::Split(FLAGS_columns, ",", strings::SkipEmpty());
   ListTabletServersRequestPB req;
   for (const auto& col : cols) {
-    if (boost::iequals(col, "state")) {
+    if (iequals(col, "state")) {
       req.set_include_states(true);
     }
   }
@@ -151,44 +151,44 @@ Status ListTServers(const RunnerContext& context) {
 
   for (const auto& column : cols) {
     vector<string> values;
-    if (boost::iequals(column, "uuid")) {
+    if (iequals(column, "uuid")) {
       for (const auto& server : servers) {
         values.emplace_back(server.instance_id().permanent_uuid());
       }
-    } else if (boost::iequals(column, "seqno")) {
+    } else if (iequals(column, "seqno")) {
       for (const auto& server : servers) {
         values.emplace_back(std::to_string(server.instance_id().instance_seqno()));
       }
-    } else if (boost::iequals(column, "rpc-addresses") ||
-               boost::iequals(column, "rpc_addresses")) {
+    } else if (iequals(column, "rpc-addresses") ||
+               iequals(column, "rpc_addresses")) {
       for (const auto& server : servers) {
         values.emplace_back(JoinMapped(server.registration().rpc_addresses(),
                                        hostport_to_string, ","));
       }
-    } else if (boost::iequals(column, "http-addresses") ||
-               boost::iequals(column, "http_addresses")) {
+    } else if (iequals(column, "http-addresses") ||
+               iequals(column, "http_addresses")) {
       for (const auto& server : servers) {
         values.emplace_back(JoinMapped(server.registration().http_addresses(),
                                        hostport_to_string, ","));
       }
-    } else if (boost::iequals(column, "version")) {
+    } else if (iequals(column, "version")) {
       for (const auto& server : servers) {
         values.emplace_back(server.registration().software_version());
       }
-    } else if (boost::iequals(column, "heartbeat")) {
+    } else if (iequals(column, "heartbeat")) {
       for (const auto& server : servers) {
         values.emplace_back(Substitute("$0ms", server.millis_since_heartbeat()));
       }
-    } else if (boost::iequals(column, "location")) {
+    } else if (iequals(column, "location")) {
       for (const auto& server : servers) {
         string loc = server.location();
         values.emplace_back(loc.empty() ? "<none>" : std::move(loc));
       }
-    } else if (boost::iequals(column, "start_time")) {
+    } else if (iequals(column, "start_time")) {
       for (const auto& server : servers) {
         values.emplace_back(StartTimeToString(server.registration()));
       }
-    } else if (boost::iequals(column, "state")) {
+    } else if (iequals(column, "state")) {
       for (const auto& server : servers) {
         values.emplace_back(TServerStatePB_Name(server.state()));
       }
diff --git a/src/kudu/util/flags.cc b/src/kudu/util/flags.cc
index 13643fa..01a0100 100644
--- a/src/kudu/util/flags.cc
+++ b/src/kudu/util/flags.cc
@@ -31,7 +31,6 @@
 #include <utility>
 #include <vector>
 
-#include <boost/algorithm/string/predicate.hpp>
 #include <gflags/gflags.h>
 #include <glog/logging.h>
 #ifdef TCMALLOC_ENABLED
@@ -595,11 +594,11 @@ GFlagsMap GetFlagsMap() {
 
 Status ParseTriState(const char* flag_name, const std::string& flag_value,
     TriStateFlag* tri_state) {
-  if (boost::iequals(flag_value, "required")) {
+  if (iequals(flag_value, "required")) {
     *tri_state = TriStateFlag::REQUIRED;
-  } else if (boost::iequals(flag_value, "optional")) {
+  } else if (iequals(flag_value, "optional")) {
     *tri_state = TriStateFlag::OPTIONAL;
-  } else if (boost::iequals(flag_value, "disabled")) {
+  } else if (iequals(flag_value, "disabled")) {
     *tri_state = TriStateFlag::DISABLED;
   } else {
     return Status::InvalidArgument(strings::Substitute(
diff --git a/src/kudu/util/string_case-test.cc b/src/kudu/util/string_case-test.cc
index 96831a1..0765b8e 100644
--- a/src/kudu/util/string_case-test.cc
+++ b/src/kudu/util/string_case-test.cc
@@ -62,4 +62,25 @@ TEST(TestStringCase, TestCapitalize) {
   ASSERT_EQ("Hibernate", word);
 }
 
+TEST(TestStringCase, TestIequals) {
+  const string foo = "foo";
+  const string capital = "Foo";
+  const string caps = "FOO";
+  const string mix = "FoO";
+  const string zero = "FO0";
+
+  ASSERT_TRUE(iequals(foo, capital));
+  ASSERT_TRUE(iequals(foo, caps));
+  ASSERT_TRUE(iequals(foo, mix));
+  ASSERT_TRUE(iequals(capital, mix));
+  ASSERT_TRUE(iequals(caps, foo));
+  ASSERT_TRUE(iequals(caps, capital));
+  ASSERT_TRUE(iequals(caps, mix));
+
+  ASSERT_FALSE(iequals(foo, zero));
+  ASSERT_FALSE(iequals(capital, zero));
+  ASSERT_FALSE(iequals(caps, zero));
+  ASSERT_FALSE(iequals(mix, zero));
+}
+
 } // namespace kudu
diff --git a/src/kudu/util/string_case.cc b/src/kudu/util/string_case.cc
index 9d11f13..7737c69 100644
--- a/src/kudu/util/string_case.cc
+++ b/src/kudu/util/string_case.cc
@@ -17,6 +17,8 @@
 
 #include "kudu/util/string_case.h"
 
+#include <stddef.h>
+
 #include <algorithm>
 #include <cctype>
 #include <cstdint>
@@ -30,9 +32,19 @@ namespace kudu {
 
 using std::string;
 
+static bool IsAscii(const string& string) {
+  for (size_t i = 0; i < string.size(); ++i) {
+    if (!ascii_isascii(string[i])) {
+      return false;
+    }
+  }
+  return true;
+}
+
 void SnakeToCamelCase(const std::string& snake_case,
                       std::string* camel_case) {
   DCHECK_NE(camel_case, &snake_case) << "Does not support in-place operation";
+  DCHECK(IsAscii(snake_case));
   camel_case->clear();
   camel_case->reserve(snake_case.size());
 
@@ -54,6 +66,7 @@ void SnakeToCamelCase(const std::string& snake_case,
 
 void ToUpperCase(const std::string& string,
                  std::string* out) {
+  DCHECK(IsAscii(string));
   if (out != &string) {
     *out = string;
   }
@@ -63,6 +76,7 @@ void ToUpperCase(const std::string& string,
 
 void ToLowerCase(const std::string& string,
                  std::string* out) {
+  DCHECK(IsAscii(string));
   if (out != &string) {
     *out = string;
   }
@@ -71,6 +85,7 @@ void ToLowerCase(const std::string& string,
 }
 
 void Capitalize(string* word) {
+  DCHECK(IsAscii(*word));
   uint32_t size = word->size();
   if (size == 0) {
     return;
@@ -83,4 +98,18 @@ void Capitalize(string* word) {
   }
 }
 
+bool iequals(const string& a, const string& b) {
+  DCHECK(IsAscii(a));
+  DCHECK(IsAscii(b));
+  if (b.size() != a.size()) {
+    return false;
+  }
+  for (size_t i = 0; i < a.size(); ++i) {
+    if (ascii_tolower(a[i]) != ascii_tolower(b[i])) {
+      return false;
+    }
+  }
+  return true;
+}
+
 } // namespace kudu
diff --git a/src/kudu/util/string_case.h b/src/kudu/util/string_case.h
index a010a2d..0a911f1 100644
--- a/src/kudu/util/string_case.h
+++ b/src/kudu/util/string_case.h
@@ -57,4 +57,7 @@ inline void ToLowerCase(std::string* s) {
 // - 'hiBerNATe' -> 'Hibernate'
 void Capitalize(std::string* word);
 
+// True if all of the ASCII characters in the given strings match case insensitively.
+bool iequals(const std::string& a, const std::string& b);
+
 } // namespace kudu


[kudu] 01/05: [docs] Fix a typo in the installation docs

Posted by gr...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 3e02cce2f2a424529ca5c9a88017a40731acf974
Author: Grant Henke <gr...@apache.org>
AuthorDate: Tue Dec 8 11:57:45 2020 -0600

    [docs] Fix a typo in the installation docs
    
    Change-Id: I96bfdd3d5245313b40205eab69434d34506e766c
    Reviewed-on: http://gerrit.cloudera.org:8080/16832
    Tested-by: Kudu Jenkins
    Reviewed-by: Alexey Serbin <as...@cloudera.com>
---
 docs/installation.adoc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/installation.adoc b/docs/installation.adoc
index 0cf233d..ffce4be 100644
--- a/docs/installation.adoc
+++ b/docs/installation.adoc
@@ -85,7 +85,7 @@ Below are the steps for each supported operating system to build Kudu from  sour
 
 [[rhel_from_source]]
 === RHEL or CentOS
-RHEL or CentOS 7.6 or later is required to build Kudu from source. To build
+RHEL or CentOS 7.0 or later is required to build Kudu from source. To build
 on a version older than 8.0, the Red Hat Developer Toolset must be installed
 (in order to have access to a C++17 capable compiler).
 


[kudu] 02/05: [tserver] KUDU-2612: participant op RPC endpoint

Posted by gr...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 70bce76af3008031e1dd9559bfa6dca1763d895f
Author: Andrew Wong <aw...@cloudera.com>
AuthorDate: Tue Dec 1 14:56:10 2020 -0800

    [tserver] KUDU-2612: participant op RPC endpoint
    
    This adds an RPC endpoint to the tablet servers that allows proxies to
    interact with transaction participants. This will be used in step 13 and
    step 18 of the transaction write path[1], allowing the TxnStatusManagers
    to update participants' transaction states.
    
    [1] https://docs.google.com/document/d/1qv7Zejpfzg-HvF5azRL49g5lRLQ4437EmJ53GiupcWQ/edit#heading=h.4lm41o75ev1x
    
    Change-Id: Ic48895438ce67e453d235934ac560efe8415921b
    Reviewed-on: http://gerrit.cloudera.org:8080/16814
    Reviewed-by: Hao Hao <ha...@cloudera.com>
    Tested-by: Kudu Jenkins
    Reviewed-by: Alexey Serbin <as...@cloudera.com>
---
 .../integration-tests/txn_participant-itest.cc     | 236 +++++++++++++++++++++
 src/kudu/tserver/tablet_service.cc                 |  32 +++
 src/kudu/tserver/tablet_service.h                  |   6 +
 src/kudu/tserver/tserver_admin.proto               |   7 +-
 4 files changed, 280 insertions(+), 1 deletion(-)

diff --git a/src/kudu/integration-tests/txn_participant-itest.cc b/src/kudu/integration-tests/txn_participant-itest.cc
index 81be4c0..0373b69 100644
--- a/src/kudu/integration-tests/txn_participant-itest.cc
+++ b/src/kudu/integration-tests/txn_participant-itest.cc
@@ -25,6 +25,7 @@
 #include <utility>
 #include <vector>
 
+#include <boost/optional/optional.hpp>
 #include <gflags/gflags_declare.h>
 #include <glog/logging.h>
 #include <gtest/gtest.h>
@@ -45,6 +46,8 @@
 #include "kudu/integration-tests/cluster_itest_util.h"
 #include "kudu/integration-tests/test_workload.h"
 #include "kudu/mini-cluster/internal_mini_cluster.h"
+#include "kudu/rpc/rpc_controller.h"
+#include "kudu/tablet/metadata.pb.h"
 #include "kudu/tablet/mvcc.h"
 #include "kudu/tablet/tablet-test-util.h"
 #include "kudu/tablet/tablet.h"
@@ -58,6 +61,7 @@
 #include "kudu/tserver/ts_tablet_manager.h"
 #include "kudu/tserver/tserver.pb.h"
 #include "kudu/tserver/tserver_admin.pb.h"
+#include "kudu/tserver/tserver_admin.proxy.h"
 #include "kudu/util/countdown_latch.h"
 #include "kudu/util/metrics.h"
 #include "kudu/util/monotime.h"
@@ -74,6 +78,7 @@ DECLARE_int32(follower_unavailable_considered_failed_sec);
 DECLARE_int32(log_segment_size_mb);
 DECLARE_int32(maintenance_manager_polling_interval_ms);
 DECLARE_int32(raft_heartbeat_interval_ms);
+DECLARE_int32(tablet_bootstrap_inject_latency_ms);
 
 METRIC_DECLARE_histogram(log_gc_duration);
 
@@ -85,7 +90,9 @@ using kudu::tablet::TabletReplica;
 using kudu::tablet::Txn;
 using kudu::tablet::TxnParticipant;
 using kudu::tserver::ParticipantOpPB;
+using kudu::tserver::ParticipantRequestPB;
 using kudu::tserver::ParticipantResponsePB;
+using kudu::tserver::TabletServerAdminServiceProxy;
 using kudu::tserver::WriteRequestPB;
 using std::string;
 using std::thread;
@@ -98,6 +105,39 @@ namespace kudu {
 namespace itest {
 
 namespace {
+ParticipantRequestPB ParticipantRequest(const string& tablet_id, int64_t txn_id,
+                                        ParticipantOpPB::ParticipantOpType type) {
+  ParticipantRequestPB req;
+  req.set_tablet_id(tablet_id);
+  auto* op_pb = req.mutable_op();
+  op_pb->set_txn_id(txn_id);
+  op_pb->set_type(type);
+  if (type == ParticipantOpPB::FINALIZE_COMMIT) {
+    op_pb->set_finalized_commit_timestamp(kDummyCommitTimestamp);
+  }
+  return req;
+}
+
+Status ParticipateInTransaction(TabletServerAdminServiceProxy* admin_proxy,
+                                const string& tablet_id, int64_t txn_id,
+                                ParticipantOpPB::ParticipantOpType type,
+                                ParticipantResponsePB* resp) {
+  rpc::RpcController rpc;
+  return admin_proxy->ParticipateInTransaction(
+      ParticipantRequest(tablet_id, txn_id, type), resp, &rpc);
+}
+
+Status ParticipateInTransactionCheckResp(TabletServerAdminServiceProxy* admin_proxy,
+                                         const string& tablet_id, int64_t txn_id,
+                                         ParticipantOpPB::ParticipantOpType type) {
+  ParticipantResponsePB resp;
+  RETURN_NOT_OK(ParticipateInTransaction(admin_proxy, tablet_id, txn_id, type, &resp));
+  if (resp.has_error()) {
+    return StatusFromPB(resp.error().status());
+  }
+  return Status::OK();
+}
+
 vector<Status> RunOnReplicas(const vector<TabletReplica*>& replicas,
                              int64_t txn_id,
                              ParticipantOpPB::ParticipantOpType type,
@@ -452,6 +492,202 @@ TEST_F(TxnParticipantITest, TestWaitOnAbortCommit) {
   ASSERT_OK(WaitForCompletedOps(follower_replica, before_abort_ts, kShortTimeout));
 }
 
+TEST_F(TxnParticipantITest, TestProxyBasicCalls) {
+  constexpr const int kLeaderIdx = 0;
+  constexpr const int kTxnId = 0;
+  vector<TabletReplica*> replicas = SetUpLeaderGetReplicas(kLeaderIdx);
+  ASSERT_OK(replicas[kLeaderIdx]->consensus()->WaitUntilLeaderForTests(MonoDelta::FromSeconds(10)));
+  auto admin_proxy = cluster_->tserver_admin_proxy(kLeaderIdx);
+  for (const auto& op : kCommitSequence) {
+    const auto req = ParticipantRequest(replicas[kLeaderIdx]->tablet_id(), kTxnId, op);
+    ParticipantResponsePB resp;
+    rpc::RpcController rpc;
+    ASSERT_OK(admin_proxy->ParticipateInTransaction(req, &resp, &rpc));
+  }
+}
+
+TEST_F(TxnParticipantITest, TestProxyInvalidStatesInCommitSequence) {
+  constexpr const int kLeaderIdx = 0;
+  constexpr const int kTxnId = 0;
+  vector<TabletReplica*> replicas = SetUpLeaderGetReplicas(kLeaderIdx);
+  ASSERT_OK(replicas[kLeaderIdx]->consensus()->WaitUntilLeaderForTests(MonoDelta::FromSeconds(10)));
+  auto admin_proxy = cluster_->tserver_admin_proxy(kLeaderIdx);
+
+  // Begin after already beginning.
+  const auto tablet_id = replicas[kLeaderIdx]->tablet_id();
+  ASSERT_OK(ParticipateInTransactionCheckResp(
+      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_TXN));
+  // TODO(awong): IllegalState error codes may be easily misconstrued with
+  // errors coming from Raft. We should make the participants return something
+  // like InvalidArgument if there's an unexpected state.
+  // TODO(awong): make repeated, idempotent return OK instead of an error.
+  Status s = ParticipateInTransactionCheckResp(
+      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_TXN);
+  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+
+  // We can't finalize the commit without beginning to commit first.
+  s = ParticipateInTransactionCheckResp(
+      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::FINALIZE_COMMIT);
+  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+
+  // Start commititng and ensure we can't start another transaction.
+  ASSERT_OK(ParticipateInTransactionCheckResp(
+      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_COMMIT));
+
+  s = ParticipateInTransactionCheckResp(
+      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_COMMIT);
+  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+
+  s = ParticipateInTransactionCheckResp(
+      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_TXN);
+  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+
+  // Finalize the commit and ensure we can do nothing else.
+  ASSERT_OK(ParticipateInTransactionCheckResp(
+      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::FINALIZE_COMMIT));
+  s = ParticipateInTransactionCheckResp(
+      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::FINALIZE_COMMIT);
+  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+
+  s = ParticipateInTransactionCheckResp(
+      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_TXN);
+  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+
+  s = ParticipateInTransactionCheckResp(
+      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_COMMIT);
+  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+
+  s = ParticipateInTransactionCheckResp(
+      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::ABORT_TXN);
+  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+}
+
+TEST_F(TxnParticipantITest, TestProxyInvalidStatesInAbortSequence) {
+  constexpr const int kLeaderIdx = 0;
+  constexpr const int kTxnId = 0;
+  vector<TabletReplica*> replicas = SetUpLeaderGetReplicas(kLeaderIdx);
+  ASSERT_OK(replicas[kLeaderIdx]->consensus()->WaitUntilLeaderForTests(MonoDelta::FromSeconds(10)));
+  auto admin_proxy = cluster_->tserver_admin_proxy(kLeaderIdx);
+
+  // Begin after already beginning.
+  const auto tablet_id = replicas[kLeaderIdx]->tablet_id();
+  ASSERT_OK(ParticipateInTransactionCheckResp(
+      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_TXN));
+  // TODO(awong): IllegalState error codes may be easily misconstrued with
+  // errors coming from Raft. We should make the participants return something
+  // like InvalidArgument if there's an unexpected state.
+  // TODO(awong): make repeated, idempotent return OK instead of an error.
+  Status s = ParticipateInTransactionCheckResp(
+      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_TXN);
+  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+
+  s = ParticipateInTransactionCheckResp(
+      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::FINALIZE_COMMIT);
+  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+
+  // Abort the transaction and ensure we can do nothing else.
+  ASSERT_OK(ParticipateInTransactionCheckResp(
+      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::ABORT_TXN));
+
+  s = ParticipateInTransactionCheckResp(
+      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::ABORT_TXN);
+  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+
+  s = ParticipateInTransactionCheckResp(
+      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::FINALIZE_COMMIT);
+  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+
+  s = ParticipateInTransactionCheckResp(
+      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_TXN);
+  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+
+  s = ParticipateInTransactionCheckResp(
+      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_COMMIT);
+  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+}
+
+TEST_F(TxnParticipantITest, TestProxyNonLeader) {
+  constexpr const int kLeaderIdx = 0;
+  constexpr const int kNonLeaderIdx = kLeaderIdx + 1;
+  constexpr const int kTxnId = 0;
+  vector<TabletReplica*> replicas = SetUpLeaderGetReplicas(kLeaderIdx);
+  ASSERT_OK(replicas[kLeaderIdx]->consensus()->WaitUntilLeaderForTests(MonoDelta::FromSeconds(10)));
+  auto admin_proxy = cluster_->tserver_admin_proxy(kNonLeaderIdx);
+  for (const auto& op : kCommitSequence) {
+    const auto req = ParticipantRequest(replicas[kLeaderIdx]->tablet_id(), kTxnId, op);
+    ParticipantResponsePB resp;
+    rpc::RpcController rpc;
+    ASSERT_OK(admin_proxy->ParticipateInTransaction(req, &resp, &rpc));
+    ASSERT_TRUE(resp.has_error());
+    auto resp_error = StatusFromPB(resp.error().status());
+    ASSERT_TRUE(resp_error.IsIllegalState()) << resp_error.ToString();
+    ASSERT_STR_CONTAINS(resp_error.ToString(), "not leader");
+  }
+}
+
+TEST_F(TxnParticipantITest, TestProxyTabletBootstrapping) {
+  constexpr const int kLeaderIdx = 0;
+  constexpr const int kTxnId = 0;
+  vector<TabletReplica*> replicas = SetUpLeaderGetReplicas(kLeaderIdx);
+  auto* leader_replica = replicas[kLeaderIdx];
+  ASSERT_OK(leader_replica->consensus()->WaitUntilLeaderForTests(MonoDelta::FromSeconds(10)));
+
+  FLAGS_tablet_bootstrap_inject_latency_ms = 1000;
+  cluster_->mini_tablet_server(kLeaderIdx)->Shutdown();
+  ASSERT_OK(cluster_->mini_tablet_server(kLeaderIdx)->Restart());
+  replicas = SetUpLeaderGetReplicas(kLeaderIdx);
+
+  auto admin_proxy = cluster_->tserver_admin_proxy(kLeaderIdx);
+  for (const auto& op : kCommitSequence) {
+    const auto req = ParticipantRequest(replicas[kLeaderIdx]->tablet_id(), kTxnId, op);
+    ParticipantResponsePB resp;
+    rpc::RpcController rpc;
+    ASSERT_OK(admin_proxy->ParticipateInTransaction(req, &resp, &rpc));
+    ASSERT_TRUE(resp.has_error());
+    auto resp_error = StatusFromPB(resp.error().status());
+    ASSERT_TRUE(resp_error.IsIllegalState()) << resp_error.ToString();
+    ASSERT_STR_CONTAINS(resp_error.ToString(), "not RUNNING");
+  }
+}
+
+TEST_F(TxnParticipantITest, TestProxyTabletNotRunning) {
+  constexpr const int kLeaderIdx = 0;
+  constexpr const int kTxnId = 0;
+  vector<TabletReplica*> replicas = SetUpLeaderGetReplicas(kLeaderIdx);
+  auto* leader_replica = replicas[kLeaderIdx];
+  ASSERT_OK(leader_replica->consensus()->WaitUntilLeaderForTests(MonoDelta::FromSeconds(10)));
+  auto* tablet_manager = cluster_->mini_tablet_server(kLeaderIdx)->server()->tablet_manager();
+  ASSERT_OK(tablet_manager->DeleteTablet(leader_replica->tablet_id(),
+      tablet::TABLET_DATA_TOMBSTONED, boost::none));
+
+  auto admin_proxy = cluster_->tserver_admin_proxy(kLeaderIdx);
+  for (const auto& op : kCommitSequence) {
+    const auto req = ParticipantRequest(replicas[kLeaderIdx]->tablet_id(), kTxnId, op);
+    ParticipantResponsePB resp;
+    rpc::RpcController rpc;
+    ASSERT_OK(admin_proxy->ParticipateInTransaction(req, &resp, &rpc));
+    ASSERT_TRUE(resp.has_error());
+    auto resp_error = StatusFromPB(resp.error().status());
+    ASSERT_TRUE(resp_error.IsIllegalState()) << resp_error.ToString();
+    ASSERT_STR_CONTAINS(resp_error.ToString(), "not RUNNING");
+  }
+}
+
+TEST_F(TxnParticipantITest, TestProxyTabletNotFound) {
+  constexpr const int kTxnId = 0;
+  auto admin_proxy = cluster_->tserver_admin_proxy(0);
+  for (const auto& op : kCommitSequence) {
+    const auto req = ParticipantRequest("dummy-tablet-id", kTxnId, op);
+    ParticipantResponsePB resp;
+    rpc::RpcController rpc;
+    ASSERT_OK(admin_proxy->ParticipateInTransaction(req, &resp, &rpc));
+    ASSERT_TRUE(resp.has_error());
+    auto resp_error = StatusFromPB(resp.error().status());
+    ASSERT_TRUE(resp_error.IsNotFound()) << resp_error.ToString();
+    ASSERT_STR_CONTAINS(resp_error.ToString(), "not found");
+  }
+}
+
 class TxnParticipantElectionStormITest : public TxnParticipantITest {
  public:
   void SetUp() override {
diff --git a/src/kudu/tserver/tablet_service.cc b/src/kudu/tserver/tablet_service.cc
index ec6c608..12c6326 100644
--- a/src/kudu/tserver/tablet_service.cc
+++ b/src/kudu/tserver/tablet_service.cc
@@ -79,6 +79,7 @@
 #include "kudu/tablet/mvcc.h"
 #include "kudu/tablet/ops/alter_schema_op.h"
 #include "kudu/tablet/ops/op.h"
+#include "kudu/tablet/ops/participant_op.h"
 #include "kudu/tablet/ops/write_op.h"
 #include "kudu/tablet/rowset.h"
 #include "kudu/tablet/tablet.h"
@@ -218,6 +219,7 @@ using kudu::security::TokenPB;
 using kudu::server::ServerBase;
 using kudu::tablet::AlterSchemaOpState;
 using kudu::tablet::MvccSnapshot;
+using kudu::tablet::ParticipantOpState;
 using kudu::tablet::TABLET_DATA_COPYING;
 using kudu::tablet::TABLET_DATA_DELETED;
 using kudu::tablet::TABLET_DATA_TOMBSTONED;
@@ -1296,6 +1298,36 @@ void TabletServiceAdminImpl::CoordinateTransaction(const CoordinateTransactionRe
   context->RespondSuccess();
 }
 
+void TabletServiceAdminImpl::ParticipateInTransaction(const ParticipantRequestPB* req,
+                                                      ParticipantResponsePB* resp,
+                                                      rpc::RpcContext* context) {
+  scoped_refptr<TabletReplica> replica;
+  if (!LookupRunningTabletReplicaOrRespond(server_->tablet_manager(), req->tablet_id(), resp,
+                                           context, &replica)) {
+    return;
+  }
+  shared_ptr<Tablet> tablet;
+  TabletServerErrorPB::Code error_code;
+  Status s = GetTabletRef(replica, &tablet, &error_code);
+  if (PREDICT_FALSE(!s.ok())) {
+    SetupErrorAndRespond(resp->mutable_error(), s, error_code, context);
+    return;
+  }
+  // TODO(awong): consider memory-based throttling?
+  // TODO(awong): we should also persist the transaction's owner, and prevent
+  // other users from mutating it.
+  unique_ptr<ParticipantOpState> op_state(
+      new ParticipantOpState(replica.get(), tablet->txn_participant(), req, resp));
+  op_state->set_completion_callback(unique_ptr<OpCompletionCallback>(
+      new RpcOpCompletionCallback<ParticipantResponsePB>(context, resp)));
+  s = replica->SubmitTxnParticipantOp(std::move(op_state));
+  if (PREDICT_FALSE(!s.ok())) {
+    SetupErrorAndRespond(resp->mutable_error(), s,
+                         TabletServerErrorPB::UNKNOWN_ERROR,
+                         context);
+  }
+}
+
 bool TabletServiceAdminImpl::SupportsFeature(uint32_t feature) const {
   switch (feature) {
     case TabletServerFeatures::COLUMN_PREDICATES:
diff --git a/src/kudu/tserver/tablet_service.h b/src/kudu/tserver/tablet_service.h
index 2c96835..a7ce79f 100644
--- a/src/kudu/tserver/tablet_service.h
+++ b/src/kudu/tserver/tablet_service.h
@@ -96,6 +96,8 @@ class CreateTabletRequestPB;
 class CreateTabletResponsePB;
 class DeleteTabletRequestPB;
 class DeleteTabletResponsePB;
+class ParticipantRequestPB;
+class ParticipantResponsePB;
 class QuiesceTabletServerRequestPB;
 class QuiesceTabletServerResponsePB;
 class ScanResultCollector;
@@ -233,6 +235,10 @@ class TabletServiceAdminImpl : public TabletServerAdminServiceIf {
                              CoordinateTransactionResponsePB* resp,
                              rpc::RpcContext* context) override;
 
+  void ParticipateInTransaction(const ParticipantRequestPB* req,
+                                ParticipantResponsePB* resp,
+                                rpc::RpcContext* context) override;
+
   bool SupportsFeature(uint32_t feature) const override;
 
  private:
diff --git a/src/kudu/tserver/tserver_admin.proto b/src/kudu/tserver/tserver_admin.proto
index e95a12a..9572318 100644
--- a/src/kudu/tserver/tserver_admin.proto
+++ b/src/kudu/tserver/tserver_admin.proto
@@ -111,7 +111,8 @@ message ParticipantOpPB {
 }
 
 message ParticipantRequestPB {
-  optional ParticipantOpPB op = 1;
+  optional bytes tablet_id = 1;
+  optional ParticipantOpPB op = 2;
 }
 
 message ParticipantResponsePB {
@@ -251,6 +252,10 @@ service TabletServerAdminService {
   // Coordinate the lifecycle of a transaction.
   rpc CoordinateTransaction(CoordinateTransactionRequestPB)
       returns (CoordinateTransactionResponsePB);
+
+  // Request that a tablet participate in a transaction, or update the state of
+  // an existing transaction participant.
+  rpc ParticipateInTransaction(ParticipantRequestPB) returns (ParticipantResponsePB);
 }
 
 message QuiesceTabletServerRequestPB {


[kudu] 04/05: [build] CMake fixes for Apple ARM builds

Posted by gr...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 42e3ae332ab91a713a31c84acb3d256d9b23edd1
Author: Grant Henke <gr...@apache.org>
AuthorDate: Thu Dec 3 09:37:36 2020 -0600

    [build] CMake fixes for Apple ARM builds
    
    This patch includes some small tweaks for CMake when building Kudu on
    Apple ARM architectures. It also includes some logging to help debug an
    issues in the future.
    
    Change-Id: Ifad9a4d272e6d720ac006da313f53490cfd47a9c
    Reviewed-on: http://gerrit.cloudera.org:8080/16816
    Tested-by: Kudu Jenkins
    Reviewed-by: Andrew Wong <aw...@cloudera.com>
---
 CMakeLists.txt                | 14 ++++++++++++--
 src/kudu/gutil/CMakeLists.txt |  2 +-
 2 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7d75ea8..d613233 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -24,8 +24,11 @@ cmake_minimum_required(VERSION 3.11.2)
 
 project(Kudu)
 
-# Print out the CMAKE_OSX_SYSROOT to help debug Apple build issues.
-if (CMAKE_OSX_SYSROOT)
+
+if (APPLE)
+  # Print out the CMAKE_OSX_* variables to help debug Apple build issues.
+  message(STATUS "Using CMAKE_OSX_ARCHITECTURES: ${CMAKE_OSX_ARCHITECTURES}")
+  message(STATUS "Using CMAKE_OSX_DEPLOYMENT_TARGET: ${CMAKE_OSX_DEPLOYMENT_TARGET}")
   message(STATUS "Using CMAKE_OSX_SYSROOT: ${CMAKE_OSX_SYSROOT}")
 endif()
 
@@ -157,6 +160,13 @@ include(CompilerInfo)
 
 # compiler flags that are common across debug/release builds
 execute_process(COMMAND uname -m OUTPUT_VARIABLE ARCH_NAME)
+message(STATUS "Found ARCH_NAME: ${ARCH_NAME}")
+# The Apple backend for aarch64 is called arm64.
+# Rename to aarch64 to simplify all of our build checks.
+if("${ARCH_NAME}" MATCHES "arm64")
+  set(ARCH_NAME "aarch64")
+endif()
+message(STATUS "Using ARCH_NAME: ${ARCH_NAME}")
 if("${ARCH_NAME}" MATCHES "aarch64")
   # Certain platforms such as ARM do not use signed chars by default
   # which causes issues with certain bounds checks.
diff --git a/src/kudu/gutil/CMakeLists.txt b/src/kudu/gutil/CMakeLists.txt
index 02808d9..460c975 100644
--- a/src/kudu/gutil/CMakeLists.txt
+++ b/src/kudu/gutil/CMakeLists.txt
@@ -50,7 +50,7 @@ set(GUTIL_SRCS
   utf/rune.c
   walltime.cc)
 
-if (NOT CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")
+if (NOT "${ARCH_NAME}" MATCHES "aarch64")
 set(GUTIL_SRCS
   atomicops-internals-x86.cc
   ${GUTIL_SRCS})


[kudu] 05/05: txn_participant: return TXN_ILLEGAL_STATE code on illegal state errors

Posted by gr...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 57c757d084956710143b8d85fddb4d6f1fc87211
Author: Andrew Wong <aw...@cloudera.com>
AuthorDate: Mon Dec 7 19:00:30 2020 -0800

    txn_participant: return TXN_ILLEGAL_STATE code on illegal state errors
    
    I have a patch coming up that sends RPCs that submit participant ops to
    the TxnParticipant and retries if necessary. One of the criteria to
    retry an RPC is checking if the Raft application returned an
    IllegalState error, e.g. in RaftConsensus::CheckLeadershipAndBindTerm.
    
    This is problematic, as TxnParticipants may also return IllegalState if
    the transaction is not in an appropriate state (e.g. an RPC tries to
    abort an already-committed transaction). To disambiguate such cases,
    this patch updates the TxnParticipants to also set a TabletServerErrorPB
    code when such an error occurs, allowing for more specific error
    handling to be determined.
    
    Change-Id: Ibfb8400666855c694b78b1425b1c121597ec7ccf
    Reviewed-on: http://gerrit.cloudera.org:8080/16831
    Reviewed-by: Alexey Serbin <as...@cloudera.com>
    Tested-by: Andrew Wong <aw...@cloudera.com>
---
 .../integration-tests/txn_participant-itest.cc     | 188 +++++++++++++--------
 src/kudu/tablet/ops/participant_op.cc              |  27 ++-
 src/kudu/tablet/ops/participant_op.h               |   5 +-
 src/kudu/tablet/txn_participant-test.cc            |   3 +
 src/kudu/tablet/txn_participant.h                  |  24 ++-
 5 files changed, 159 insertions(+), 88 deletions(-)

diff --git a/src/kudu/integration-tests/txn_participant-itest.cc b/src/kudu/integration-tests/txn_participant-itest.cc
index 0373b69..b962571 100644
--- a/src/kudu/integration-tests/txn_participant-itest.cc
+++ b/src/kudu/integration-tests/txn_participant-itest.cc
@@ -18,6 +18,7 @@
 #include <atomic>
 #include <cstdint>
 #include <functional>
+#include <initializer_list>
 #include <memory>
 #include <string>
 #include <thread>
@@ -93,6 +94,7 @@ using kudu::tserver::ParticipantOpPB;
 using kudu::tserver::ParticipantRequestPB;
 using kudu::tserver::ParticipantResponsePB;
 using kudu::tserver::TabletServerAdminServiceProxy;
+using kudu::tserver::TabletServerErrorPB;
 using kudu::tserver::WriteRequestPB;
 using std::string;
 using std::thread;
@@ -129,10 +131,14 @@ Status ParticipateInTransaction(TabletServerAdminServiceProxy* admin_proxy,
 
 Status ParticipateInTransactionCheckResp(TabletServerAdminServiceProxy* admin_proxy,
                                          const string& tablet_id, int64_t txn_id,
-                                         ParticipantOpPB::ParticipantOpType type) {
+                                         ParticipantOpPB::ParticipantOpType type,
+                                         TabletServerErrorPB::Code* code = nullptr) {
   ParticipantResponsePB resp;
   RETURN_NOT_OK(ParticipateInTransaction(admin_proxy, tablet_id, txn_id, type, &resp));
   if (resp.has_error()) {
+    if (code) {
+      *code = resp.error().code();
+    }
     return StatusFromPB(resp.error().status());
   }
   return Status::OK();
@@ -506,7 +512,7 @@ TEST_F(TxnParticipantITest, TestProxyBasicCalls) {
   }
 }
 
-TEST_F(TxnParticipantITest, TestProxyInvalidStatesInCommitSequence) {
+TEST_F(TxnParticipantITest, TestProxyIllegalStatesInCommitSequence) {
   constexpr const int kLeaderIdx = 0;
   constexpr const int kTxnId = 0;
   vector<TabletReplica*> replicas = SetUpLeaderGetReplicas(kLeaderIdx);
@@ -515,95 +521,135 @@ TEST_F(TxnParticipantITest, TestProxyInvalidStatesInCommitSequence) {
 
   // Begin after already beginning.
   const auto tablet_id = replicas[kLeaderIdx]->tablet_id();
-  ASSERT_OK(ParticipateInTransactionCheckResp(
-      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_TXN));
-  // TODO(awong): IllegalState error codes may be easily misconstrued with
-  // errors coming from Raft. We should make the participants return something
-  // like InvalidArgument if there's an unexpected state.
-  // TODO(awong): make repeated, idempotent return OK instead of an error.
-  Status s = ParticipateInTransactionCheckResp(
-      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_TXN);
-  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+  {
+    TabletServerErrorPB::Code code = TabletServerErrorPB::UNKNOWN_ERROR;
+    ASSERT_OK(ParticipateInTransactionCheckResp(
+        admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_TXN, &code));
+    ASSERT_EQ(TabletServerErrorPB::UNKNOWN_ERROR, code);
+  }
+  // TODO(awong): make repeated, idempotent return OK instead of an error? Or
+  // return a different error code so callers can distinguish between a benign
+  // error.
+  {
+    TabletServerErrorPB::Code code = TabletServerErrorPB::UNKNOWN_ERROR;
+    Status s = ParticipateInTransactionCheckResp(
+        admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_TXN, &code);
+    ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+    ASSERT_EQ(TabletServerErrorPB::TXN_ILLEGAL_STATE, code);
+  }
 
   // We can't finalize the commit without beginning to commit first.
-  s = ParticipateInTransactionCheckResp(
-      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::FINALIZE_COMMIT);
-  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+  {
+    TabletServerErrorPB::Code code = TabletServerErrorPB::UNKNOWN_ERROR;
+    Status s = ParticipateInTransactionCheckResp(
+        admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::FINALIZE_COMMIT, &code);
+    ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+    ASSERT_EQ(TabletServerErrorPB::TXN_ILLEGAL_STATE, code);
+  }
 
   // Start commititng and ensure we can't start another transaction.
-  ASSERT_OK(ParticipateInTransactionCheckResp(
-      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_COMMIT));
-
-  s = ParticipateInTransactionCheckResp(
-      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_COMMIT);
-  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
-
-  s = ParticipateInTransactionCheckResp(
-      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_TXN);
-  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+  {
+    TabletServerErrorPB::Code code = TabletServerErrorPB::UNKNOWN_ERROR;
+    ASSERT_OK(ParticipateInTransactionCheckResp(
+        admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_COMMIT, &code));
+    ASSERT_EQ(TabletServerErrorPB::UNKNOWN_ERROR, code);
+  }
+  {
+    TabletServerErrorPB::Code code = TabletServerErrorPB::UNKNOWN_ERROR;
+    Status s = ParticipateInTransactionCheckResp(
+        admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_COMMIT, &code);
+    ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+    ASSERT_EQ(TabletServerErrorPB::TXN_ILLEGAL_STATE, code);
+  }
+  {
+    TabletServerErrorPB::Code code = TabletServerErrorPB::UNKNOWN_ERROR;
+    Status s = ParticipateInTransactionCheckResp(
+        admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_TXN, &code);
+    ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+    ASSERT_EQ(TabletServerErrorPB::TXN_ILLEGAL_STATE, code);
+  }
 
   // Finalize the commit and ensure we can do nothing else.
-  ASSERT_OK(ParticipateInTransactionCheckResp(
-      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::FINALIZE_COMMIT));
-  s = ParticipateInTransactionCheckResp(
-      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::FINALIZE_COMMIT);
-  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
-
-  s = ParticipateInTransactionCheckResp(
-      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_TXN);
-  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
-
-  s = ParticipateInTransactionCheckResp(
-      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_COMMIT);
-  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
-
-  s = ParticipateInTransactionCheckResp(
-      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::ABORT_TXN);
-  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+  {
+    TabletServerErrorPB::Code code = TabletServerErrorPB::UNKNOWN_ERROR;
+    ASSERT_OK(ParticipateInTransactionCheckResp(
+        admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::FINALIZE_COMMIT, &code));
+    ASSERT_EQ(TabletServerErrorPB::UNKNOWN_ERROR, code);
+  }
+  {
+    TabletServerErrorPB::Code code = TabletServerErrorPB::UNKNOWN_ERROR;
+    Status s = ParticipateInTransactionCheckResp(
+        admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::FINALIZE_COMMIT, &code);
+    ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+    ASSERT_EQ(TabletServerErrorPB::TXN_ILLEGAL_STATE, code);
+  }
+  {
+    TabletServerErrorPB::Code code = TabletServerErrorPB::UNKNOWN_ERROR;
+    Status s = ParticipateInTransactionCheckResp(
+        admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_TXN, &code);
+    ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+    ASSERT_EQ(TabletServerErrorPB::TXN_ILLEGAL_STATE, code);
+  }
+  {
+    TabletServerErrorPB::Code code = TabletServerErrorPB::UNKNOWN_ERROR;
+    Status s = ParticipateInTransactionCheckResp(
+        admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_COMMIT, &code);
+    ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+    ASSERT_EQ(TabletServerErrorPB::TXN_ILLEGAL_STATE, code);
+  }
+  {
+    TabletServerErrorPB::Code code = TabletServerErrorPB::UNKNOWN_ERROR;
+    Status s = ParticipateInTransactionCheckResp(
+        admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::ABORT_TXN, &code);
+    ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+    ASSERT_EQ(TabletServerErrorPB::TXN_ILLEGAL_STATE, code);
+  }
 }
 
-TEST_F(TxnParticipantITest, TestProxyInvalidStatesInAbortSequence) {
+TEST_F(TxnParticipantITest, TestProxyIllegalStatesInAbortSequence) {
   constexpr const int kLeaderIdx = 0;
   constexpr const int kTxnId = 0;
   vector<TabletReplica*> replicas = SetUpLeaderGetReplicas(kLeaderIdx);
   ASSERT_OK(replicas[kLeaderIdx]->consensus()->WaitUntilLeaderForTests(MonoDelta::FromSeconds(10)));
   auto admin_proxy = cluster_->tserver_admin_proxy(kLeaderIdx);
 
-  // Begin after already beginning.
+  // Try our illegal ops when our transaction is open.
   const auto tablet_id = replicas[kLeaderIdx]->tablet_id();
   ASSERT_OK(ParticipateInTransactionCheckResp(
       admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_TXN));
-  // TODO(awong): IllegalState error codes may be easily misconstrued with
-  // errors coming from Raft. We should make the participants return something
-  // like InvalidArgument if there's an unexpected state.
   // TODO(awong): make repeated, idempotent return OK instead of an error.
-  Status s = ParticipateInTransactionCheckResp(
-      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_TXN);
-  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
-
-  s = ParticipateInTransactionCheckResp(
-      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::FINALIZE_COMMIT);
-  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+  {
+    TabletServerErrorPB::Code code = TabletServerErrorPB::UNKNOWN_ERROR;
+    Status s = ParticipateInTransactionCheckResp(
+        admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_TXN, &code);
+    ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+    ASSERT_EQ(TabletServerErrorPB::TXN_ILLEGAL_STATE, code);
+  }
+  {
+    TabletServerErrorPB::Code code = TabletServerErrorPB::UNKNOWN_ERROR;
+    Status s = ParticipateInTransactionCheckResp(
+        admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::FINALIZE_COMMIT, &code);
+    ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+    ASSERT_EQ(TabletServerErrorPB::TXN_ILLEGAL_STATE, code);
+  }
 
   // Abort the transaction and ensure we can do nothing else.
-  ASSERT_OK(ParticipateInTransactionCheckResp(
-      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::ABORT_TXN));
-
-  s = ParticipateInTransactionCheckResp(
-      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::ABORT_TXN);
-  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
-
-  s = ParticipateInTransactionCheckResp(
-      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::FINALIZE_COMMIT);
-  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
-
-  s = ParticipateInTransactionCheckResp(
-      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_TXN);
-  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
-
-  s = ParticipateInTransactionCheckResp(
-      admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::BEGIN_COMMIT);
-  ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+  {
+    TabletServerErrorPB::Code code = TabletServerErrorPB::UNKNOWN_ERROR;
+    ASSERT_OK(ParticipateInTransactionCheckResp(
+        admin_proxy.get(), tablet_id, kTxnId, ParticipantOpPB::ABORT_TXN, &code));
+    ASSERT_EQ(TabletServerErrorPB::UNKNOWN_ERROR, code);
+  }
+  for (auto type : { ParticipantOpPB::ABORT_TXN,
+                     ParticipantOpPB::FINALIZE_COMMIT,
+                     ParticipantOpPB::BEGIN_TXN,
+                     ParticipantOpPB::BEGIN_COMMIT}) {
+    TabletServerErrorPB::Code code = TabletServerErrorPB::UNKNOWN_ERROR;
+    Status s = ParticipateInTransactionCheckResp(
+        admin_proxy.get(), tablet_id, kTxnId, type, &code);
+    ASSERT_TRUE(s.IsIllegalState()) << s.ToString();
+    ASSERT_EQ(TabletServerErrorPB::TXN_ILLEGAL_STATE, code);
+  }
 }
 
 TEST_F(TxnParticipantITest, TestProxyNonLeader) {
diff --git a/src/kudu/tablet/ops/participant_op.cc b/src/kudu/tablet/ops/participant_op.cc
index fb5295b..621f24e 100644
--- a/src/kudu/tablet/ops/participant_op.cc
+++ b/src/kudu/tablet/ops/participant_op.cc
@@ -36,6 +36,7 @@
 #include "kudu/tablet/tablet.h"
 #include "kudu/tablet/tablet_replica.h"
 #include "kudu/tablet/txn_participant.h"
+#include "kudu/tserver/tserver.pb.h"
 #include "kudu/util/debug/trace_event.h"
 #include "kudu/util/pb_util.h"
 #include "kudu/util/status.h"
@@ -48,6 +49,7 @@ using kudu::consensus::OpId;
 using kudu::pb_util::SecureShortDebugString;
 using kudu::tablet::TabletReplica;
 using kudu::tserver::ParticipantOpPB;
+using kudu::tserver::TabletServerErrorPB;
 using std::string;
 using std::unique_ptr;
 using strings::Substitute;
@@ -91,20 +93,31 @@ string ParticipantOpState::ToString() const {
       ParticipantOpPB::ParticipantOpType_Name(request_->op().type()));
 }
 
-Status ParticipantOpState::ValidateOp() const {
+Status ParticipantOpState::ValidateOp() {
   const auto& op = request()->op();
   DCHECK(txn_);
+  TabletServerErrorPB::Code code = TabletServerErrorPB::UNKNOWN_ERROR;
+  Status s;
   switch (op.type()) {
     case ParticipantOpPB::BEGIN_TXN:
-      return txn_->ValidateBeginTransaction();
+      s = txn_->ValidateBeginTransaction(&code);
+      break;
     case ParticipantOpPB::BEGIN_COMMIT:
-      return txn_->ValidateBeginCommit();
+      s = txn_->ValidateBeginCommit(&code);
+      break;
     case ParticipantOpPB::FINALIZE_COMMIT:
-      return txn_->ValidateFinalize();
+      s = txn_->ValidateFinalize(&code);
+      break;
     case ParticipantOpPB::ABORT_TXN:
-      return txn_->ValidateAbort();
-    case ParticipantOpPB::UNKNOWN:
-      return Status::InvalidArgument("unknown op type");
+      s = txn_->ValidateAbort(&code);
+      break;
+    default:
+      s = Status::InvalidArgument("unknown op type");
+      break;
+  }
+  if (PREDICT_FALSE(!s.ok())) {
+    completion_callback()->set_error(s, code);
+    return s;
   }
   return Status::OK();
 }
diff --git a/src/kudu/tablet/ops/participant_op.h b/src/kudu/tablet/ops/participant_op.h
index 7c8bbd1..5c7a281 100644
--- a/src/kudu/tablet/ops/participant_op.h
+++ b/src/kudu/tablet/ops/participant_op.h
@@ -103,8 +103,9 @@ class ParticipantOpState : public OpState {
   friend class ParticipantOp;
 
   // Returns an error if the transaction is not in an appropriate state for
-  // the state change requested by this op.
-  Status ValidateOp() const;
+  // the state change requested by this op, also setting the OpState's callback
+  // error with an appropriate error code.
+  Status ValidateOp();
 
   // The particpant being mutated. This may differ from the one we'd get from
   // TabletReplica if, for instance, we're bootstrapping a new Tablet.
diff --git a/src/kudu/tablet/txn_participant-test.cc b/src/kudu/tablet/txn_participant-test.cc
index 868b0a4..3e24d07 100644
--- a/src/kudu/tablet/txn_participant-test.cc
+++ b/src/kudu/tablet/txn_participant-test.cc
@@ -75,6 +75,7 @@ using kudu::pb_util::SecureShortDebugString;
 using kudu::tserver::ParticipantRequestPB;
 using kudu::tserver::ParticipantResponsePB;
 using kudu::tserver::ParticipantOpPB;
+using kudu::tserver::TabletServerErrorPB;
 using kudu::tserver::WriteRequestPB;
 using std::map;
 using std::string;
@@ -254,6 +255,7 @@ TEST_F(TxnParticipantTest, TestTransactionNotFound) {
       SCOPED_TRACE(SecureShortDebugString(resp));
       ASSERT_TRUE(resp.has_error());
       ASSERT_TRUE(resp.error().has_status());
+      ASSERT_EQ(TabletServerErrorPB::UNKNOWN_ERROR, resp.error().code());
       ASSERT_EQ(AppStatusPB::NOT_FOUND, resp.error().status().code());
       ASSERT_FALSE(resp.has_timestamp());
     }
@@ -285,6 +287,7 @@ TEST_F(TxnParticipantTest, TestIllegalTransitions) {
       ASSERT_TRUE(resp.has_error());
       ASSERT_TRUE(resp.error().has_status());
       ASSERT_EQ(AppStatusPB::ILLEGAL_STATE, resp.error().status().code());
+      ASSERT_EQ(TabletServerErrorPB::TXN_ILLEGAL_STATE, resp.error().code());
       ASSERT_FALSE(resp.has_timestamp());
     }
   };
diff --git a/src/kudu/tablet/txn_participant.h b/src/kudu/tablet/txn_participant.h
index 3cea176..480b6c0 100644
--- a/src/kudu/tablet/txn_participant.h
+++ b/src/kudu/tablet/txn_participant.h
@@ -34,6 +34,7 @@
 #include "kudu/gutil/strings/substitute.h"
 #include "kudu/tablet/mvcc.h"
 #include "kudu/tablet/tablet_metadata.h"
+#include "kudu/tserver/tserver.pb.h"
 #include "kudu/util/locks.h"
 #include "kudu/util/rw_semaphore.h"
 #include "kudu/util/status.h"
@@ -110,45 +111,50 @@ class Txn : public RefCountedThreadSafe<Txn> {
   // Validates that the transaction is in the appropriate state to perform the
   // given operation. Should be called while holding the state lock before
   // replicating a participant op.
-  Status ValidateBeginTransaction() const {
+  Status ValidateBeginTransaction(tserver::TabletServerErrorPB::Code* code) const {
     DCHECK(state_lock_.is_locked());
     if (PREDICT_FALSE(tablet_metadata_->HasTxnMetadata(txn_id_))) {
+      *code = tserver::TabletServerErrorPB::TXN_ILLEGAL_STATE;
       return Status::IllegalState(
           strings::Substitute("Transaction metadata for transaction $0 already exists",
                               txn_id_));
     }
     if (PREDICT_FALSE(state_ != kInitializing)) {
+      *code = tserver::TabletServerErrorPB::TXN_ILLEGAL_STATE;
       return Status::IllegalState(
           strings::Substitute("Cannot begin transaction in state: $0",
                               StateToString(state_)));
     }
     return Status::OK();
   }
-  Status ValidateBeginCommit() const {
+  Status ValidateBeginCommit(tserver::TabletServerErrorPB::Code* code) const {
     DCHECK(state_lock_.is_locked());
-    RETURN_NOT_OK(CheckFinishedInitializing());
+    RETURN_NOT_OK(CheckFinishedInitializing(code));
     if (PREDICT_FALSE(state_ != kOpen)) {
+      *code = tserver::TabletServerErrorPB::TXN_ILLEGAL_STATE;
       return Status::IllegalState(
           strings::Substitute("Cannot begin committing transaction in state: $0",
                               StateToString(state_)));
     }
     return Status::OK();
   }
-  Status ValidateFinalize() const {
+  Status ValidateFinalize(tserver::TabletServerErrorPB::Code* code) const {
     DCHECK(state_lock_.is_locked());
-    RETURN_NOT_OK(CheckFinishedInitializing());
+    RETURN_NOT_OK(CheckFinishedInitializing(code));
     if (PREDICT_FALSE(state_ != kCommitInProgress)) {
+      *code = tserver::TabletServerErrorPB::TXN_ILLEGAL_STATE;
       return Status::IllegalState(
           strings::Substitute("Cannot finalize transaction in state: $0",
                               StateToString(state_)));
     }
     return Status::OK();
   }
-  Status ValidateAbort() const {
+  Status ValidateAbort(tserver::TabletServerErrorPB::Code* code) const {
     DCHECK(state_lock_.is_locked());
-    RETURN_NOT_OK(CheckFinishedInitializing());
+    RETURN_NOT_OK(CheckFinishedInitializing(code));
     if (PREDICT_FALSE(state_ != kOpen &&
                       state_ != kCommitInProgress)) {
+      *code = tserver::TabletServerErrorPB::TXN_ILLEGAL_STATE;
       return Status::IllegalState(
           strings::Substitute("Cannot abort transaction in state: $0",
                               StateToString(state_)));
@@ -207,15 +213,17 @@ class Txn : public RefCountedThreadSafe<Txn> {
   }
 
   // Returns an error if the transaction has not finished initializing.
-  Status CheckFinishedInitializing() const {
+  Status CheckFinishedInitializing(tserver::TabletServerErrorPB::Code* code) const {
     if (PREDICT_FALSE(state_ == kInitializing)) {
       if (tablet_metadata_->HasTxnMetadata(txn_id_)) {
         // We created this transaction as a part of this op (i.e. it was not
         // already in flight), and there is existing metadata for it.
+        *code = tserver::TabletServerErrorPB::TXN_ILLEGAL_STATE;
         return Status::IllegalState(
             strings::Substitute("Transaction metadata for transaction $0 already exists",
                                 txn_id_));
       }
+      // TODO(awong): add another code for this?
       return Status::NotFound("Transaction hasn't been successfully started");
     }
     return Status::OK();