kudu-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From a...@apache.org
Subject [5/9] kudu git commit: KUDU-1311 [master] support adding and dropping range partitions
Date Mon, 01 Aug 2016 22:11:42 GMT
KUDU-1311 [master] support adding and dropping range partitions

Change-Id: I42437f365397baf9d4b39b5b17a1587fae70c4be
Reviewed-on: http://gerrit.cloudera.org:8080/3648
Tested-by: Kudu Jenkins
Reviewed-by: Adar Dembo <adar@cloudera.com>


Project: http://git-wip-us.apache.org/repos/asf/kudu/repo
Commit: http://git-wip-us.apache.org/repos/asf/kudu/commit/232474a5
Tree: http://git-wip-us.apache.org/repos/asf/kudu/tree/232474a5
Diff: http://git-wip-us.apache.org/repos/asf/kudu/diff/232474a5

Branch: refs/heads/master
Commit: 232474a51f727dc19a6cbbb56fc7d174553f196d
Parents: 284591a
Author: Dan Burkert <dan@cloudera.com>
Authored: Fri Jun 24 13:43:14 2016 -0700
Committer: Dan Burkert <dan@cloudera.com>
Committed: Mon Aug 1 15:31:08 2016 +0000

----------------------------------------------------------------------
 src/kudu/client/client-internal.cc              |   9 +-
 src/kudu/client/client-internal.h               |   3 +-
 src/kudu/client/client-test.cc                  |  10 +-
 src/kudu/client/client.cc                       |  88 ++++-
 src/kudu/client/client.h                        |  53 +++
 src/kudu/client/meta_cache.cc                   |  32 +-
 src/kudu/client/meta_cache.h                    |   8 +-
 src/kudu/client/table_alterer-internal.cc       |  30 +-
 src/kudu/client/table_alterer-internal.h        |  16 +-
 src/kudu/common/partition.cc                    |   4 +-
 .../alter_table-randomized-test.cc              | 328 ++++++++++++----
 src/kudu/integration-tests/alter_table-test.cc  | 351 ++++++++++++++++-
 .../integration-tests/external_mini_cluster.cc  |  39 +-
 .../integration-tests/external_mini_cluster.h   |   4 +-
 src/kudu/master/catalog_manager.cc              | 373 ++++++++++++++++---
 src/kudu/master/catalog_manager.h               |  31 +-
 src/kudu/master/master.proto                    |  20 +
 src/kudu/master/master_service.cc               |   6 +-
 src/kudu/tools/ksck-test.cc                     |   2 +-
 src/kudu/tools/ksck.cc                          |  21 +-
 20 files changed, 1215 insertions(+), 213 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kudu/blob/232474a5/src/kudu/client/client-internal.cc
