This is an automated email from the ASF dual-hosted git repository.
alexey pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kudu.git
The following commit(s) were added to refs/heads/master by this push:
new e8ce3a9bf KUDU-2671 Make WebUI compatible with custom hash schema
e8ce3a9bf is described below
commit e8ce3a9bff9d3a0c00af7e71592e552c33ce5b24
Author: Abhishek Chennaka <[email protected]>
AuthorDate: Fri Jul 8 01:59:58 2022 -0400
KUDU-2671 Make WebUI compatible with custom hash schema
This patch updates the /table?id=<table_id> page in the Kudu master
WebUI to show custom hash schemas in the sections of:
1. Partition Schema
The custom hash schema if present for a particular range is displayed
right beside the range schema. Different dimensions of the hash
schema are comma separated.
2. Detail
There are new columns to identify if a particular partition has
custom or table wide hash schema, display the hash schema and the hash
partition id of the partition.
The Kudu tablet server WebUI's pages /tablets and
/tablet?id=<tablet_id> are also tested to reflect the custom hash
schema or table wide hash schema accordingly.
Below are the screenshots of the WebUI after the changes
Master WebUI:
https://i.imgur.com/O4ra4JA.png
Tablet server WebUI:
https://i.imgur.com/BxdfsYt.png
https://i.imgur.com/l2wA08Q.png
Change-Id: Ic8b8d90f70c39f13b838e858c870e08dacbdfcd3
Reviewed-on: http://gerrit.cloudera.org:8080/18712
Reviewed-by: Alexey Serbin <[email protected]>
Tested-by: Kudu Jenkins
---
src/kudu/client/flex_partitioning_client-test.cc | 108 +++++++++++++
src/kudu/common/partition-test.cc | 10 +-
src/kudu/common/partition.cc | 101 +++++++++++-
src/kudu/common/partition.h | 8 +
src/kudu/master/master-test.cc | 196 ++++++++++++++++++++++-
src/kudu/master/master_path_handlers.cc | 6 +-
6 files changed, 413 insertions(+), 16 deletions(-)
diff --git a/src/kudu/client/flex_partitioning_client-test.cc
b/src/kudu/client/flex_partitioning_client-test.cc
index 8a56050d1..c8af1b26d 100644
--- a/src/kudu/client/flex_partitioning_client-test.cc
+++ b/src/kudu/client/flex_partitioning_client-test.cc
@@ -37,6 +37,7 @@
#include "kudu/gutil/port.h"
#include "kudu/gutil/ref_counted.h"
#include "kudu/gutil/stl_util.h"
+#include "kudu/gutil/strings/substitute.h"
#include "kudu/master/catalog_manager.h"
#include "kudu/master/master.h"
#include "kudu/master/mini_master.h"
@@ -47,6 +48,8 @@
#include "kudu/tserver/tablet_server.h"
#include "kudu/tserver/ts_tablet_manager.h"
#include "kudu/util/metrics.h"
+#include "kudu/util/curl_util.h"
+#include "kudu/util/faststring.h"
#include "kudu/util/net/sockaddr.h"
#include "kudu/util/slice.h"
#include "kudu/util/status.h"
@@ -55,6 +58,7 @@
DECLARE_bool(enable_per_range_hash_schemas);
DECLARE_int32(heartbeat_interval_ms);
+DECLARE_string(webserver_doc_root);
METRIC_DECLARE_counter(scans_started);
@@ -63,9 +67,13 @@ using kudu::client::KuduValue;
using kudu::cluster::InternalMiniCluster;
using kudu::cluster::InternalMiniClusterOptions;
using kudu::master::CatalogManager;
+using kudu::master::TableInfo;
+using kudu::master::TabletInfo;
+using kudu::tablet::TabletReplica;
using std::string;
using std::unique_ptr;
using std::vector;
+using strings::Substitute;
static constexpr const char* const kKeyColumn = "key";
static constexpr const char* const kIntValColumn = "int_val";
@@ -97,6 +105,10 @@ class FlexPartitioningTest : public KuduTest {
// Reduce the TS<->Master heartbeat interval to speed up testing.
FLAGS_heartbeat_interval_ms = 10;
+ // Ensure the static pages are not available as tests are written based
+ // on this value of the flag
+ FLAGS_webserver_doc_root = "";
+
// Start minicluster and wait for tablet servers to connect to master.
cluster_.reset(new InternalMiniCluster(env_,
InternalMiniClusterOptions()));
ASSERT_OK(cluster_->Start());
@@ -546,6 +558,102 @@ TEST_F(FlexPartitioningCreateTableTest,
DefaultAndCustomHashSchemas) {
}
}
+TEST_F(FlexPartitioningCreateTableTest, TabletServerWebUI) {
+ // Create a table with the following partitions:
+ //
+ // hash bucket
+ // key 0 1 2 3
+ // -----------------------------------------------------------
+ // <111 x:{key} x:{key} - -
+ // 111-222 x:{key} x:{key} x:{key} -
+ // 222-333 x:{key} x:{key} x:{key} x:{key}
+ constexpr const char* const kTableName = "TabletServerWebUI";
+
+ unique_ptr<KuduTableCreator> table_creator(client_->NewTableCreator());
+ table_creator->table_name(kTableName)
+ .schema(&schema_)
+ .num_replicas(1)
+ .add_hash_partitions({ kKeyColumn }, 2)
+ .set_range_partition_columns({ kKeyColumn });
+
+ // Add a range partition with the table-wide hash partitioning rules.
+ {
+ unique_ptr<KuduPartialRow> lower(schema_.NewRow());
+ ASSERT_OK(lower->SetInt32(kKeyColumn, INT32_MIN));
+ unique_ptr<KuduPartialRow> upper(schema_.NewRow());
+ ASSERT_OK(upper->SetInt32(kKeyColumn, 111));
+ table_creator->add_range_partition(lower.release(), upper.release());
+ }
+
+ // Add a range partition with custom hash sub-partitioning rules:
+ // 3 buckets with hash based on the "key" column with hash seed 1.
+ {
+ auto p = CreateRangePartition(111, 222);
+ ASSERT_OK(p->add_hash_partitions({ kKeyColumn }, 3, 1));
+ table_creator->add_custom_range_partition(p.release());
+ }
+
+ // Add a range partition with custom hash sub-partitioning rules:
+ // 4 buckets with hash based on the "key" column with hash seed 2.
+ {
+ auto p = CreateRangePartition(222, 333);
+ ASSERT_OK(p->add_hash_partitions({ kKeyColumn }, 4, 2));
+ table_creator->add_custom_range_partition(p.release());
+ }
+
+ ASSERT_OK(table_creator->Create());
+ NO_FATALS(CheckTabletCount(kTableName, 9));
+
+ // Obtain the web page contents
+ EasyCurl c;
+ faststring buf;
+ ASSERT_OK(c.FetchURL(Substitute("http://$0/tablets",
+
cluster_->mini_tablet_server(0)->bound_http_addr().ToString()),
+ &buf));
+ string raw = buf.ToString();
+
+ // Get the list of tablets present in this table
+ std::vector<scoped_refptr<TableInfo>> tables;
+ {
+ CatalogManager::ScopedLeaderSharedLock l(
+ cluster_->mini_master(0)->master()->catalog_manager());
+
cluster_->mini_master(0)->master()->catalog_manager()->GetAllTables(&tables);
+ }
+ ASSERT_EQ(1, tables.size());
+ vector<scoped_refptr<TabletInfo>> tablets;
+ tables.front()->GetAllTablets(&tablets);
+
+ ASSERT_EQ(9, tablets.size());
+ // Validate the partition information rendered in the page
+ ASSERT_STR_CONTAINS(raw, Substitute("id=$0\"},\"partition\":\"HASH (key)
PARTITION 0, "
+ "RANGE (key) PARTITION -2147483648 <=
VALUES < 111",
+ tablets[0]->id()));
+ ASSERT_STR_CONTAINS(raw, Substitute("id=$0\"},\"partition\":\"HASH (key)
PARTITION 0, "
+ "RANGE (key) PARTITION 111 <= VALUES <
222",
+ tablets[1]->id()));
+ ASSERT_STR_CONTAINS(raw, Substitute("id=$0\"},\"partition\":\"HASH (key)
PARTITION 0, "
+ "RANGE (key) PARTITION 222 <= VALUES <
333",
+ tablets[2]->id()));
+ ASSERT_STR_CONTAINS(raw, Substitute("id=$0\"},\"partition\":\"HASH (key)
PARTITION 1, "
+ "RANGE (key) PARTITION -2147483648 <=
VALUES < 111",
+ tablets[3]->id()));
+ ASSERT_STR_CONTAINS(raw, Substitute("id=$0\"},\"partition\":\"HASH (key)
PARTITION 1, "
+ "RANGE (key) PARTITION 111 <= VALUES <
222",
+ tablets[4]->id()));
+ ASSERT_STR_CONTAINS(raw, Substitute("id=$0\"},\"partition\":\"HASH (key)
PARTITION 1, "
+ "RANGE (key) PARTITION 222 <= VALUES <
333",
+ tablets[5]->id()));
+ ASSERT_STR_CONTAINS(raw, Substitute("id=$0\"},\"partition\":\"HASH (key)
PARTITION 2, "
+ "RANGE (key) PARTITION 111 <= VALUES <
222",
+ tablets[6]->id()));
+ ASSERT_STR_CONTAINS(raw, Substitute("id=$0\"},\"partition\":\"HASH (key)
PARTITION 2, "
+ "RANGE (key) PARTITION 222 <= VALUES <
333",
+ tablets[7]->id()));
+ ASSERT_STR_CONTAINS(raw, Substitute("id=$0\"},\"partition\":\"HASH (key)
PARTITION 3, "
+ "RANGE (key) PARTITION 222 <= VALUES <
333",
+ tablets[8]->id()));
+}
+
// Parameters for a single hash dimension.
struct HashDimensionParameters {
vector<string> columns; // names of the columns to use for hash bucketing
diff --git a/src/kudu/common/partition-test.cc
b/src/kudu/common/partition-test.cc
index 1202f96ee..b992a3957 100644
--- a/src/kudu/common/partition-test.cc
+++ b/src/kudu/common/partition-test.cc
@@ -198,7 +198,7 @@ TEST_F(PartitionTest, TestCompoundRangeKeyEncoding) {
partition_schema.PartitionDebugString(partitions[1], schema));
EXPECT_EQ(R"(RANGE (c1, c2, c3) PARTITION ("", "b", "c") <= VALUES < ("d",
"", "f"))",
partition_schema.PartitionDebugString(partitions[2], schema));
- EXPECT_EQ(R"(RANGE (c1, c2, c3) PARTITION VALUES >= ("e", "", ""))",
+ EXPECT_EQ(R"(RANGE (c1, c2, c3) PARTITION ("e", "", "") <= VALUES)",
partition_schema.PartitionDebugString(partitions[3], schema));
}
@@ -597,7 +597,7 @@ TEST_F(PartitionTest, TestCreatePartitions) {
EXPECT_EQ(string("\0\0\0\0" "\0\0\0\0" "a2\0\0b2\0\0", 16),
partitions[2].begin().ToString());
EXPECT_EQ(string("\0\0\0\0" "\0\0\0\1", 8), partitions[2].end().ToString());
EXPECT_EQ("HASH (a) PARTITION 0, HASH (b) PARTITION 0, "
- R"(RANGE (a, b, c) PARTITION VALUES >= ("a2", "b2", ""))",
+ R"(RANGE (a, b, c) PARTITION ("a2", "b2", "") <= VALUES)",
partition_schema.PartitionDebugString(partitions[2], schema));
EXPECT_EQ(0, partitions[3].hash_buckets()[0]);
@@ -628,7 +628,7 @@ TEST_F(PartitionTest, TestCreatePartitions) {
EXPECT_EQ(string("\0\0\0\0" "\0\0\0\1" "a2\0\0b2\0\0", 16),
partitions[5].begin().ToString());
EXPECT_EQ(string("\0\0\0\1", 4), partitions[5].end().ToString());
EXPECT_EQ("HASH (a) PARTITION 0, HASH (b) PARTITION 1, "
- R"(RANGE (a, b, c) PARTITION VALUES >= ("a2", "b2", ""))",
+ R"(RANGE (a, b, c) PARTITION ("a2", "b2", "") <= VALUES)",
partition_schema.PartitionDebugString(partitions[5], schema));
EXPECT_EQ(1, partitions[6].hash_buckets()[0]);
@@ -659,7 +659,7 @@ TEST_F(PartitionTest, TestCreatePartitions) {
EXPECT_EQ(string("\0\0\0\1" "\0\0\0\0" "a2\0\0b2\0\0", 16),
partitions[8].begin().ToString());
EXPECT_EQ(string("\0\0\0\1" "\0\0\0\1", 8), partitions[8].end().ToString());
EXPECT_EQ("HASH (a) PARTITION 1, HASH (b) PARTITION 0, "
- R"(RANGE (a, b, c) PARTITION VALUES >= ("a2", "b2", ""))",
+ R"(RANGE (a, b, c) PARTITION ("a2", "b2", "") <= VALUES)",
partition_schema.PartitionDebugString(partitions[8], schema));
EXPECT_EQ(1, partitions[9].hash_buckets()[0]);
@@ -690,7 +690,7 @@ TEST_F(PartitionTest, TestCreatePartitions) {
EXPECT_EQ(string("\0\0\0\1" "\0\0\0\1" "a2\0\0b2\0\0", 16),
partitions[11].begin().ToString());
EXPECT_EQ("", partitions[11].end().ToString());
EXPECT_EQ("HASH (a) PARTITION 1, HASH (b) PARTITION 1, "
- R"(RANGE (a, b, c) PARTITION VALUES >= ("a2", "b2", ""))",
+ R"(RANGE (a, b, c) PARTITION ("a2", "b2", "") <= VALUES)",
partition_schema.PartitionDebugString(partitions[11], schema));
}
diff --git a/src/kudu/common/partition.cc b/src/kudu/common/partition.cc
index 309071ef4..85bfbfb96 100644
--- a/src/kudu/common/partition.cc
+++ b/src/kudu/common/partition.cc
@@ -997,7 +997,7 @@ string PartitionSchema::RangePartitionDebugString(const
KuduPartialRow& lower_bo
return Substitute("VALUES < $0", RangeKeyDebugString(upper_bound));
}
if (upper_unbounded) {
- return Substitute("VALUES >= $0", RangeKeyDebugString(lower_bound));
+ return Substitute("$0 <= VALUES", RangeKeyDebugString(lower_bound));
}
// TODO(dan): recognize when a simplified 'VALUE =' form can be used (see
// org.apache.kudu.client.Partition#formatRangePartition).
@@ -1028,6 +1028,54 @@ string PartitionSchema::RangePartitionDebugString(Slice
lower_bound,
return RangePartitionDebugString(lower, upper);
}
+string PartitionSchema::RangeWithCustomHashPartitionDebugString(Slice
lower_bound,
+ Slice
upper_bound,
+ const Schema&
schema) const {
+ // Partitions are considered metadata, so don't redact them.
+ ScopedDisableRedaction no_redaction;
+ HashSchema hash_schema = GetHashSchemaForRange(lower_bound.ToString());
+ KuduPartialRow lower(&schema);
+ Arena arena(256);
+
+ // Decode the lower and upper bounds
+ Status s = DecodeRangeKey(&lower_bound, &lower, &arena);
+ if (!s.ok()) {
+ return Substitute("<range-key-decode-error: $0>", s.ToString());
+ }
+
+ KuduPartialRow upper(&schema);
+ s = DecodeRangeKey(&upper_bound, &upper, &arena);
+ if (!s.ok()) {
+ return Substitute("<range-key-decode-error: $0>", s.ToString());
+ }
+
+ // Get the range bounds information for the partition
+ bool lower_unbounded = IsRangePartitionKeyEmpty(lower);
+ bool upper_unbounded = IsRangePartitionKeyEmpty(upper);
+ string partition;
+ if (lower_unbounded && upper_unbounded) {
+ partition = "UNBOUNDED";
+ } else if (lower_unbounded) {
+ partition = Substitute("VALUES < $0", RangeKeyDebugString(upper));
+ } else if (upper_unbounded) {
+ partition = Substitute("$0 <= VALUES", RangeKeyDebugString(lower));
+ } else {
+ partition = Substitute("$0 <= VALUES < $1",
+ RangeKeyDebugString(lower),
+ RangeKeyDebugString(upper));
+ }
+
+ // Get the hash schema information for the partition
+ if (hash_schema != hash_schema_) {
+ for (const auto& hash_dimension: hash_schema) {
+ partition.append(Substitute(" HASH($0) PARTITIONS $1",
+ ColumnIdsToColumnNames(schema,
hash_dimension.column_ids),
+ hash_dimension.num_buckets));
+ }
+ }
+ return partition;
+}
+
string PartitionSchema::RangeKeyDebugString(Slice range_key, const Schema&
schema) const {
Arena arena(256);
KuduPartialRow row(&schema);
@@ -1124,11 +1172,12 @@ string PartitionSchema::DisplayString(const Schema&
schema,
string PartitionSchema::PartitionTableHeader(const Schema& schema) const {
string header;
- for (const auto& hash_schema : hash_schema_) {
- SubstituteAndAppend(&header, "<th>HASH ($0) PARTITION</th>",
- EscapeForHtmlToString(ColumnIdsToColumnNames(
- schema, hash_schema.column_ids)));
+ if (!hash_schema_.empty()) {
+ header.append("<th>Hash Schema Type</th>");
+ header.append("<th>Hash Schema</th>");
+ header.append("<th>Hash Partition</th>");
}
+
if (!range_schema_.column_ids.empty()) {
SubstituteAndAppend(&header, "<th>RANGE ($0) PARTITION</th>",
EscapeForHtmlToString(
@@ -1140,8 +1189,46 @@ string PartitionSchema::PartitionTableHeader(const
Schema& schema) const {
string PartitionSchema::PartitionTableEntry(const Schema& schema,
const Partition& partition) const {
string entry;
- for (int32_t bucket : partition.hash_buckets_) {
- SubstituteAndAppend(&entry, "<td>$0</td>", bucket);
+ const auto* idx_ptr = FindOrNull(
+ hash_schema_idx_by_encoded_range_start_, partition.begin_.range_key());
+
+ if (idx_ptr) {
+ const auto& range = ranges_with_custom_hash_schemas_[*idx_ptr];
+ entry.append("<td>Range Specific</td>");
+ entry.append("<td>");
+ for (size_t i = 0; i < range.hash_schema.size(); i++) {
+ SubstituteAndAppend(&entry, "HASH ($0) PARTITIONS $1<br>",
+ EscapeForHtmlToString(ColumnIdsToColumnNames(
+ schema, range.hash_schema[i].column_ids)),
+ range.hash_schema[i].num_buckets);
+ }
+ entry.append("</td>");
+ entry.append("<td>");
+ for (size_t i = 0; i < range.hash_schema.size(); i++) {
+ SubstituteAndAppend(&entry, "HASH ($0) PARTITION: $1<br>",
+ EscapeForHtmlToString(ColumnIdsToColumnNames(
+ schema, range.hash_schema[i].column_ids)),
+ partition.hash_buckets_[i]);
+ }
+ entry.append("</td>");
+ } else {
+ entry.append("<td>Table Wide</td>");
+ entry.append("<td>");
+ for (size_t i = 0; i < hash_schema_.size(); i++) {
+ SubstituteAndAppend(&entry, "HASH ($0) PARTITIONS $1<br>",
+ EscapeForHtmlToString(ColumnIdsToColumnNames(
+ schema, hash_schema_[i].column_ids)),
+ hash_schema_[i].num_buckets);
+ }
+ entry.append("</td>");
+ entry.append("<td>");
+ for (size_t i = 0; i < hash_schema_.size(); i++) {
+ SubstituteAndAppend(&entry, "HASH ($0) PARTITION: $1<br>",
+ EscapeForHtmlToString(ColumnIdsToColumnNames(
+ schema, hash_schema_[i].column_ids)),
+ partition.hash_buckets_[i]);
+ }
+ entry.append("</td>");
}
if (!range_schema_.column_ids.empty()) {
diff --git a/src/kudu/common/partition.h b/src/kudu/common/partition.h
index ef4b48d3a..bcfb1f2d1 100644
--- a/src/kudu/common/partition.h
+++ b/src/kudu/common/partition.h
@@ -401,6 +401,14 @@ class PartitionSchema {
Slice upper_bound,
const Schema& schema) const;
+ // Returns a text description of the partition with the provided inclusive
+ // lower bound and exclusive upper bound along with custom hash schema for
+ // the partition if present. The custom hash schema is a space separated
+ // descriptions of hash dimensions.
+ std::string RangeWithCustomHashPartitionDebugString(Slice lower_bound,
+ Slice upper_bound,
+ const Schema& schema)
const;
+
// Returns a text description of this partition schema suitable for debug
printing.
//
// The partition schema is considered metadata, so partition bound
information
diff --git a/src/kudu/master/master-test.cc b/src/kudu/master/master-test.cc
index d6bdda02c..b82bbb1e0 100644
--- a/src/kudu/master/master-test.cc
+++ b/src/kudu/master/master-test.cc
@@ -157,6 +157,10 @@ class MasterTest : public KuduTest {
// but we have no tablet servers. Typically this would be disallowed.
FLAGS_catalog_manager_check_ts_count_for_create_table = false;
+ // Ensure the static pages are not available as tests are written based
+ // on this value of the flag
+ FLAGS_webserver_doc_root = "";
+
// Start master
mini_master_.reset(new MiniMaster(GetTestPath("Master"),
HostPort("127.0.0.1", 0)));
ASSERT_OK(mini_master_->Start());
@@ -956,7 +960,7 @@ TEST_P(AlterTableWithRangeSpecificHashSchema,
TestAlterTableWithDifferentHashDim
custom_range_hash_schema = {{{"key"}, 3, 0}};
}
- //Create AlterTableRequestPB and populate it for the alter table operation
+ // Create AlterTableRequestPB and populate it for the alter table operation
AlterTableRequestPB req;
AlterTableResponsePB resp;
RpcController controller;
@@ -1436,6 +1440,196 @@ TEST_F(MasterTest,
AlterTableAddDropRangeWithTableWideHashSchema) {
}
}
+TEST_F(MasterTest, MasterWebUIWithCustomHashPartitioning) {
+ constexpr const char* const kTableName = "master_webui_custom_hash_ps";
+ constexpr const char* const kCol0 = "c_int32";
+ constexpr const char* const kCol1 = "c_int64";
+ const Schema kTableSchema({ColumnSchema(kCol0, INT32),
+ ColumnSchema(kCol1, INT64)}, 2);
+ FLAGS_default_num_replicas = 1;
+
+ // Create a table with one range partition based on the table-wide hash
schema.
+ CreateTableResponsePB create_table_resp;
+ {
+ KuduPartialRow lower(&kTableSchema);
+ ASSERT_OK(lower.SetInt32(kCol0, 0));
+ ASSERT_OK(lower.SetInt64(kCol1, 0));
+ KuduPartialRow upper(&kTableSchema);
+ ASSERT_OK(upper.SetInt32(kCol0, 100));
+ ASSERT_OK(upper.SetInt64(kCol1, 100));
+ ASSERT_OK(CreateTable(
+ kTableName, kTableSchema, nullopt, nullopt, nullopt, {}, {{lower,
upper}},
+ {}, {{{kCol0}, 2, 0}, {{kCol1}, 2, 0}}, &create_table_resp));
+ }
+
+ // Get all the tablets of this table
+ std::vector<scoped_refptr<TableInfo>> tables;
+ {
+ CatalogManager::ScopedLeaderSharedLock l(master_->catalog_manager());
+ master_->catalog_manager()->GetAllTables(&tables);
+ }
+ ASSERT_EQ(1, tables.size());
+
+ vector<scoped_refptr<TabletInfo>> tablets;
+ tables.front()->GetAllTablets(&tablets);
+ ASSERT_EQ(4, tablets.size());
+ EasyCurl c;
+ faststring buf;
+ ASSERT_OK(c.FetchURL(Substitute("http://$0/table?id=$1",
+ mini_master_->bound_http_addr().ToString(),
+ create_table_resp.table_id()),
+ &buf));
+ string raw = buf.ToString();
+
+ // Check the "Partition Schema" section
+ ASSERT_STR_CONTAINS(raw,
+ "\"partition_schema\":\""
+ "HASH (c_int32) PARTITIONS 2,\\n"
+ "HASH (c_int64) PARTITIONS 2,\\n"
+ "RANGE (c_int32, c_int64) (\\n"
+ " PARTITION (0, 0) <= VALUES < (100, 100)\\n"
+ ")\"");
+
+ // Check the "Detail" table section
+ {
+ int k = 0;
+ for (int i = 0; i < 2; i++) {
+ for (int j = 0; j < 2; j++) {
+ ASSERT_STR_CONTAINS(raw,
+ Substitute(
+ "\"id\":\"$0\",\"partition_cols\":\"<td>Table
Wide</td>"
+ "<td>HASH (c_int32) PARTITIONS 2<br>"
+ "HASH (c_int64) PARTITIONS 2<br></td>"
+ "<td>HASH (c_int32) PARTITION: $1<br>"
+ "HASH (c_int64) PARTITION: $2<br></td>"
+ "<td>(0, 0) <= VALUES < (100,
100)</td>\"",
+ tablets[k++]->id(), i, j));
+ }
+ }
+ }
+
+ const auto& table_id = create_table_resp.table_id();
+ const HashSchema custom_hash_schema{{{kCol0}, 2, 0}, {{kCol1}, 3, 0}};
+
+ // Alter the table, adding a new range with custom hash schema.
+ {
+ AlterTableRequestPB req;
+ AlterTableResponsePB resp;
+ req.mutable_table()->set_table_name(kTableName);
+ req.mutable_table()->set_table_id(table_id);
+
+ // Add the required information on the table's schema:
+ // key and non-null columns must be present in the request.
+ {
+ ColumnSchemaPB* col0 = req.mutable_schema()->add_columns();
+ col0->set_name(kCol0);
+ col0->set_type(INT32);
+ col0->set_is_key(true);
+
+ ColumnSchemaPB* col1 = req.mutable_schema()->add_columns();
+ col1->set_name(kCol1);
+ col1->set_type(INT64);
+ col1->set_is_key(true);
+ }
+
+ AlterTableRequestPB::Step* step = req.add_alter_schema_steps();
+ step->set_type(AlterTableRequestPB::ADD_RANGE_PARTITION);
+ KuduPartialRow lower(&kTableSchema);
+ ASSERT_OK(lower.SetInt32(kCol0, 100));
+ ASSERT_OK(lower.SetInt64(kCol1, 100));
+ KuduPartialRow upper(&kTableSchema);
+ ASSERT_OK(upper.SetInt32(kCol0, 200));
+ ASSERT_OK(upper.SetInt64(kCol1, 200));
+ RowOperationsPBEncoder enc(
+ step->mutable_add_range_partition()->mutable_range_bounds());
+ enc.Add(RowOperationsPB::RANGE_LOWER_BOUND, lower);
+ enc.Add(RowOperationsPB::RANGE_UPPER_BOUND, upper);
+ for (const auto& hash_dimension: custom_hash_schema) {
+ auto* hash_dimension_pb = step->mutable_add_range_partition()->
+ mutable_custom_hash_schema()->add_hash_schema();
+ for (const auto& col_name: hash_dimension.columns) {
+ hash_dimension_pb->add_columns()->set_name(col_name);
+ }
+ hash_dimension_pb->set_num_buckets(hash_dimension.num_buckets);
+ hash_dimension_pb->set_seed(hash_dimension.seed);
+ }
+
+ RpcController ctl;
+ ASSERT_OK(proxy_->AlterTable(req, &resp, &ctl));
+ ASSERT_FALSE(resp.has_error())
+ << StatusFromPB(resp.error().status()).ToString();
+ }
+
+ ASSERT_OK(c.FetchURL(Substitute("http://$0/table?id=$1",
+ mini_master_->bound_http_addr().ToString(),
+ create_table_resp.table_id()),
+ &buf));
+ raw = buf.ToString();
+
+ // Check the "Partition Schema" section
+ ASSERT_STR_CONTAINS(raw, "\"partition_schema\":\""
+ "HASH (c_int32) PARTITIONS 2,\\n"
+ "HASH (c_int64) PARTITIONS 2,\\n"
+ "RANGE (c_int32, c_int64) (\\n"
+ " PARTITION (0, 0) <= VALUES < (100, 100),\\n"
+ " PARTITION (100, 100) <= VALUES < (200, 200) "
+ "HASH(c_int32) PARTITIONS 2 HASH(c_int64)
PARTITIONS 3\\n)");
+
+ {
+ CatalogManager::ScopedLeaderSharedLock l(master_->catalog_manager());
+ master_->catalog_manager()->GetAllTables(&tables);
+ }
+ ASSERT_EQ(1, tables.size());
+
+ tables.front()->GetAllTablets(&tablets);
+ // At this point we have the previously created 4 tables and now added 6
tablets
+ ASSERT_EQ(10, tablets.size());
+
+ // Check the "Detail" table section of all the 10 tablets present
+ for (int i = 0; i < 2; i++) {
+ ASSERT_STR_CONTAINS(raw,Substitute(
+ "\"id\":\"$0\",\"partition_cols\":\"<td>Table Wide</td>"
+ "<td>HASH (c_int32) PARTITIONS 2<br>"
+ "HASH (c_int64) PARTITIONS 2<br></td>"
+ "<td>HASH (c_int32) PARTITION: $1<br>"
+ "HASH (c_int64) PARTITION: 0<br></td>"
+ "<td>(0, 0) <= VALUES < (100, 100)</td>\"",
+ tablets[i*5+0]->id(), i));
+ ASSERT_STR_CONTAINS(raw,Substitute(
+ "\"id\":\"$0\",\"partition_cols\":\"<td>Range Specific</td>"
+ "<td>HASH (c_int32) PARTITIONS 2<br>"
+ "HASH (c_int64) PARTITIONS 3<br></td>"
+ "<td>HASH (c_int32) PARTITION: $1<br>"
+ "HASH (c_int64) PARTITION: 0<br></td>"
+ "<td>(100, 100) <= VALUES < (200, 200)</td>\"",
+ tablets[i*5+1]->id(), i));
+ ASSERT_STR_CONTAINS(raw,Substitute(
+ "\"id\":\"$0\",\"partition_cols\":\"<td>Table Wide</td>"
+ "<td>HASH (c_int32) PARTITIONS 2<br>"
+ "HASH (c_int64) PARTITIONS 2<br></td>"
+ "<td>HASH (c_int32) PARTITION: $1<br>"
+ "HASH (c_int64) PARTITION: 1<br></td>"
+ "<td>(0, 0) <= VALUES < (100, 100)</td>\"",
+ tablets[i*5+2]->id(), i));
+ ASSERT_STR_CONTAINS(raw,Substitute(
+ "\"id\":\"$0\",\"partition_cols\":\"<td>Range Specific</td>"
+ "<td>HASH (c_int32) PARTITIONS 2<br>"
+ "HASH (c_int64) PARTITIONS 3<br></td>"
+ "<td>HASH (c_int32) PARTITION: $1<br>"
+ "HASH (c_int64) PARTITION: 1<br></td>"
+ "<td>(100, 100) <= VALUES < (200, 200)</td>\"",
+ tablets[i*5+3]->id(), i));
+ ASSERT_STR_CONTAINS(raw,Substitute(
+ "\"id\":\"$0\",\"partition_cols\":\"<td>Range Specific</td>"
+ "<td>HASH (c_int32) PARTITIONS 2<br>"
+ "HASH (c_int64) PARTITIONS 3<br></td>"
+ "<td>HASH (c_int32) PARTITION: $1<br>"
+ "HASH (c_int64) PARTITION: 2<br></td>"
+ "<td>(100, 100) <= VALUES < (200, 200)</td>\"",
+ tablets[i*5+4]->id(), i));
+ }
+}
+
TEST_F(MasterTest, TestCreateTableCheckRangeInvariants) {
constexpr const char* const kTableName = "testtb";
const Schema kTableSchema({ ColumnSchema("key", INT32), ColumnSchema("val",
INT32) }, 1);
diff --git a/src/kudu/master/master_path_handlers.cc
b/src/kudu/master/master_path_handlers.cc
index 36ff3b174..fbff516d7 100644
--- a/src/kudu/master/master_path_handlers.cc
+++ b/src/kudu/master/master_path_handlers.cc
@@ -472,9 +472,9 @@ void MasterPathHandlers::HandleTablePage(const
Webserver::WebRequest& req,
partition.hash_buckets().end(),
[] (const int32_t& bucket) { return bucket == 0; })) {
range_partitions.emplace_back(
-
partition_schema.RangePartitionDebugString(partition.begin().range_key(),
-
partition.end().range_key(),
- schema));
+
partition_schema.RangeWithCustomHashPartitionDebugString(partition.begin().range_key(),
+
partition.end().range_key(),
+ schema));
}
// Combine the tablet details and partition info for each tablet.