----------------------------------------------------------------------
diff --git a/src/kudu/client/client-internal.cc b/src/kudu/client/client-internal.cc
index bdd8067..69d643d 100644
--- a/src/kudu/client/client-internal.cc
+++ b/src/kudu/client/client-internal.cc
@@ -436,7 +436,12 @@ Status KuduClient::Data::DeleteTable(KuduClient* client,
 
 Status KuduClient::Data::AlterTable(KuduClient* client,
                                     const AlterTableRequestPB& req,
-                                    const MonoTime& deadline) {
+                                    const MonoTime& deadline,
+                                    bool has_add_drop_partition) {
+  vector<uint32_t> required_feature_flags;
+  if (has_add_drop_partition) {
+    required_feature_flags.push_back(MasterFeatures::ADD_DROP_RANGE_PARTITIONS);
+  }
   AlterTableResponsePB resp;
   Status s =
       SyncLeaderMasterRpc<AlterTableRequestPB, AlterTableResponsePB>(
@@ -446,7 +451,7 @@ Status KuduClient::Data::AlterTable(KuduClient* client,
           &resp,
           "AlterTable",
           &MasterServiceProxy::AlterTable,
-          {});
+          std::move(required_feature_flags));
   RETURN_NOT_OK(s);
   // TODO: Consider the situation where the request is sent to the
   // server, gets executed on the server and written to the server,

http://git-wip-us.apache.org/repos/asf/kudu/blob/232474a5/src/kudu/client/client-internal.h
----------------------------------------------------------------------
diff --git a/src/kudu/client/client-internal.h b/src/kudu/client/client-internal.h
index 6028bb3..86e253a 100644
--- a/src/kudu/client/client-internal.h
+++ b/src/kudu/client/client-internal.h
@@ -91,7 +91,8 @@ class KuduClient::Data {
 
   Status AlterTable(KuduClient* client,
                     const master::AlterTableRequestPB& req,
-                    const MonoTime& deadline);
+                    const MonoTime& deadline,
+                    bool has_add_drop_partition);
 
   Status IsAlterTableInProgress(KuduClient* client,
                                 const std::string& table_name,

http://git-wip-us.apache.org/repos/asf/kudu/blob/232474a5/src/kudu/client/client-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/client/client-test.cc b/src/kudu/client/client-test.cc
index b2e5645..e50a1c8 100644
--- a/src/kudu/client/client-test.cc
+++ b/src/kudu/client/client-test.cc
@@ -1115,11 +1115,11 @@ TEST_F(ClientTest, TestNonCoveringRangePartitions) {
   // cache will execute GetTableLocation RPCs at different partition keys.
 
   ASSERT_NO_FATAL_FAILURE(InsertTestRows(table.get(), 50, 0));
-  client_->data_->meta_cache_->ClearCacheForTesting();
+  client_->data_->meta_cache_->ClearCache();
   ASSERT_NO_FATAL_FAILURE(InsertTestRows(table.get(), 50, 50));
-  client_->data_->meta_cache_->ClearCacheForTesting();
+  client_->data_->meta_cache_->ClearCache();
   ASSERT_NO_FATAL_FAILURE(InsertTestRows(table.get(), 100, 200));
-  client_->data_->meta_cache_->ClearCacheForTesting();
+  client_->data_->meta_cache_->ClearCache();
 
   // Insert out-of-range rows.
   shared_ptr<KuduSession> session = client_->NewSession();
@@ -1135,7 +1135,7 @@ TEST_F(ClientTest, TestNonCoveringRangePartitions) {
   out_of_range_inserts.emplace_back(BuildTestRow(table.get(), 350));
 
   for (auto& insert : out_of_range_inserts) {
-    client_->data_->meta_cache_->ClearCacheForTesting();
+    client_->data_->meta_cache_->ClearCache();
     Status result = session->Apply(insert.release());
     EXPECT_TRUE(result.IsIOError());
     vector<KuduError*> errors;
@@ -1235,7 +1235,7 @@ TEST_F(ClientTest, TestMetaCacheExpiry) {
   auto& meta_cache = client_->data_->meta_cache_;
 
   // Clear the cache.
-  meta_cache->ClearCacheForTesting();
+  meta_cache->ClearCache();
   internal::MetaCacheEntry entry;
   ASSERT_FALSE(meta_cache->LookupTabletByKeyFastPath(client_table_.get(), "", &entry));
 

http://git-wip-us.apache.org/repos/asf/kudu/blob/232474a5/src/kudu/client/client.cc
----------------------------------------------------------------------
diff --git a/src/kudu/client/client.cc b/src/kudu/client/client.cc
index 6c7b967..6999a01 100644
--- a/src/kudu/client/client.cc
+++ b/src/kudu/client/client.cc
@@ -830,23 +830,69 @@ KuduTableAlterer* KuduTableAlterer::RenameTo(const string& new_name) {
 }
 
 KuduColumnSpec* KuduTableAlterer::AddColumn(const string& name) {
-  Data::Step s = {AlterTableRequestPB::ADD_COLUMN,
-                  new KuduColumnSpec(name)};
-  data_->steps_.push_back(s);
+  Data::Step s = { AlterTableRequestPB::ADD_COLUMN,
+                   new KuduColumnSpec(name), nullptr, nullptr };
+  data_->steps_.emplace_back(std::move(s));
   return s.spec;
 }
 
 KuduColumnSpec* KuduTableAlterer::AlterColumn(const string& name) {
-  Data::Step s = {AlterTableRequestPB::ALTER_COLUMN,
-                  new KuduColumnSpec(name)};
-  data_->steps_.push_back(s);
+  Data::Step s = { AlterTableRequestPB::ALTER_COLUMN,
+                   new KuduColumnSpec(name), nullptr, nullptr };
+  data_->steps_.emplace_back(std::move(s));
   return s.spec;
 }
 
 KuduTableAlterer* KuduTableAlterer::DropColumn(const string& name) {
-  Data::Step s = {AlterTableRequestPB::DROP_COLUMN,
-                  new KuduColumnSpec(name)};
-  data_->steps_.push_back(s);
+  Data::Step s = { AlterTableRequestPB::DROP_COLUMN,
+                   new KuduColumnSpec(name), nullptr, nullptr };
+  data_->steps_.emplace_back(std::move(s));
+  return this;
+}
+
+KuduTableAlterer* KuduTableAlterer::AddRangePartition(KuduPartialRow* lower_bound,
+                                                      KuduPartialRow* upper_bound) {
+  if (lower_bound == nullptr || upper_bound == nullptr) {
+    data_->status_ = Status::InvalidArgument("range partition bounds may not be null");
+  }
+  if (!lower_bound->schema()->Equals(*upper_bound->schema())) {
+    data_->status_ = Status::InvalidArgument("range partition bounds must have matching schemas");
+  }
+  if (data_->schema_ == nullptr) {
+    data_->schema_ = lower_bound->schema();
+  } else if (!lower_bound->schema()->Equals(*data_->schema_)) {
+    data_->status_ = Status::InvalidArgument("range partition bounds must have matching schemas");
+  }
+
+  Data::Step s { AlterTableRequestPB::ADD_RANGE_PARTITION,
+                 nullptr,
+                 unique_ptr<KuduPartialRow>(lower_bound),
+                 unique_ptr<KuduPartialRow>(upper_bound) };
+  data_->steps_.emplace_back(std::move(s));
+  data_->has_alter_partitioning_steps = true;
+  return this;
+}
+
+KuduTableAlterer* KuduTableAlterer::DropRangePartition(KuduPartialRow* lower_bound,
+                                                       KuduPartialRow* upper_bound) {
+  if (lower_bound == nullptr || upper_bound == nullptr) {
+    data_->status_ = Status::InvalidArgument("range partition bounds may not be null");
+  }
+  if (!lower_bound->schema()->Equals(*upper_bound->schema())) {
+    data_->status_ = Status::InvalidArgument("range partition bounds must have matching schemas");
+  }
+  if (data_->schema_ == nullptr) {
+    data_->schema_ = lower_bound->schema();
+  } else if (!lower_bound->schema()->Equals(*data_->schema_)) {
+    data_->status_ = Status::InvalidArgument("range partition bounds must have matching schemas");
+  }
+
+  Data::Step s { AlterTableRequestPB::DROP_RANGE_PARTITION,
+                 nullptr,
+                 unique_ptr<KuduPartialRow>(lower_bound),
+                 unique_ptr<KuduPartialRow>(upper_bound) };
+  data_->steps_.emplace_back(std::move(s));
+  data_->has_alter_partitioning_steps = true;
   return this;
 }
 
@@ -869,7 +915,29 @@ Status KuduTableAlterer::Alter() {
     data_->client_->default_admin_operation_timeout();
   MonoTime deadline = MonoTime::Now(MonoTime::FINE);
   deadline.AddDelta(timeout);
-  RETURN_NOT_OK(data_->client_->data_->AlterTable(data_->client_, req, deadline));
+  RETURN_NOT_OK(data_->client_->data_->AlterTable(data_->client_, req, deadline,
+                                                  data_->has_alter_partitioning_steps));
+
+  if (data_->has_alter_partitioning_steps) {
+    // If the table partitions change, clear the local meta cache so that the
+    // new tablets can immediately be written to and scanned, and the old
+    // tablets won't be seen again. This also prevents rows being batched for
+    // the wrong tablet when a partition is dropped and added in the same alter
+    // table transaction. We could clear the meta cache for just the table being
+    // altered or just the partition key ranges being changed, but that would
+    // require opening the table in order to get the ID, schema, and partition
+    // schema.
+    //
+    // It is not necessary to wait for the alteration to be completed before
+    // clearing the cache (i.e. the tablets to be created), because the master
+    // has its soft state updated as part of handling the alter table RPC. When
+    // the meta cache looks up the new tablet locations during a subsequent
+    // write or scan, the master will return a ServiceUnavailable response if
+    // the new tablets are not yet running. The meta cache will automatically
+    // retry after a delay when it encounters this error.
+    data_->client_->data_->meta_cache_->ClearCache();
+  }
+
   if (data_->wait_) {
     string alter_name = data_->rename_to_.get_value_or(data_->table_name_);
     RETURN_NOT_OK(data_->client_->data_->WaitForAlterTableToFinish(

http://git-wip-us.apache.org/repos/asf/kudu/blob/232474a5/src/kudu/client/client.h
----------------------------------------------------------------------
diff --git a/src/kudu/client/client.h b/src/kudu/client/client.h
index 31fb630..625ff3e 100644
--- a/src/kudu/client/client.h
+++ b/src/kudu/client/client.h
@@ -772,6 +772,59 @@ class KUDU_EXPORT KuduTableAlterer {
   /// @return Raw pointer to this alterer object.
   KuduTableAlterer* DropColumn(const std::string& name);
 
+  /// Add a range partition to the table with an inclusive lower bound and
+  /// exclusive upper bound.
+  ///
+  /// @note The table alterer takes ownership of the rows.
+  ///
+  /// @note Multiple range partitions may be added as part of a single alter
+  ///   table transaction by calling this method multiple times on the table
+  ///   alterer.
+  ///
+  /// @note This client may immediately write and scan the new tablets when
+  ///   Alter() returns success, however other existing clients may have to wait
+  ///   for a timeout period to elapse before the tablets become visible. This
+  ///   period is configured by the master's 'table_locations_ttl_ms' flag, and
+  ///   defaults to one hour.
+  ///
+  /// @param [in] lower_bound
+  ///   The inclusive lower bound of the range partition to add. If the row is
+  ///   empty, then the lower bound is unbounded. If any of the columns are
+  ///   unset, the logical minimum value for the column's type will be used by
+  ///   default.
+  /// @param [in] upper_bound
+  ///   The exclusive upper bound of the range partition to add. If the row is
+  ///   empty, then the upper bound is unbounded. If any of the individual
+  ///   columns are unset, the logical minimum value for the column' type will
+  ///   be used by default.
+  /// @return Raw pointer to this alterer object.
+  KuduTableAlterer* AddRangePartition(KuduPartialRow* lower_bound,
+                                      KuduPartialRow* upper_bound);
+
+  /// Drop the range partition from the table with the specified inclusive lower
+  /// bound and exclusive upper bound. The bounds must match an existing range
+  /// partition exactly, and may not span multiple range partitions.
+  ///
+  /// @note The table alterer takes ownership of the rows.
+  ///
+  /// @note Multiple range partitions may be dropped as part of a single alter
+  ///   table transaction by calling this method multiple times on the table
+  ///   alterer.
+  ///
+  /// @param [in] lower_bound
+  ///   The inclusive lower bound of the range partition to drop. If the row is
+  ///   empty, then the lower bound is unbounded. If any of the columns are
+  ///   unset, the logical minimum value for the column's type will be used by
+  ///   default.
+  /// @param [in] upper_bound
+  ///   The exclusive upper bound of the range partition to add. If the row is
+  ///   empty, then the upper bound is unbounded. If any of the individual
+  ///   columns are unset, the logical minimum value for the column' type will
+  ///   be used by default.
+  /// @return Raw pointer to this alterer object.
+  KuduTableAlterer* DropRangePartition(KuduPartialRow* lower_bound,
+                                       KuduPartialRow* upper_bound);
+
   /// Set a timeout for the alteration operation.
   ///
   /// This includes any waiting after the alter has been submitted

http://git-wip-us.apache.org/repos/asf/kudu/blob/232474a5/src/kudu/client/meta_cache.cc
----------------------------------------------------------------------
diff --git a/src/kudu/client/meta_cache.cc b/src/kudu/client/meta_cache.cc
index 92cbe78..0ee1c39 100644
--- a/src/kudu/client/meta_cache.cc
+++ b/src/kudu/client/meta_cache.cc
@@ -604,11 +604,7 @@ void LookupRpc::SendRpc() {
   MetaCacheEntry entry;
   while (PREDICT_TRUE(meta_cache_->LookupTabletByKeyFastPath(table_, partition_key_, &entry))
          && (entry.is_non_covered_range() || entry.tablet()->HasLeader())) {
-    VLOG(4) << "Fast lookup: found " << entry.DebugString(table_)
-            << " for ("
-            << table_->partition_schema()
-                      .PartitionKeyDebugString(partition_key_, *table_->schema().schema_)
-            << ") of " << table_->name();
+    VLOG(4) << "Fast lookup: found " << entry.DebugString(table_) << " for " << ToString();
     if (!entry.is_non_covered_range()) {
       if (remote_tablet_) {
         *remote_tablet_ = entry.tablet();
@@ -618,10 +614,8 @@ void LookupRpc::SendRpc() {
       return;
     }
     if (is_exact_lookup_ || entry.upper_bound_partition_key().empty()) {
-      user_cb_.Run(Status::NotFound(
-            "No tablet covering the requested range partition",
-            table_->partition_schema()
-                   .PartitionKeyDebugString(partition_key_, *table_->schema().schema_)));
+      user_cb_.Run(Status::NotFound("No tablet covering the requested range partition",
+                                    entry.DebugString(table_)));
       delete this;
       return;
     }
@@ -629,10 +623,7 @@ void LookupRpc::SendRpc() {
   }
 
   // Slow path: must lookup the tablet in the master.
-  VLOG(4) << "Fast lookup: no cache entry"
-          << " for (" << table_->partition_schema()
-                                .PartitionKeyDebugString(partition_key_, *table_->schema().schema_)
-          << ") of " << table_->name()
+  VLOG(4) << "Fast lookup: no cache entry for " << ToString()
           << ": refreshing our metadata from the Master";
 
   if (!has_permit_) {
@@ -672,8 +663,9 @@ void LookupRpc::SendRpc() {
 string LookupRpc::ToString() const {
   return Substitute("GetTableLocations { table: '$0', partition-key: ($1), attempt: $2 }",
                     table_->name(),
-                    table_->partition_schema()
-                           .PartitionKeyDebugString(partition_key_, *table_->schema().schema_),
+                    (partition_key_.empty() ? "<start>" :
+                     table_->partition_schema()
+                            .PartitionKeyDebugString(partition_key_, *table_->schema().schema_)),
                     num_attempts());
 }
 
@@ -761,10 +753,8 @@ void LookupRpc::SendRpcCb(const Status& status) {
     MetaCacheEntry entry;
     new_status = meta_cache_->ProcessLookupResponse(*this, &entry);
     if (entry.is_non_covered_range()) {
-      new_status = Status::NotFound(
-          "No tablet covering the requested range partition",
-          table_->partition_schema()
-                 .PartitionKeyDebugString(partition_key_, *table_->schema().schema_));
+      new_status = Status::NotFound("No tablet covering the requested range partition",
+                                    entry.DebugString(table_));
     } else if (remote_tablet_) {
       *remote_tablet_ = entry.tablet();
     }
@@ -949,9 +939,9 @@ bool MetaCache::LookupTabletByKeyFastPath(const KuduTable* table,
   return false;
 }
 
-void MetaCache::ClearCacheForTesting() {
+void MetaCache::ClearCache() {
   VLOG(3) << "Clearing cache";
-  shared_lock<rw_spinlock> l(lock_);
+  std::lock_guard<rw_spinlock> l(lock_);
   STLDeleteValues(&ts_cache_);
   tablets_by_id_.clear();
   tablets_by_table_and_key_.clear();

http://git-wip-us.apache.org/repos/asf/kudu/blob/232474a5/src/kudu/client/meta_cache.h
----------------------------------------------------------------------
diff --git a/src/kudu/client/meta_cache.h b/src/kudu/client/meta_cache.h
index 4dddfc4..1f93440 100644
--- a/src/kudu/client/meta_cache.h
+++ b/src/kudu/client/meta_cache.h
@@ -58,7 +58,6 @@ namespace client {
 
 class ClientTest_TestMasterLookupPermits_Test;
 class ClientTest_TestMetaCacheExpiry_Test;
-class ClientTest_TestNonCoveringRangePartitions_Test;
 class KuduClient;
 class KuduTable;
 
@@ -375,6 +374,9 @@ class MetaCache : public RefCountedThreadSafe<MetaCache> {
                                scoped_refptr<RemoteTablet>* remote_tablet,
                                const StatusCallback& callback);
 
+  // Clears the meta cache.
+  void ClearCache();
+
   // Mark any replicas of any tablets hosted by 'ts' as failed. They will
   // not be returned in future cache lookups.
   void MarkTSFailed(RemoteTabletServer* ts, const Status& status);
@@ -391,7 +393,6 @@ class MetaCache : public RefCountedThreadSafe<MetaCache> {
 
   FRIEND_TEST(client::ClientTest, TestMasterLookupPermits);
   FRIEND_TEST(client::ClientTest, TestMetaCacheExpiry);
-  FRIEND_TEST(client::ClientTest, TestNonCoveringRangePartitions);
 
   // Called on the slow LookupTablet path when the master responds. Populates
   // the tablet caches and returns a reference to the first one.
@@ -403,9 +404,6 @@ class MetaCache : public RefCountedThreadSafe<MetaCache> {
                                  const std::string& partition_key,
                                  MetaCacheEntry* entry);
 
-  // Clears the meta cache for testing purposes.
-  void ClearCacheForTesting();
-
   // Update our information about the given tablet server.
   //
   // This is called when we get some response from the master which contains

http://git-wip-us.apache.org/repos/asf/kudu/blob/232474a5/src/kudu/client/table_alterer-internal.cc
----------------------------------------------------------------------
diff --git a/src/kudu/client/table_alterer-internal.cc b/src/kudu/client/table_alterer-internal.cc
index 178bb8f..3eab03d 100644
--- a/src/kudu/client/table_alterer-internal.cc
+++ b/src/kudu/client/table_alterer-internal.cc
@@ -21,6 +21,7 @@
 
 #include "kudu/client/schema.h"
 #include "kudu/client/schema-internal.h"
+#include "kudu/common/row_operations.h"
 #include "kudu/common/wire_protocol.h"
 #include "kudu/master/master.pb.h"
 
@@ -35,7 +36,8 @@ using master::AlterTableRequestPB_AlterColumn;
 KuduTableAlterer::Data::Data(KuduClient* client, string name)
     : client_(client),
       table_name_(std::move(name)),
-      wait_(true) {
+      wait_(true),
+      schema_(nullptr) {
 }
 
 KuduTableAlterer::Data::~Data() {
@@ -49,8 +51,7 @@ Status KuduTableAlterer::Data::ToRequest(AlterTableRequestPB* req) {
     return status_;
   }
 
-  if (!rename_to_.is_initialized() &&
-      steps_.empty()) {
+  if (!rename_to_.is_initialized() && steps_.empty()) {
     return Status::InvalidArgument("No alter steps provided");
   }
 
@@ -60,6 +61,10 @@ Status KuduTableAlterer::Data::ToRequest(AlterTableRequestPB* req) {
     req->set_new_table_name(rename_to_.get());
   }
 
+  if (schema_ != nullptr) {
+    RETURN_NOT_OK(SchemaToPBWithoutIds(*schema_, req->mutable_schema()));
+  }
+
   for (const Step& s : steps_) {
     AlterTableRequestPB::Step* pb_step = req->add_alter_schema_steps();
     pb_step->set_type(s.step_type);
@@ -102,11 +107,26 @@ Status KuduTableAlterer::Data::ToRequest(AlterTableRequestPB* req) {
         pb_step->mutable_rename_column()->set_new_name(s.spec->data_->rename_to);
         pb_step->set_type(AlterTableRequestPB::RENAME_COLUMN);
         break;
+      case AlterTableRequestPB::ADD_RANGE_PARTITION:
+      {
+        RowOperationsPBEncoder encoder(pb_step->mutable_add_range_partition()
+                                              ->mutable_range_bounds());
+        encoder.Add(RowOperationsPB::RANGE_LOWER_BOUND, *s.lower_bound);
+        encoder.Add(RowOperationsPB::RANGE_UPPER_BOUND, *s.upper_bound);
+        break;
+      }
+      case AlterTableRequestPB::DROP_RANGE_PARTITION:
+      {
+        RowOperationsPBEncoder encoder(pb_step->mutable_drop_range_partition()
+                                              ->mutable_range_bounds());
+        encoder.Add(RowOperationsPB::RANGE_LOWER_BOUND, *s.lower_bound);
+        encoder.Add(RowOperationsPB::RANGE_UPPER_BOUND, *s.upper_bound);
+        break;
+      }
       default:
-        LOG(FATAL) << "unknown step type " << s.step_type;
+        LOG(FATAL) << "unknown step type " << AlterTableRequestPB::StepType_Name(s.step_type);
     }
   }
-
   return Status::OK();
 }
 

http://git-wip-us.apache.org/repos/asf/kudu/blob/232474a5/src/kudu/client/table_alterer-internal.h
----------------------------------------------------------------------
diff --git a/src/kudu/client/table_alterer-internal.h b/src/kudu/client/table_alterer-internal.h
index aa0c641..4188baa 100644
--- a/src/kudu/client/table_alterer-internal.h
+++ b/src/kudu/client/table_alterer-internal.h
@@ -18,10 +18,12 @@
 #define KUDU_CLIENT_TABLE_ALTERER_INTERNAL_H
 
 #include <boost/optional.hpp>
+#include <memory>
 #include <string>
 #include <vector>
 
 #include "kudu/client/client.h"
+#include "kudu/common/schema.h"
 #include "kudu/master/master.pb.h"
 #include "kudu/util/status.h"
 
@@ -48,8 +50,14 @@ class KuduTableAlterer::Data {
   struct Step {
     master::AlterTableRequestPB::StepType step_type;
 
-    // Owned by KuduTableAlterer::Data.
+    // Owned by KuduTableAlterer::Data. Only set when the StepType is
+    // [ADD|DROP|RENAME|ALTER]_COLUMN.
     KuduColumnSpec *spec;
+
+    // Lower and upper bound partition keys. Only set when the StepType is
+    // [ADD|DROP]_RANGE_PARTITION.
+    std::unique_ptr<KuduPartialRow> lower_bound;
+    std::unique_ptr<KuduPartialRow> upper_bound;
   };
   std::vector<Step> steps_;
 
@@ -59,6 +67,12 @@ class KuduTableAlterer::Data {
 
   boost::optional<std::string> rename_to_;
 
+  // Set to true if there are alter partition steps.
+  bool has_alter_partitioning_steps = false;
+
+  // Schema of add/drop range partition bound rows.
+  const Schema* schema_;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(Data);
 };

http://git-wip-us.apache.org/repos/asf/kudu/blob/232474a5/src/kudu/common/partition.cc
----------------------------------------------------------------------
diff --git a/src/kudu/common/partition.cc b/src/kudu/common/partition.cc
index 9991398..d1a7733 100644
--- a/src/kudu/common/partition.cc
+++ b/src/kudu/common/partition.cc
@@ -26,6 +26,7 @@
 #include "kudu/common/partial_row.h"
 #include "kudu/common/wire_protocol.pb.h"
 #include "kudu/gutil/map-util.h"
+#include "kudu/gutil/strings/escaping.h"
 #include "kudu/gutil/strings/join.h"
 #include "kudu/gutil/strings/substitute.h"
 #include "kudu/util/hash_util.h"
@@ -496,7 +497,8 @@ Status PartitionSchema::DecodeRangeKey(Slice* encoded_key,
     BitmapSet(row->isset_bitmap_, column_idx);
   }
   if (!encoded_key->empty()) {
-    return Status::InvalidArgument("unable to fully decode partition key range components");
+    return Status::InvalidArgument("unable to fully decode range key",
+                                   CHexEscape(encoded_key->ToString()));
   }
   return Status::OK();
 }

http://git-wip-us.apache.org/repos/asf/kudu/blob/232474a5/src/kudu/integration-tests/alter_table-randomized-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/integration-tests/alter_table-randomized-test.cc b/src/kudu/integration-tests/alter_table-randomized-test.cc
index 79a65ff..3560f48 100644
--- a/src/kudu/integration-tests/alter_table-randomized-test.cc
+++ b/src/kudu/integration-tests/alter_table-randomized-test.cc
@@ -15,17 +15,20 @@
 // specific language governing permissions and limitations
 // under the License.
 
-
 #include <algorithm>
 #include <map>
+#include <memory>
+#include <string>
 #include <vector>
 
 #include "kudu/client/client-test-util.h"
 #include "kudu/gutil/map-util.h"
 #include "kudu/gutil/stl_util.h"
+#include "kudu/gutil/strings/join.h"
 #include "kudu/gutil/strings/substitute.h"
 #include "kudu/integration-tests/cluster_verifier.h"
 #include "kudu/integration-tests/external_mini_cluster.h"
+#include "kudu/util/net/sockaddr.h"
 #include "kudu/util/random.h"
 #include "kudu/util/random_util.h"
 #include "kudu/util/test_util.h"
@@ -37,6 +40,7 @@ using client::KuduClientBuilder;
 using client::KuduColumnSchema;
 using client::KuduError;
 using client::KuduInsert;
+using client::KuduScanner;
 using client::KuduSchema;
 using client::KuduSchemaBuilder;
 using client::KuduSession;
@@ -49,15 +53,18 @@ using client::sp::shared_ptr;
 using std::make_pair;
 using std::map;
 using std::pair;
+using std::string;
+using std::unique_ptr;
 using std::vector;
 using strings::SubstituteAndAppend;
 
 const char* kTableName = "test-table";
 const int kMaxColumns = 30;
+const uint32_t kMaxRangePartitions = 32;
 
 class AlterTableRandomized : public KuduTest {
  public:
-  virtual void SetUp() OVERRIDE {
+  void SetUp() override {
     KuduTest::SetUp();
 
     ExternalMiniClusterOptions opts;
@@ -76,7 +83,7 @@ class AlterTableRandomized : public KuduTest {
     ASSERT_OK(cluster_->CreateClient(builder, &client_));
   }
 
-  virtual void TearDown() OVERRIDE {
+  void TearDown() override {
     cluster_->Shutdown();
     KuduTest::TearDown();
   }
@@ -86,11 +93,21 @@ class AlterTableRandomized : public KuduTest {
     cluster_->tablet_server(idx)->Shutdown();
     CHECK_OK(cluster_->tablet_server(idx)->Restart());
     CHECK_OK(cluster_->WaitForTabletsRunning(cluster_->tablet_server(idx),
-        -1, MonoDelta::FromSeconds(60)));
+                                             -1, MonoDelta::FromSeconds(60)));
+    LOG(INFO) << "TS " << idx << " Restarted";
+  }
+
+  void RestartMaster() {
+    LOG(INFO) << "Restarting Master";
+    cluster_->master()->Shutdown();
+    CHECK_OK(cluster_->master()->Restart());
+    CHECK_OK(cluster_->master()->WaitForCatalogManager());
+    CHECK_OK(cluster_->WaitForTabletServerCount(3, MonoDelta::FromSeconds(60)));
+    LOG(INFO) << "Master Restarted";
   }
 
  protected:
-  gscoped_ptr<ExternalMiniCluster> cluster_;
+  unique_ptr<ExternalMiniCluster> cluster_;
   shared_ptr<KuduClient> client_;
 };
 
@@ -99,13 +116,12 @@ struct RowState {
   // We ensure that we never insert or update to this value except in the case of
   // NULLable columns.
   static const int32_t kNullValue = 0xdeadbeef;
-  vector<pair<string, int32_t> > cols;
+  vector<pair<string, int32_t>> cols;
 
   string ToString() const {
     string ret = "(";
-    typedef pair<string, int32_t> entry;
     bool first = true;
-    for (const entry& e : cols) {
+    for (const auto& e : cols) {
       if (!first) {
         ret.append(", ");
       }
@@ -122,20 +138,58 @@ struct RowState {
 };
 
 struct TableState {
-  TableState() {
+  TableState()
+      : rand_(SeedRandom()) {
     col_names_.push_back("key");
     col_nullable_.push_back(false);
+    AddRangePartition();
+  }
+
+  int32_t GetRandomNewRowKey() {
+    CHECK(!range_partitions_.empty());
+    while (true) {
+      auto partition = range_partitions_.begin();
+      std::advance(partition, rand_.Uniform(range_partitions_.size()));
+
+      int32_t rowkey = partition->first + rand_.Uniform(partition->second - partition->first);
+      if (!ContainsKey(rows_, rowkey)) {
+        return rowkey;
+      }
+    }
   }
 
-  ~TableState() {
-    STLDeleteValues(&rows_);
+  string GetRandomNewColumnName() {
+    while (true) {
+      string name = strings::Substitute("c$0", rand_.Uniform(1000));
+      if (std::find(col_names_.begin(), col_names_.end(), name) == col_names_.end()) {
+        return name;
+      }
+    }
+  }
+
+  // Returns the name of a random column not in the primary key.
+  string GetRandomExistingColumnName() {
+    CHECK(col_names_.size() > 1);
+    return col_names_[1 + rand_.Uniform(col_names_.size() - 1)];
+  }
+
+  int32_t GetRandomExistingRowKey() {
+    CHECK(!rows_.empty());
+
+    auto row = rows_.begin();
+    std::advance(row, rand_.Uniform(rows_.size()));
+    return row->first;
   }
 
-  void GenRandomRow(int32_t key, int32_t seed,
-                    vector<pair<string, int32_t> >* row) {
+  // Generates a random row.
+  void GenRandomRow(vector<pair<string, int32_t>>* row) {
+    int32_t key = GetRandomNewRowKey();
+
+    int32_t seed = rand_.Next();
     if (seed == RowState::kNullValue) {
       seed++;
     }
+
     row->clear();
     row->push_back(make_pair("key", key));
     for (int i = 1; i < col_names_.size(); i++) {
@@ -149,37 +203,35 @@ struct TableState {
     }
   }
 
-  bool Insert(const vector<pair<string, int32_t> >& data) {
+  bool Insert(const vector<pair<string, int32_t>>& data) {
     DCHECK_EQ("key", data[0].first);
     int32_t key = data[0].second;
     if (ContainsKey(rows_, key)) return false;
 
     auto r = new RowState;
     r->cols = data;
-    rows_[key] = r;
+    rows_[key].reset(r);
     return true;
   }
 
-  bool Update(const vector<pair<string, int32_t> >& data) {
+  bool Update(const vector<pair<string, int32_t>>& data) {
     DCHECK_EQ("key", data[0].first);
     int32_t key = data[0].second;
     if (!ContainsKey(rows_, key)) return false;
 
-    RowState* r = rows_[key];
-    r->cols = data;
+    rows_[key]->cols = data;
     return true;
   }
 
   void Delete(int32_t row_key) {
-    RowState* r = EraseKeyReturnValuePtr(&rows_, row_key);
+    unique_ptr<RowState> r = EraseKeyReturnValuePtr(&rows_, row_key);
     CHECK(r) << "row key " << row_key << " not found";
-    delete r;
   }
 
   void AddColumnWithDefault(const string& name, int32_t def, bool nullable) {
     col_names_.push_back(name);
     col_nullable_.push_back(nullable);
-    for (entry& e : rows_) {
+    for (auto& e : rows_) {
       e.second->cols.push_back(make_pair(name, def));
     }
   }
@@ -189,24 +241,49 @@ struct TableState {
     int index = col_it - col_names_.begin();
     col_names_.erase(col_it);
     col_nullable_.erase(col_nullable_.begin() + index);
-    for (entry& e : rows_) {
+    for (auto& e : rows_) {
       e.second->cols.erase(e.second->cols.begin() + index);
     }
   }
 
-  int32_t GetRandomRowKey(int32_t rand) {
-    CHECK(!rows_.empty());
-    int idx = rand % rows_.size();
-    map<int32_t, RowState*>::const_iterator it = rows_.begin();
-    for (int i = 0; i < idx; i++) {
-      ++it;
+  void RenameColumn(const string& existing_name, string new_name) {
+    auto iter = std::find(col_names_.begin(), col_names_.end(), existing_name);
+    CHECK(iter != col_names_.end());
+    *iter = std::move(new_name);
+  }
+
+  pair<int32_t, int32_t> AddRangePartition() {
+    CHECK(range_partitions_.size() < kMaxRangePartitions);
+    while (true) {
+      uint32_t width = INT32_MAX / kMaxRangePartitions;
+      int32_t lower_bound = width * rand_.Uniform(kMaxRangePartitions);
+      int32_t upper_bound = lower_bound + width;
+      CHECK(upper_bound > lower_bound);
+
+      if (InsertIfNotPresent(&range_partitions_, make_pair(lower_bound, upper_bound))) {
+        return make_pair(lower_bound, upper_bound);
+      }
     }
-    return it->first;
+  }
+
+  pair<int32_t, int32_t> DropRangePartition() {
+    CHECK(!range_partitions_.empty());
+
+    auto partition = range_partitions_.begin();
+    std::advance(partition, rand_.Uniform(range_partitions_.size()));
+
+    int32_t lower_bound = partition->first;
+    int32_t upper_bound = partition->second;
+
+    range_partitions_.erase(partition);
+
+    rows_.erase(rows_.lower_bound(lower_bound), rows_.lower_bound(upper_bound));
+    return make_pair(lower_bound, upper_bound);
   }
 
   void ToStrings(vector<string>* strs) {
     strs->clear();
-    for (const entry& e : rows_) {
+    for (const auto& e : rows_) {
       strs->push_back(e.second->ToString());
     }
   }
@@ -218,8 +295,12 @@ struct TableState {
   // Has the same length as col_names_.
   vector<bool> col_nullable_;
 
-  typedef pair<const int32_t, RowState*> entry;
-  map<int32_t, RowState*> rows_;
+  map<int32_t, unique_ptr<RowState>> rows_;
+
+  // The lower and upper bounds of all range partitions in the table.
+  map<int32_t, int32_t> range_partitions_;
+
+  Random rand_;
 };
 
 struct MirrorTable {
@@ -231,33 +312,39 @@ struct MirrorTable {
     KuduSchemaBuilder b;
     b.AddColumn("key")->Type(KuduColumnSchema::INT32)->NotNull()->PrimaryKey();
     CHECK_OK(b.Build(&schema));
-    gscoped_ptr<KuduTableCreator> table_creator(client_->NewTableCreator());
-    RETURN_NOT_OK(table_creator->table_name(kTableName)
-             .schema(&schema)
-             .set_range_partition_columns({ "key" })
-             .num_replicas(3)
-             .Create());
-    return Status::OK();
-  }
-
-  bool TryInsert(int32_t row_key, int32_t rand) {
-    vector<pair<string, int32_t> > row;
-    ts_.GenRandomRow(row_key, rand, &row);
+    unique_ptr<KuduTableCreator> table_creator(client_->NewTableCreator());
+    table_creator->table_name(kTableName)
+                  .schema(&schema)
+                  .set_range_partition_columns({ "key" })
+                  .num_replicas(3);
+
+    for (const auto& partition : ts_.range_partitions_) {
+      unique_ptr<KuduPartialRow> lower(schema.NewRow());
+      unique_ptr<KuduPartialRow> upper(schema.NewRow());
+      RETURN_NOT_OK(lower->SetInt32("key", partition.first));
+      RETURN_NOT_OK(upper->SetInt32("key", partition.second));
+      table_creator->add_range_bound(lower.release(), upper.release());
+    }
+
+    return table_creator->Create();
+  }
+
+  void InsertRandomRow() {
+    vector<pair<string, int32_t>> row;
+    ts_.GenRandomRow(&row);
     Status s = DoRealOp(row, INSERT);
     if (s.IsAlreadyPresent()) {
       CHECK(!ts_.Insert(row)) << "real table said already-present, fake table succeeded";
-      return false;
     }
     CHECK_OK(s);
 
     CHECK(ts_.Insert(row));
-    return true;
   }
 
-  void DeleteRandomRow(uint32_t rand) {
+  void DeleteRandomRow() {
     if (ts_.rows_.empty()) return;
-    int32_t row_key = ts_.GetRandomRowKey(rand);
-    vector<pair<string, int32_t> > del;
+    int32_t row_key = ts_.GetRandomExistingRowKey();
+    vector<pair<string, int32_t>> del;
     del.push_back(make_pair("key", row_key));
     CHECK_OK(DoRealOp(del, DELETE));
 
@@ -266,9 +353,9 @@ struct MirrorTable {
 
   void UpdateRandomRow(uint32_t rand) {
     if (ts_.rows_.empty()) return;
-    int32_t row_key = ts_.GetRandomRowKey(rand);
+    int32_t row_key = ts_.GetRandomExistingRowKey();
 
-    vector<pair<string, int32_t> > update;
+    vector<pair<string, int32_t>> update;
     update.push_back(make_pair("key", row_key));
     for (int i = 1; i < num_columns(); i++) {
       int32_t val = rand * i;
@@ -294,59 +381,135 @@ struct MirrorTable {
     CHECK(ts_.Update(update));
   }
 
-  void AddAColumn(const string& name) {
+  void RandomAlterTable() {
+    LOG(INFO) << "Beginning Alterations";
+
+    // This schema must outlive the table alterer, since the rows in add/drop
+    // range partition hold a pointer to it.
+    KuduSchema schema;
+    CHECK_OK(client_->GetTableSchema(kTableName, &schema));
+    unique_ptr<KuduTableAlterer> table_alterer(client_->NewTableAlterer(kTableName));
+
+    int step_count = 1 + ts_.rand_.Uniform(10);
+    for (int step = 0; step < step_count; step++) {
+      int r = ts_.rand_.Uniform(4);
+      if (r < 1 && num_columns() < kMaxColumns) {
+        AddAColumn(table_alterer.get());
+      } else if (r < 2 && num_columns() > 1) {
+        DropAColumn(table_alterer.get());
+      } else if (num_range_partitions() == 0 ||
+                 (r < 3 && num_range_partitions() < kMaxRangePartitions)) {
+        AddARangePartition(schema, table_alterer.get());
+      } else {
+        DropARangePartition(schema, table_alterer.get());
+      }
+    }
+    LOG(INFO) << "Committing Alterations";
+    CHECK_OK(table_alterer->Alter());
+  }
+
+  void AddAColumn(KuduTableAlterer* table_alterer) {
+    string name = ts_.GetRandomNewColumnName();
+    LOG(INFO) << "Adding column " << name << ", existing columns: "
+              << JoinStrings(ts_.col_names_, ", ");
+
     int32_t default_value = rand();
     bool nullable = rand() % 2 == 1;
 
     // Add to the real table.
-    gscoped_ptr<KuduTableAlterer> table_alterer(client_->NewTableAlterer(kTableName));
-
     if (nullable) {
       default_value = RowState::kNullValue;
       table_alterer->AddColumn(name)->Type(KuduColumnSchema::INT32);
     } else {
       table_alterer->AddColumn(name)->Type(KuduColumnSchema::INT32)->NotNull()
-        ->Default(KuduValue::FromInt(default_value));
+                   ->Default(KuduValue::FromInt(default_value));
     }
-    ASSERT_OK(table_alterer->Alter());
 
     // Add to the mirror state.
     ts_.AddColumnWithDefault(name, default_value, nullable);
   }
 
-  void DropAColumn(const string& name) {
-    gscoped_ptr<KuduTableAlterer> table_alterer(client_->NewTableAlterer(kTableName));
-    CHECK_OK(table_alterer->DropColumn(name)->Alter());
+  void DropAColumn(KuduTableAlterer* table_alterer) {
+    string name = ts_.GetRandomExistingColumnName();
+    LOG(INFO) << "Dropping column " << name << ", existing columns: "
+              << JoinStrings(ts_.col_names_, ", ");
+    table_alterer->DropColumn(name);
     ts_.DropColumn(name);
   }
 
-  void DropRandomColumn(int seed) {
-    if (num_columns() == 1) return;
+  void RenameAColumn(KuduTableAlterer* table_alterer) {
+    string original_name = ts_.GetRandomExistingColumnName();
+    string new_name = ts_.GetRandomNewColumnName();
+    LOG(INFO) << "Renaming column " << original_name << " to " << new_name;
+    table_alterer->AlterColumn(original_name)->RenameTo(new_name);
+    ts_.RenameColumn(original_name, std::move(new_name));
+  }
+
+  void AddARangePartition(KuduSchema& schema, KuduTableAlterer* table_alterer) {
+    auto bounds = ts_.AddRangePartition();
+    LOG(INFO) << "Adding range partition: [" << bounds.first << ", " << bounds.second << ")"
+              << " resulting partitions: ("
+              << JoinKeysAndValuesIterator(ts_.range_partitions_.begin(),
+                                           ts_.range_partitions_.end(),
+                                           ", ", "], (") << ")";
 
-    string name = ts_.col_names_[1 + (seed % (num_columns() - 1))];
-    DropAColumn(name);
+    unique_ptr<KuduPartialRow> lower_bound(schema.NewRow());
+    CHECK_OK(lower_bound->SetInt32("key", bounds.first));
+    unique_ptr<KuduPartialRow> upper_bound(schema.NewRow());
+    CHECK_OK(upper_bound->SetInt32("key", bounds.second));
+
+    table_alterer->AddRangePartition(lower_bound.release(), upper_bound.release());
+  }
+
+  void DropARangePartition(KuduSchema& schema, KuduTableAlterer* table_alterer) {
+    auto bounds = ts_.DropRangePartition();
+    LOG(INFO) << "Dropping range partition: [" << bounds.first << ", " << bounds.second << ")"
+              << " resulting partitions: ("
+              << JoinKeysAndValuesIterator(ts_.range_partitions_.begin(),
+                                           ts_.range_partitions_.end(),
+                                           ", ", "], (") << ")";
+
+    unique_ptr<KuduPartialRow> lower_bound(schema.NewRow());
+    CHECK_OK(lower_bound->SetInt32("key", bounds.first));
+    unique_ptr<KuduPartialRow> upper_bound(schema.NewRow());
+    CHECK_OK(upper_bound->SetInt32("key", bounds.second));
+
+    table_alterer->DropRangePartition(lower_bound.release(), upper_bound.release());
   }
 
   int num_columns() const {
     return ts_.col_names_.size();
   }
 
+  int num_rows() const {
+    return ts_.rows_.size();
+  }
+
+  int num_range_partitions() const {
+    return ts_.range_partitions_.size();
+  }
+
   void Verify() {
+    LOG(INFO) << "Verifying " << ts_.rows_.size() << " rows in "
+              << ts_.range_partitions_.size() << " tablets";
     // First scan the real table
     vector<string> rows;
     {
       shared_ptr<KuduTable> table;
       CHECK_OK(client_->OpenTable(kTableName, &table));
-      client::ScanTableToStrings(table.get(), &rows);
+      KuduScanner scanner(table.get());
+      ASSERT_OK(scanner.SetSelection(KuduClient::LEADER_ONLY));
+      ASSERT_OK(scanner.SetFaultTolerant());
+      scanner.SetTimeoutMillis(60000);
+      NO_FATALS(ScanToStrings(&scanner, &rows));
     }
-    std::sort(rows.begin(), rows.end());
 
     // Then get our mock table.
     vector<string> expected;
     ts_.ToStrings(&expected);
 
     // They should look the same.
-    ASSERT_EQ(rows, expected);
+    ASSERT_EQ(expected, rows);
   }
 
  private:
@@ -354,14 +517,14 @@ struct MirrorTable {
     INSERT, UPDATE, DELETE
   };
 
-  Status DoRealOp(const vector<pair<string, int32_t> >& data,
-                  OpType op_type) {
+  Status DoRealOp(const vector<pair<string, int32_t>>& data, OpType op_type) {
+    VLOG(1) << "Applying op " << op_type << " " << data[0].first << ": " << data[0].second;
     shared_ptr<KuduSession> session = client_->NewSession();
     shared_ptr<KuduTable> table;
     RETURN_NOT_OK(session->SetFlushMode(KuduSession::MANUAL_FLUSH));
     session->SetTimeoutMillis(15 * 1000);
     RETURN_NOT_OK(client_->OpenTable(kTableName, &table));
-    gscoped_ptr<KuduWriteOperation> op;
+    unique_ptr<KuduWriteOperation> op;
     switch (op_type) {
       case INSERT: op.reset(table->NewInsert()); break;
       case UPDATE: op.reset(table->NewUpdate()); break;
@@ -414,21 +577,21 @@ TEST_F(AlterTableRandomized, TestRandomSequence) {
     // Perform different operations with varying probability.
     // We mostly insert and update, with occasional deletes,
     // and more occasional table alterations or restarts.
+
     int r = rng.Uniform(1000);
-    if (r < 400) {
-      t.TryInsert(1000000 + rng.Uniform(1000000), rng.Next());
-    } else if (r < 600) {
-      t.UpdateRandomRow(rng.Next());
-    } else if (r < 920) {
-      t.DeleteRandomRow(rng.Next());
-    } else if (r < 970) {
-      if (t.num_columns() < kMaxColumns) {
-        t.AddAColumn(strings::Substitute("c$0", i));
-      }
-    } else if (r < 995) {
-      t.DropRandomColumn(rng.Next());
-    } else {
+
+    if (r < 3) {
+      RestartMaster();
+    } else if (r < 10) {
       RestartTabletServer(rng.Uniform(cluster_->num_tablet_servers()));
+    } else if (r < 35 || t.num_range_partitions() == 0) {
+      t.RandomAlterTable();
+    } else if (r < 500) {
+      t.InsertRandomRow();
+    } else if (r < 750) {
+      t.DeleteRandomRow();
+    } else {
+      t.UpdateRandomRow(rng.Next());
     }
 
     if (i % 1000 == 0) {
@@ -440,6 +603,7 @@ TEST_F(AlterTableRandomized, TestRandomSequence) {
 
   // Not only should the data returned by a scanner match what we expect,
   // we also expect all of the replicas to agree with each other.
+  LOG(INFO) << "Verifying cluster";
   ClusterVerifier v(cluster_.get());
   NO_FATALS(v.CheckCluster());
 }

http://git-wip-us.apache.org/repos/asf/kudu/blob/232474a5/src/kudu/integration-tests/alter_table-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/integration-tests/alter_table-test.cc b/src/kudu/integration-tests/alter_table-test.cc
index 85321ff..c91f4c9 100644
--- a/src/kudu/integration-tests/alter_table-test.cc
+++ b/src/kudu/integration-tests/alter_table-test.cc
@@ -19,22 +19,23 @@
 #include <gflags/gflags.h>
 #include <gtest/gtest.h>
 #include <map>
+#include <memory>
 #include <string>
 #include <utility>
 
 #include "kudu/client/client.h"
 #include "kudu/client/client-test-util.h"
 #include "kudu/client/row_result.h"
+#include "kudu/client/scan_batch.h"
 #include "kudu/client/schema.h"
-#include "kudu/gutil/gscoped_ptr.h"
 #include "kudu/gutil/stl_util.h"
 #include "kudu/gutil/strings/join.h"
 #include "kudu/gutil/strings/substitute.h"
 #include "kudu/integration-tests/mini_cluster.h"
-#include "kudu/master/mini_master.h"
 #include "kudu/master/master.h"
 #include "kudu/master/master.pb.h"
 #include "kudu/master/master-test-util.h"
+#include "kudu/master/mini_master.h"
 #include "kudu/server/hybrid_clock.h"
 #include "kudu/tablet/tablet_peer.h"
 #include "kudu/tserver/mini_tablet_server.h"
@@ -54,12 +55,14 @@ DECLARE_bool(use_hybrid_clock);
 
 namespace kudu {
 
+using client::CountTableRows;
 using client::KuduClient;
 using client::KuduClientBuilder;
 using client::KuduColumnSchema;
 using client::KuduError;
 using client::KuduInsert;
 using client::KuduRowResult;
+using client::KuduScanBatch;
 using client::KuduScanner;
 using client::KuduSchema;
 using client::KuduSchemaBuilder;
@@ -75,6 +78,7 @@ using master::AlterTableResponsePB;
 using master::MiniMaster;
 using std::map;
 using std::pair;
+using std::unique_ptr;
 using std::vector;
 using tablet::TabletPeer;
 using tserver::MiniTabletServer;
@@ -98,7 +102,7 @@ class AlterTableTest : public KuduTest {
                          "safe to change at runtime");
   }
 
-  virtual void SetUp() OVERRIDE {
+  void SetUp() override {
     // Make heartbeats faster to speed test runtime.
     FLAGS_heartbeat_interval_ms = 10;
 
@@ -110,9 +114,9 @@ class AlterTableTest : public KuduTest {
     ASSERT_OK(cluster_->Start());
 
     CHECK_OK(KuduClientBuilder()
-             .add_master_server_addr(cluster_->mini_master()->bound_rpc_addr_str())
-             .default_admin_operation_timeout(MonoDelta::FromSeconds(60))
-             .Build(&client_));
+        .add_master_server_addr(cluster_->mini_master()->bound_rpc_addr_str())
+        .default_admin_operation_timeout(MonoDelta::FromSeconds(60))
+        .Build(&client_));
 
     // Add a table, make sure it reports itself.
     gscoped_ptr<KuduTableCreator> table_creator(client_->NewTableCreator());
@@ -129,7 +133,7 @@ class AlterTableTest : public KuduTest {
     LOG(INFO) << "Tablet successfully located";
   }
 
-  virtual void TearDown() OVERRIDE {
+  void TearDown() override {
     tablet_peer_.reset();
     cluster_->Shutdown();
   }
@@ -209,6 +213,8 @@ class AlterTableTest : public KuduTest {
 
   void InsertRows(int start_row, int num_rows);
 
+  Status InsertRowsSequential(const string& table_name, int start_row, int num_rows);
+
   void UpdateRow(int32_t row_key, const map<string, int32_t>& updates);
 
   void ScanToStrings(vector<string>* rows);
@@ -233,6 +239,12 @@ class AlterTableTest : public KuduTest {
         .Create();
   }
 
+  Status CreateTable(const string& table_name,
+                     const KuduSchema& schema,
+                     const vector<string>& range_partition_columns,
+                     vector<unique_ptr<KuduPartialRow>> split_rows,
+                     vector<pair<unique_ptr<KuduPartialRow>, unique_ptr<KuduPartialRow>>> bounds);
+
  protected:
   virtual int num_replicas() const { return 1; }
 
@@ -441,6 +453,35 @@ void AlterTableTest::InsertRows(int start_row, int num_rows) {
   FlushSessionOrDie(session);
 }
 
+Status AlterTableTest::InsertRowsSequential(const string& table_name, int start_row, int num_rows) {
+  shared_ptr<KuduSession> session = client_->NewSession();
+  shared_ptr<KuduTable> table;
+  RETURN_NOT_OK(session->SetFlushMode(KuduSession::MANUAL_FLUSH));
+  session->SetTimeoutMillis(15 * 1000);
+  RETURN_NOT_OK(client_->OpenTable(table_name, &table));
+
+  // Insert a bunch of rows with the current schema
+  for (int i = start_row; i < start_row + num_rows; i++) {
+    unique_ptr<KuduInsert> insert(table->NewInsert());
+    RETURN_NOT_OK(insert->mutable_row()->SetInt32(0, i));
+    if (table->schema().num_columns() > 1) {
+      RETURN_NOT_OK(insert->mutable_row()->SetInt32(1, i));
+    }
+    RETURN_NOT_OK(session->Apply(insert.release()));
+  }
+  Status s = session->Flush();
+  if (!s.ok()) {
+    vector<KuduError*> errors;
+    ElementDeleter d(&errors);
+    bool overflow;
+    session->GetPendingErrors(&errors, &overflow);
+    for (auto* error : errors) {
+      LOG(WARNING) << error->status().ToString();
+    }
+  }
+  return s;
+}
+
 void AlterTableTest::UpdateRow(int32_t row_key,
                                const map<string, int32_t>& updates) {
   shared_ptr<KuduSession> session = client_->NewSession();
@@ -448,7 +489,7 @@ void AlterTableTest::UpdateRow(int32_t row_key,
   CHECK_OK(client_->OpenTable(kTableName, &table));
   CHECK_OK(session->SetFlushMode(KuduSession::MANUAL_FLUSH));
   session->SetTimeoutMillis(15 * 1000);
-  gscoped_ptr<KuduUpdate> update(table->NewUpdate());
+  unique_ptr<KuduUpdate> update(table->NewUpdate());
   int32_t key = bswap_32(row_key); // endian swap to match 'InsertRows'
   CHECK_OK(update->mutable_row()->SetInt32(0, key));
   typedef map<string, int32_t>::value_type entry;
@@ -513,6 +554,29 @@ void AlterTableTest::VerifyRows(int start_row, int num_rows, VerifyPattern patte
   CHECK_EQ(verified, num_rows);
 }
 
+Status AlterTableTest::CreateTable(const string& table_name,
+                                   const KuduSchema& schema,
+                                   const vector<string>& range_partition_columns,
+                                   vector<unique_ptr<KuduPartialRow>> split_rows,
+                                   vector<pair<unique_ptr<KuduPartialRow>,
+                                               unique_ptr<KuduPartialRow>>> bounds) {
+  unique_ptr<KuduTableCreator> table_creator(client_->NewTableCreator());
+  table_creator->table_name(table_name)
+                .schema(&schema)
+                .set_range_partition_columns(range_partition_columns)
+                .num_replicas(1);
+
+  for (auto& split_row : split_rows) {
+    table_creator->add_range_split(split_row.release());
+  }
+
+  for (auto& bound : bounds) {
+    table_creator->add_range_bound(bound.first.release(), bound.second.release());
+  }
+
+  return table_creator->Create();
+}
+
 // Test inserting/updating some data, dropping a column, and adding a new one
 // with the same name. Data should not "reappear" from the old column.
 //
@@ -530,7 +594,7 @@ TEST_F(AlterTableTest, TestDropAndAddNewColumn) {
   VerifyRows(0, kNumRows, C1_MATCHES_INDEX);
 
   LOG(INFO) << "Dropping and adding back c1";
-  gscoped_ptr<KuduTableAlterer> table_alterer(client_->NewTableAlterer(kTableName));
+  unique_ptr<KuduTableAlterer> table_alterer(client_->NewTableAlterer(kTableName));
   ASSERT_OK(table_alterer->DropColumn("c1")
             ->Alter());
 
@@ -543,7 +607,7 @@ TEST_F(AlterTableTest, TestDropAndAddNewColumn) {
 // Tests that a renamed table can still be altered. This is a regression test, we used to not carry
 // over column ids after a table rename.
 TEST_F(AlterTableTest, TestRenameTableAndAdd) {
-  gscoped_ptr<KuduTableAlterer> table_alterer(client_->NewTableAlterer(kTableName));
+  unique_ptr<KuduTableAlterer> table_alterer(client_->NewTableAlterer(kTableName));
   string new_name = "someothername";
   ASSERT_OK(table_alterer->RenameTo(new_name)
             ->Alter());
@@ -571,7 +635,7 @@ TEST_F(AlterTableTest, TestBootstrapAfterAlters) {
   ASSERT_EQ("(int32 c0=16777216, int32 c1=10002, int32 c2=12345)", rows[1]);
 
   LOG(INFO) << "Dropping c1";
-  gscoped_ptr<KuduTableAlterer> table_alterer(client_->NewTableAlterer(kTableName));
+  unique_ptr<KuduTableAlterer> table_alterer(client_->NewTableAlterer(kTableName));
   ASSERT_OK(table_alterer->DropColumn("c1")->Alter());
 
   NO_FATALS(ScanToStrings(&rows));
@@ -626,7 +690,7 @@ TEST_F(AlterTableTest, TestCompactAfterUpdatingRemovedColumn) {
 
   // Drop c1.
   LOG(INFO) << "Dropping c1";
-  gscoped_ptr<KuduTableAlterer> table_alterer(client_->NewTableAlterer(kTableName));
+  unique_ptr<KuduTableAlterer> table_alterer(client_->NewTableAlterer(kTableName));
   ASSERT_OK(table_alterer->DropColumn("c1")->Alter());
 
   NO_FATALS(ScanToStrings(&rows));
@@ -662,7 +726,7 @@ TEST_F(AlterTableTest, TestMajorCompactDeltasAfterUpdatingRemovedColumn) {
 
   // Drop c1.
   LOG(INFO) << "Dropping c1";
-  gscoped_ptr<KuduTableAlterer> table_alterer(client_->NewTableAlterer(kTableName));
+  unique_ptr<KuduTableAlterer> table_alterer(client_->NewTableAlterer(kTableName));
   ASSERT_OK(table_alterer->DropColumn("c1") ->Alter());
 
   NO_FATALS(ScanToStrings(&rows));
@@ -764,7 +828,7 @@ TEST_F(AlterTableTest, TestMajorCompactDeltasAfterAddUpdateRemoveColumn) {
 
   // Drop c2.
   LOG(INFO) << "Dropping c2";
-  gscoped_ptr<KuduTableAlterer> table_alterer(client_->NewTableAlterer(kTableName));
+  unique_ptr<KuduTableAlterer> table_alterer(client_->NewTableAlterer(kTableName));
   ASSERT_OK(table_alterer->DropColumn("c2")->Alter());
 
   NO_FATALS(ScanToStrings(&rows));
@@ -800,7 +864,7 @@ void AlterTableTest::InserterThread() {
   CHECK_OK(client_->OpenTable(kTableName, &table));
   int32_t i = 0;
   while (!stop_threads_.Load()) {
-    gscoped_ptr<KuduInsert> insert(table->NewInsert());
+    unique_ptr<KuduInsert> insert(table->NewInsert());
     // Endian-swap the key so that we spew inserts randomly
     // instead of just a sequential write pattern. This way
     // compactions may actually be triggered.
@@ -832,7 +896,7 @@ void AlterTableTest::UpdaterThread() {
   Random rng(1);
   int32_t i = 0;
   while (!stop_threads_.Load()) {
-    gscoped_ptr<KuduUpdate> update(table->NewUpdate());
+    unique_ptr<KuduUpdate> update(table->NewUpdate());
 
     int32_t max = inserted_idx_.Load();
     if (max == 0) {
@@ -935,7 +999,7 @@ TEST_F(AlterTableTest, TestInsertAfterAlterTable) {
   ASSERT_OK(AddNewI32Column(kSplitTableName, "new-i32", 10));
   shared_ptr<KuduTable> table;
   ASSERT_OK(client_->OpenTable(kSplitTableName, &table));
-  gscoped_ptr<KuduInsert> insert(table->NewInsert());
+  unique_ptr<KuduInsert> insert(table->NewInsert());
   ASSERT_OK(insert->mutable_row()->SetInt32("c0", 1));
   ASSERT_OK(insert->mutable_row()->SetInt32("c1", 1));
   ASSERT_OK(insert->mutable_row()->SetInt32("new-i32", 1));
@@ -972,7 +1036,7 @@ TEST_F(AlterTableTest, TestMultipleAlters) {
 
   // Issue a bunch of new alters without waiting for them to finish.
   for (int i = 0; i < kNumNewCols; i++) {
-    gscoped_ptr<KuduTableAlterer> table_alterer(client_->NewTableAlterer(kSplitTableName));
+    unique_ptr<KuduTableAlterer> table_alterer(client_->NewTableAlterer(kSplitTableName));
     table_alterer->AddColumn(strings::Substitute("new_col$0", i))
       ->Type(KuduColumnSchema::INT32)->NotNull()
       ->Default(KuduValue::FromInt(kDefaultValue));
@@ -988,6 +1052,255 @@ TEST_F(AlterTableTest, TestMultipleAlters) {
   ASSERT_EQ(kNumNewCols + schema_.num_columns(), new_schema.num_columns());
 }
 
+TEST_F(AlterTableTest, TestAlterRangePartitioning) {
+  unique_ptr<KuduTableAlterer> table_alterer;
+
+  // Create initial table with single range partition covering the entire key
+  // space, and two hash buckets.
+  string table_name = "test-alter-range-partitioning";
+  unique_ptr<KuduTableCreator> table_creator(client_->NewTableCreator());
+  ASSERT_OK(table_creator->table_name(table_name)
+                          .schema(&schema_)
+                          .set_range_partition_columns({ "c0" })
+                          .add_hash_partitions({ "c0" }, 2)
+                          .num_replicas(1)
+                          .Create());
+  shared_ptr<KuduTable> table;
+  ASSERT_OK(client_->OpenTable(table_name, &table));
+
+  // Insert some rows, and then drop the partition and ensure that the table is empty.
+  ASSERT_OK(InsertRowsSequential(table_name, 0, 100));
+  table_alterer.reset(client_->NewTableAlterer(table_name));
+  ASSERT_OK(table_alterer->DropRangePartition(schema_.NewRow(),
+                                              schema_.NewRow())->Alter());
+  ASSERT_EQ(0, CountTableRows(table.get()));
+
+  // Add new range partition and insert rows.
+  unique_ptr<KuduPartialRow> lower(schema_.NewRow());
+  unique_ptr<KuduPartialRow> upper(schema_.NewRow());
+  ASSERT_OK(lower->SetInt32("c0", 0));
+  ASSERT_OK(upper->SetInt32("c0", 100));
+  table_alterer.reset(client_->NewTableAlterer(table_name));
+  ASSERT_OK(table_alterer->AddRangePartition(lower.release(), upper.release())->Alter());
+  ASSERT_OK(InsertRowsSequential(table_name, 0, 100));
+  ASSERT_EQ(100, CountTableRows(table.get()));
+
+  // Replace the range partition with a different one.
+  table_alterer.reset(client_->NewTableAlterer(table_name));
+  lower.reset(schema_.NewRow());
+  upper.reset(schema_.NewRow());
+  ASSERT_OK(lower->SetInt32("c0", 0));
+  ASSERT_OK(upper->SetInt32("c0", 100));
+  table_alterer->DropRangePartition(lower.release(), upper.release());
+  lower.reset(schema_.NewRow());
+  upper.reset(schema_.NewRow());
+  ASSERT_OK(lower->SetInt32("c0", 50));
+  ASSERT_OK(upper->SetInt32("c0", 150));
+  table_alterer->AddRangePartition(lower.release(), upper.release());
+  ASSERT_OK(table_alterer->wait(false)->Alter());
+  ASSERT_OK(InsertRowsSequential(table_name, 50, 75));
+  ASSERT_EQ(75, CountTableRows(table.get()));
+
+  // Replace the range partition with the same one.
+  table_alterer.reset(client_->NewTableAlterer(table_name));
+  lower.reset(schema_.NewRow());
+  upper.reset(schema_.NewRow());
+  ASSERT_OK(lower->SetInt32("c0", 50));
+  ASSERT_OK(upper->SetInt32("c0", 150));
+  table_alterer->DropRangePartition(lower.release(), upper.release());
+  lower.reset(schema_.NewRow());
+  upper.reset(schema_.NewRow());
+  ASSERT_OK(lower->SetInt32("c0", 50));
+  ASSERT_OK(upper->SetInt32("c0", 150));
+  table_alterer->AddRangePartition(lower.release(), upper.release());
+  ASSERT_OK(table_alterer->Alter());
+  ASSERT_EQ(0, CountTableRows(table.get()));
+  ASSERT_OK(InsertRowsSequential(table_name, 50, 75));
+  ASSERT_EQ(75, CountTableRows(table.get()));
+
+  // Alter table partitioning + alter table schema
+  table_alterer.reset(client_->NewTableAlterer(table_name));
+  lower.reset(schema_.NewRow());
+  upper.reset(schema_.NewRow());
+  ASSERT_OK(lower->SetInt32("c0", 200));
+  ASSERT_OK(upper->SetInt32("c0", 300));
+  table_name += "-renamed";
+  table_alterer->AddRangePartition(lower.release(), upper.release())
+               ->RenameTo(table_name)
+               ->AddColumn("c2")->Type(KuduColumnSchema::INT32);
+  ASSERT_OK(table_alterer->Alter());
+  ASSERT_OK(InsertRowsSequential(table_name, 200, 100));
+  ASSERT_EQ(175, CountTableRows(table.get()));
+  ASSERT_OK(client_->OpenTable(table_name, &table));
+  ASSERT_EQ(3, table->schema().num_columns());
+
+  // Drop all range partitions + alter table schema. This also serves to test
+  // specifying range bounds with a subset schema (since a column was
+  // previously added).
+  table_alterer.reset(client_->NewTableAlterer(table_name));
+  lower.reset(schema_.NewRow());
+  upper.reset(schema_.NewRow());
+  ASSERT_OK(lower->SetInt32("c0", 50));
+  ASSERT_OK(upper->SetInt32("c0", 150));
+  table_alterer->DropRangePartition(lower.release(), upper.release());
+  lower.reset(schema_.NewRow());
+  upper.reset(schema_.NewRow());
+  ASSERT_OK(lower->SetInt32("c0", 200));
+  ASSERT_OK(upper->SetInt32("c0", 300));
+  table_alterer->DropRangePartition(lower.release(), upper.release());
+  table_alterer->AddColumn("c3")->Type(KuduColumnSchema::STRING);
+  ASSERT_OK(table_alterer->Alter());
+  ASSERT_EQ(0, CountTableRows(table.get()));
+}
+
+TEST_F(AlterTableTest, TestAlterRangePartitioningInvalid) {
+  unique_ptr<KuduTableAlterer> table_alterer;
+  Status s;
+
+  // Create initial table with single range partition covering [0, 100).
+  string table_name = "test-alter-range-partitioning-invalid";
+  unique_ptr<KuduTableCreator> table_creator(client_->NewTableCreator());
+  unique_ptr<KuduPartialRow> lower(schema_.NewRow());
+  unique_ptr<KuduPartialRow> upper(schema_.NewRow());
+  ASSERT_OK(lower->SetInt32("c0", 0));
+  ASSERT_OK(upper->SetInt32("c0", 100));
+  ASSERT_OK(table_creator->table_name(table_name)
+                          .schema(&schema_)
+                          .set_range_partition_columns({ "c0" })
+                          .add_range_bound(lower.release(), upper.release())
+                          .num_replicas(1)
+                          .Create());
+  shared_ptr<KuduTable> table;
+  ASSERT_OK(client_->OpenTable(table_name, &table));
+  ASSERT_OK(InsertRowsSequential(table_name, 0, 100));
+  ASSERT_EQ(100, CountTableRows(table.get()));
+
+  // ADD [0, 100) <- illegal (duplicate)
+  table_alterer.reset(client_->NewTableAlterer(table_name));
+  lower.reset(schema_.NewRow());
+  upper.reset(schema_.NewRow());
+  ASSERT_OK(lower->SetInt32("c0", 0));
+  ASSERT_OK(upper->SetInt32("c0", 100));
+  table_alterer->AddRangePartition(lower.release(), upper.release());
+  s = table_alterer->wait(false)->Alter();
+  ASSERT_FALSE(s.ok());
+  ASSERT_STR_CONTAINS(s.ToString(), "New partition conflicts with existing partition");
+  ASSERT_EQ(100, CountTableRows(table.get()));
+
+  // ADD [50, 150) <- illegal (overlap)
+  table_alterer.reset(client_->NewTableAlterer(table_name));
+  lower.reset(schema_.NewRow());
+  upper.reset(schema_.NewRow());
+  ASSERT_OK(lower->SetInt32("c0", 50));
+  ASSERT_OK(upper->SetInt32("c0", 150));
+  table_alterer->AddRangePartition(lower.release(), upper.release());
+  s = table_alterer->wait(false)->Alter();
+  ASSERT_FALSE(s.ok());
+  ASSERT_STR_CONTAINS(s.ToString(), "New partition conflicts with existing partition");
+  ASSERT_EQ(100, CountTableRows(table.get()));
+
+  // ADD [-50, 50) <- illegal (overlap)
+  table_alterer.reset(client_->NewTableAlterer(table_name));
+  lower.reset(schema_.NewRow());
+  upper.reset(schema_.NewRow());
+  ASSERT_OK(lower->SetInt32("c0", -50));
+  ASSERT_OK(upper->SetInt32("c0", 50));
+  table_alterer->AddRangePartition(lower.release(), upper.release());
+  s = table_alterer->wait(false)->Alter();
+  ASSERT_FALSE(s.ok());
+  ASSERT_STR_CONTAINS(s.ToString(), "New partition conflicts with existing partition");
+  ASSERT_EQ(100, CountTableRows(table.get()));
+
+  // ADD [200, 300)
+  // ADD [-50, 150) <- illegal (overlap)
+  table_alterer.reset(client_->NewTableAlterer(table_name));
+  lower.reset(schema_.NewRow());
+  upper.reset(schema_.NewRow());
+  ASSERT_OK(lower->SetInt32("c0", 200));
+  ASSERT_OK(upper->SetInt32("c0", 300));
+  table_alterer->AddRangePartition(lower.release(), upper.release());
+  lower.reset(schema_.NewRow());
+  upper.reset(schema_.NewRow());
+  ASSERT_OK(lower->SetInt32("c0", -50));
+  ASSERT_OK(upper->SetInt32("c0", 150));
+  table_alterer->AddRangePartition(lower.release(), upper.release());
+  s = table_alterer->wait(false)->Alter();
+  ASSERT_FALSE(s.ok());
+  ASSERT_STR_CONTAINS(s.ToString(), "New partition conflicts with existing partition");
+  ASSERT_FALSE(InsertRowsSequential(table_name, 200, 100).ok());
+  ASSERT_EQ(100, CountTableRows(table.get()));
+
+  // DROP [<start>, <end>)
+  table_alterer.reset(client_->NewTableAlterer(table_name));
+  table_alterer->DropRangePartition(schema_.NewRow(), schema_.NewRow());
+  s = table_alterer->Alter();
+  ASSERT_FALSE(s.ok());
+  ASSERT_STR_CONTAINS(s.ToString(), "No tablet found for drop partition step");
+  ASSERT_EQ(100, CountTableRows(table.get()));
+
+  // DROP [50, 150)
+  // RENAME foo
+  table_alterer.reset(client_->NewTableAlterer(table_name));
+  lower.reset(schema_.NewRow());
+  upper.reset(schema_.NewRow());
+  ASSERT_OK(lower->SetInt32("c0", 50));
+  ASSERT_OK(upper->SetInt32("c0", 150));
+  table_alterer->DropRangePartition(lower.release(), upper.release());
+  table_alterer->RenameTo("foo");
+  s = table_alterer->Alter();
+  ASSERT_FALSE(s.ok());
+  ASSERT_STR_CONTAINS(s.ToString(), "No tablet found for drop partition step");
+  ASSERT_EQ(100, CountTableRows(table.get()));
+
+  // DROP [-50, 50)
+  table_alterer.reset(client_->NewTableAlterer(table_name));
+  lower.reset(schema_.NewRow());
+  upper.reset(schema_.NewRow());
+  ASSERT_OK(lower->SetInt32("c0", -50));
+  ASSERT_OK(upper->SetInt32("c0", 50));
+  table_alterer->DropRangePartition(lower.release(), upper.release());
+  s = table_alterer->Alter();
+  ASSERT_FALSE(s.ok());
+  ASSERT_STR_CONTAINS(s.ToString(), "No tablet found for drop partition step");
+  ASSERT_EQ(100, CountTableRows(table.get()));
+
+  // DROP [0, 100)
+  // ADD  [100, 200)
+  // DROP [100, 200)
+  // ADD  [150, 250)
+  // DROP [0, 10)    <- illegal
+  table_alterer.reset(client_->NewTableAlterer(table_name));
+  lower.reset(schema_.NewRow());
+  upper.reset(schema_.NewRow());
+  ASSERT_OK(lower->SetInt32("c0", 0));
+  ASSERT_OK(upper->SetInt32("c0", 100));
+  table_alterer->DropRangePartition(lower.release(), upper.release());
+  lower.reset(schema_.NewRow());
+  upper.reset(schema_.NewRow());
+  ASSERT_OK(lower->SetInt32("c0", 100));
+  ASSERT_OK(upper->SetInt32("c0", 200));
+  table_alterer->AddRangePartition(lower.release(), upper.release());
+  lower.reset(schema_.NewRow());
+  upper.reset(schema_.NewRow());
+  ASSERT_OK(lower->SetInt32("c0", 100));
+  ASSERT_OK(upper->SetInt32("c0", 200));
+  table_alterer->DropRangePartition(lower.release(), upper.release());
+  lower.reset(schema_.NewRow());
+  upper.reset(schema_.NewRow());
+  ASSERT_OK(lower->SetInt32("c0", 150));
+  ASSERT_OK(upper->SetInt32("c0", 250));
+  table_alterer->AddRangePartition(lower.release(), upper.release());
+  lower.reset(schema_.NewRow());
+  upper.reset(schema_.NewRow());
+  ASSERT_OK(lower->SetInt32("c0", 0));
+  ASSERT_OK(upper->SetInt32("c0", 10));
+  table_alterer->DropRangePartition(lower.release(), upper.release());
+  s = table_alterer->wait(false)->Alter();
+  ASSERT_FALSE(s.ok());
+  ASSERT_STR_CONTAINS(s.ToString(), "No tablet found for drop partition step");
+  ASSERT_EQ(100, CountTableRows(table.get()));
+}
+
 TEST_F(ReplicatedAlterTableTest, TestReplicatedAlter) {
   const int kNumRows = 100;
   InsertRows(0, kNumRows);
@@ -996,7 +1309,7 @@ TEST_F(ReplicatedAlterTableTest, TestReplicatedAlter) {
   VerifyRows(0, kNumRows, C1_MATCHES_INDEX);
 
   LOG(INFO) << "Dropping and adding back c1";
-  gscoped_ptr<KuduTableAlterer> table_alterer(client_->NewTableAlterer(kTableName));
+  unique_ptr<KuduTableAlterer> table_alterer(client_->NewTableAlterer(kTableName));
   ASSERT_OK(table_alterer->DropColumn("c1")->Alter());
 
   ASSERT_OK(AddNewI32Column(kTableName, "c1", 0xdeadbeef));

http://git-wip-us.apache.org/repos/asf/kudu/blob/232474a5/src/kudu/integration-tests/external_mini_cluster.cc
----------------------------------------------------------------------
diff --git a/src/kudu/integration-tests/external_mini_cluster.cc b/src/kudu/integration-tests/external_mini_cluster.cc
index 03da89c..62daef2 100644
--- a/src/kudu/integration-tests/external_mini_cluster.cc
+++ b/src/kudu/integration-tests/external_mini_cluster.cc
@@ -48,13 +48,17 @@
 #include "kudu/util/test_util.h"
 
 using kudu::master::GetLeaderMasterRpc;
+using kudu::master::ListTablesRequestPB;
+using kudu::master::ListTablesResponsePB;
 using kudu::master::MasterServiceProxy;
+using kudu::rpc::RpcController;
 using kudu::server::ServerStatusPB;
 using kudu::tserver::ListTabletsRequestPB;
 using kudu::tserver::ListTabletsResponsePB;
 using kudu::tserver::TabletServerServiceProxy;
 using rapidjson::Value;
 using std::string;
+using std::unique_ptr;
 using strings::Substitute;
 
 typedef ListTabletsResponsePB::StatusAndSchemaPB StatusAndSchemaPB;
@@ -81,7 +85,6 @@ ExternalMiniClusterOptions::ExternalMiniClusterOptions()
 ExternalMiniClusterOptions::~ExternalMiniClusterOptions() {
 }
 
-
 ExternalMiniCluster::ExternalMiniCluster(const ExternalMiniClusterOptions& opts)
   : opts_(opts) {
 }
@@ -839,6 +842,40 @@ Status ExternalMaster::Restart() {
   return Status::OK();
 }
 
+Status ExternalMaster::WaitForCatalogManager() {
+  unique_ptr<MasterServiceProxy> proxy(
+      new MasterServiceProxy(messenger_, bound_rpc_addr()));
+  while (true) {
+    ListTablesRequestPB req;
+    ListTablesResponsePB resp;
+    RpcController rpc;
+    Status s = proxy->ListTables(req, &resp, &rpc);
+    if (s.ok()) {
+      if (!resp.has_error()) {
+        // This master is the leader and is up and running.
+        break;
+      } else {
+        s = StatusFromPB(resp.error().status());
+        if (s.IsIllegalState()) {
+          // This master is not the leader but is otherwise up and running.
+          break;
+        } else if (!s.IsServiceUnavailable()) {
+          // Unexpected error from master.
+          return s;
+        }
+      }
+    } else if (!s.IsNetworkError()) {
+      // Unexpected error from proxy.
+      return s;
+    }
+
+    // There was some kind of transient network error or the master isn't yet
+    // ready. Sleep and retry.
+    SleepFor(MonoDelta::FromMilliseconds(50));
+  }
+  return Status::OK();
+}
+
 
 //------------------------------------------------------------
 // ExternalTabletServer

http://git-wip-us.apache.org/repos/asf/kudu/blob/232474a5/src/kudu/integration-tests/external_mini_cluster.h
----------------------------------------------------------------------
diff --git a/src/kudu/integration-tests/external_mini_cluster.h b/src/kudu/integration-tests/external_mini_cluster.h
index 4836549..9bf4c1d 100644
--- a/src/kudu/integration-tests/external_mini_cluster.h
+++ b/src/kudu/integration-tests/external_mini_cluster.h
@@ -403,6 +403,9 @@ class ExternalMaster : public ExternalDaemon {
   // Requires that it has previously been shutdown.
   Status Restart() WARN_UNUSED_RESULT;
 
+  // Blocks until the master's catalog manager is initialized and responding to
+  // RPCs.
+  Status WaitForCatalogManager() WARN_UNUSED_RESULT;
 
  private:
   friend class RefCountedThreadSafe<ExternalMaster>;
@@ -425,7 +428,6 @@ class ExternalTabletServer : public ExternalDaemon {
   // Requires that it has previously been shutdown.
   Status Restart() WARN_UNUSED_RESULT;
 
-
  private:
   const std::string master_addrs_;
   const std::string bind_host_;


Mime
View raw message