This is an automated email from the ASF dual-hosted git repository.
mgreber 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 d2d0e0a92 KUDU-3608 REST API Follow-up
d2d0e0a92 is described below
commit d2d0e0a92975c847cf5db87d1b99c042d4abdf84
Author: gabriellalotz <[email protected]>
AuthorDate: Mon Mar 31 14:51:56 2025 +0000
KUDU-3608 REST API Follow-up
Follow-up on the REST API for Metadata Management commit
(https://gerrit.cloudera.org/c/21823/) by adding additional error
handling and refactoring. Improved error responses for table operations.
Refactored path handler functions for better readability and
consistency, including handling of authentication principal extraction.
Adjusted test cases to align with the changes and ensure correctness.
Change-Id: I3cdb14d5a932eb8d1ba209ca803731749117024e
Reviewed-on: http://gerrit.cloudera.org:8080/22744
Reviewed-by: Alexey Serbin <[email protected]>
Reviewed-by: Zoltan Chovan <[email protected]>
Tested-by: Marton Greber <[email protected]>
---
src/kudu/master/master.cc | 2 +-
src/kudu/master/rest_catalog-test.cc | 182 ++++++++++++++++----------
src/kudu/master/rest_catalog_path_handlers.cc | 105 +++++++++------
src/kudu/master/rest_catalog_path_handlers.h | 15 ++-
src/kudu/master/rest_catalog_test_base.h | 36 +++--
src/kudu/master/spnego_rest_catalog-test.cc | 34 ++---
6 files changed, 220 insertions(+), 154 deletions(-)
diff --git a/src/kudu/master/master.cc b/src/kudu/master/master.cc
index 5472c86d7..c0fa3bf1c 100644
--- a/src/kudu/master/master.cc
+++ b/src/kudu/master/master.cc
@@ -316,7 +316,7 @@ Status Master::Init() {
if (web_server_) {
RETURN_NOT_OK(path_handlers_->Register(web_server_.get()));
if (rest_catalog_path_handlers_) {
- RETURN_NOT_OK(rest_catalog_path_handlers_->Register(web_server_.get()));
+ rest_catalog_path_handlers_->Register(web_server_.get());
}
}
diff --git a/src/kudu/master/rest_catalog-test.cc
b/src/kudu/master/rest_catalog-test.cc
index 2e09a6fd0..6e17ff9c7 100644
--- a/src/kudu/master/rest_catalog-test.cc
+++ b/src/kudu/master/rest_catalog-test.cc
@@ -109,30 +109,59 @@ class RestCatalogTest : public RestCatalogTestBase {
"{\"range_schema\":{\"columns\":[{\"id\":0}]}}";
};
-TEST_F(RestCatalogTest, TestInvalidMethod) {
- EasyCurl c;
- c.set_custom_method("DELETE");
- faststring buf;
- Status s = c.FetchURL(
- Substitute("http://$0/api/v1/tables",
cluster_->mini_master()->bound_http_addr().ToString()),
- &buf);
- ASSERT_STR_CONTAINS(s.ToString(), "HTTP 405");
- ASSERT_STR_CONTAINS(buf.ToString(), "{\"error\":\"Method not allowed\"}");
+TEST_F(RestCatalogTest, TestInvalidMethods) {
+ const std::vector<std::string> invalid_methods = {"PUT", "DELETE",
"CONNECT"};
+
+ for (const auto& method : invalid_methods) {
+ EasyCurl c;
+ c.set_custom_method(method);
+ faststring buf;
+ Status s;
+ if (method == "PUT") {
+ s = c.PostToURL(Substitute("http://$0/api/v1/tables",
+
cluster_->mini_master()->bound_http_addr().ToString()),
+ "{\"name\":\"test_table\"}",
+ &buf);
+ } else {
+ s = c.FetchURL(Substitute("http://$0/api/v1/tables",
+
cluster_->mini_master()->bound_http_addr().ToString()),
+ &buf);
+ }
+ ASSERT_TRUE(!s.ok());
+ ASSERT_STR_CONTAINS(s.ToString(), "HTTP 405") << "Failed for method: " <<
method;
+ ASSERT_STR_CONTAINS(buf.ToString(), "{\"error\":\"Method not allowed\"}")
+ << "Failed for method: " << method;
+ }
}
TEST_F(RestCatalogTest, TestInvalidMethodOnTableEndpoint) {
ASSERT_OK(CreateTestTable());
string table_id;
ASSERT_OK(GetTableId(kTableName, &table_id));
- EasyCurl c;
- c.set_custom_method("CONNECT");
- faststring buf;
- Status s = c.FetchURL(Substitute("http://$0/api/v1/tables/$1",
-
cluster_->mini_master()->bound_http_addr().ToString(),
- table_id),
- &buf);
- ASSERT_STR_CONTAINS(s.ToString(), "HTTP 405");
- ASSERT_STR_CONTAINS(buf.ToString(), "{\"error\":\"Method not allowed\"}");
+ const std::vector<std::string> invalid_methods = {"POST", "CONNECT"};
+
+ for (const auto& method : invalid_methods) {
+ EasyCurl c;
+ c.set_custom_method(method);
+ faststring buf;
+ Status s;
+ if (method == "POST") {
+ s = c.PostToURL(Substitute("http://$0/api/v1/tables/$1",
+
cluster_->mini_master()->bound_http_addr().ToString(),
+ table_id),
+ "{\"name\":\"test_table\"}",
+ &buf);
+ } else {
+ s = c.FetchURL(Substitute("http://$0/api/v1/tables/$1",
+
cluster_->mini_master()->bound_http_addr().ToString(),
+ table_id),
+ &buf);
+ }
+ ASSERT_TRUE(!s.ok());
+ ASSERT_STR_CONTAINS(s.ToString(), "HTTP 405") << "Failed for method: " <<
method;
+ ASSERT_STR_CONTAINS(buf.ToString(), "{\"error\":\"Method not allowed\"}")
+ << "Failed for method: " << method;
+ }
}
TEST_F(RestCatalogTest, TestGetTablesZeroTables) {
@@ -158,6 +187,16 @@ TEST_F(RestCatalogTest, TestGetTablesOneTable) {
Substitute("{\"tables\":[{\"table_id\":\"$0\",\"table_name\":\"test_table\"}]}",
table_id));
}
+TEST_F(RestCatalogTest, TestGetTableNoId) {
+ EasyCurl c;
+ faststring buf;
+ Status s = c.FetchURL(Substitute("http://$0/api/v1/tables/",
+
cluster_->mini_master()->bound_http_addr().ToString()),
+ &buf);
+ ASSERT_TRUE(!s.ok());
+ ASSERT_STR_CONTAINS(s.ToString(), "HTTP 404");
+}
+
TEST_F(RestCatalogTest, TestGetTableEndpoint) {
ASSERT_OK(CreateTestTable());
string table_id;
@@ -192,6 +231,7 @@ TEST_F(RestCatalogTest, TestGetTableNotFound) {
Status s =
c.FetchURL(Substitute("http://$0/api/v1/tables/05755b4c0c7640cd9f6673c2530a4e78",
cluster_->mini_master()->bound_http_addr().ToString()),
&buf);
+ ASSERT_TRUE(!s.ok());
ASSERT_STR_CONTAINS(s.ToString(), "HTTP 404");
ASSERT_STR_CONTAINS(buf.ToString(), "{\"error\":\"Table not found\"}");
}
@@ -202,6 +242,7 @@ TEST_F(RestCatalogTest, TestGetTableMalformedId) {
Status s = c.FetchURL(Substitute("http://$0/api/v1/tables/123",
cluster_->mini_master()->bound_http_addr().ToString()),
&buf);
+ ASSERT_TRUE(!s.ok());
ASSERT_STR_CONTAINS(s.ToString(), "HTTP 400");
ASSERT_STR_CONTAINS(buf.ToString(),
"{\"error\":\"Invalid table ID: must be exactly 32
characters long.\"}");
@@ -211,10 +252,10 @@ TEST_F(RestCatalogTest, TestDeleteTableNonExistent) {
EasyCurl c;
faststring buf;
c.set_custom_method("DELETE");
- c.set_verbose(true);
Status s =
c.FetchURL(Substitute("http://$0/api/v1/tables/05755b4c0c7640cd9f6673c2530a4e78",
cluster_->mini_master()->bound_http_addr().ToString()),
&buf);
+ ASSERT_TRUE(!s.ok());
ASSERT_STR_CONTAINS(s.ToString(), "HTTP 404");
ASSERT_STR_CONTAINS(buf.ToString(), "{\"error\":\"Table not found\"}");
}
@@ -226,14 +267,13 @@ TEST_F(RestCatalogTest, TestDeleteTableEndpoint) {
EasyCurl c;
faststring buf;
c.set_custom_method("DELETE");
- Status s = c.FetchURL(Substitute("http://$0/api/v1/tables/$1",
-
cluster_->mini_master()->bound_http_addr().ToString(),
- table_id),
- &buf);
- ASSERT_STR_CONTAINS(s.ToString(), "OK");
+ ASSERT_OK(c.FetchURL(Substitute("http://$0/api/v1/tables/$1",
+
cluster_->mini_master()->bound_http_addr().ToString(),
+ table_id),
+ &buf));
ASSERT_TRUE(buf.size() == 0);
shared_ptr<KuduTable> table;
- s = client_->OpenTable(kTableName, &table);
+ Status s = client_->OpenTable(kTableName, &table);
ASSERT_STR_CONTAINS(s.ToString(),
"Not found: the table does not exist: table_name:
\"test_table\"");
ASSERT_TRUE(table == nullptr);
@@ -246,6 +286,7 @@ TEST_F(RestCatalogTest, TestDeleteTableMalformedId) {
Status s = c.FetchURL(Substitute("http://$0/api/v1/tables/123",
cluster_->mini_master()->bound_http_addr().ToString()),
&buf);
+ ASSERT_TRUE(!s.ok());
ASSERT_STR_CONTAINS(s.ToString(), "HTTP 400");
ASSERT_STR_CONTAINS(buf.ToString(),
"{\"error\":\"Invalid table ID: must be exactly 32
characters long.\"}");
@@ -258,6 +299,7 @@ TEST_F(RestCatalogTest, TestPostTableNoData) {
Status s = c.FetchURL(
Substitute("http://$0/api/v1/tables",
cluster_->mini_master()->bound_http_addr().ToString()),
&buf);
+ ASSERT_TRUE(!s.ok());
ASSERT_STR_CONTAINS(s.ToString(), "HTTP 411");
}
@@ -268,6 +310,7 @@ TEST_F(RestCatalogTest, TestPostTableMalformedData) {
Substitute("http://$0/api/v1/tables",
cluster_->mini_master()->bound_http_addr().ToString()),
"{\"name\":\"test_table\"}",
&buf);
+ ASSERT_TRUE(!s.ok());
ASSERT_STR_CONTAINS(s.ToString(), "HTTP 400");
ASSERT_STR_CONTAINS(
buf.ToString(),
@@ -278,7 +321,7 @@ TEST_F(RestCatalogTest, TestPostTableEndpoint) {
EasyCurl c;
faststring buf;
c.set_custom_method("POST");
- Status s = c.PostToURL(
+ ASSERT_OK(c.PostToURL(
Substitute("http://$0/api/v1/tables",
cluster_->mini_master()->bound_http_addr().ToString()),
R"({
"name": "test_table",
@@ -295,8 +338,7 @@ TEST_F(RestCatalogTest, TestPostTableEndpoint) {
},
"num_replicas": 1
})",
- &buf);
- ASSERT_STR_CONTAINS(s.ToString(), "OK");
+ &buf));
string table_id;
ASSERT_OK(GetTableId(kTableName, &table_id));
shared_ptr<KuduTable> table;
@@ -314,8 +356,8 @@ TEST_F(RestCatalogTest, TestPostTableEndpoint) {
partition_schema,
table->owner()));
ASSERT_TRUE(table != nullptr);
- ASSERT_EQ(table->name(), kTableName);
- ASSERT_EQ(table->num_replicas(), 1);
+ ASSERT_EQ(kTableName, table->name());
+ ASSERT_EQ(1, table->num_replicas());
}
TEST_F(RestCatalogTest, TestPutTableMalformedId) {
@@ -326,6 +368,7 @@ TEST_F(RestCatalogTest, TestPutTableMalformedId) {
cluster_->mini_master()->bound_http_addr().ToString()),
"{\"name\":\"test_table\"}",
&buf);
+ ASSERT_TRUE(!s.ok());
ASSERT_STR_CONTAINS(s.ToString(), "HTTP 400");
ASSERT_STR_CONTAINS(buf.ToString(),
"{\"error\":\"Invalid table ID: must be exactly 32
characters long.\"}");
@@ -342,6 +385,7 @@ TEST_F(RestCatalogTest, TestPutTableNoData) {
cluster_->mini_master()->bound_http_addr().ToString(),
table_id),
&buf);
+ ASSERT_TRUE(!s.ok());
ASSERT_STR_CONTAINS(s.ToString(), "HTTP 411");
}
@@ -357,6 +401,7 @@ TEST_F(RestCatalogTest, TestPutTableMalformedData) {
table_id),
"{\"name\":\"test_table\"}",
&buf);
+ ASSERT_TRUE(!s.ok());
ASSERT_STR_CONTAINS(s.ToString(), "HTTP 400");
ASSERT_STR_CONTAINS(
buf.ToString(),
@@ -388,6 +433,7 @@ TEST_F(RestCatalogTest, TestPutTableNonExistent) {
}
)",
&buf);
+ ASSERT_TRUE(!s.ok());
ASSERT_STR_CONTAINS(s.ToString(), "HTTP 404");
ASSERT_STR_CONTAINS(buf.ToString(), "{\"error\":\"Table not found\"}");
}
@@ -399,10 +445,10 @@ TEST_F(RestCatalogTest, TestPutTableEndpointAddColumn) {
EasyCurl c;
faststring buf;
c.set_custom_method("PUT");
- Status s = c.PostToURL(Substitute("http://$0/api/v1/tables/$1",
-
cluster_->mini_master()->bound_http_addr().ToString(),
- table_id),
- R"({
+ ASSERT_OK(c.PostToURL(Substitute("http://$0/api/v1/tables/$1",
+
cluster_->mini_master()->bound_http_addr().ToString(),
+ table_id),
+ R"({
"table": {
"table_name": "test_table"
},
@@ -420,10 +466,9 @@ TEST_F(RestCatalogTest, TestPutTableEndpointAddColumn) {
]
}
)",
- &buf);
- ASSERT_STR_CONTAINS(s.ToString(), "OK");
+ &buf));
shared_ptr<KuduTable> table;
- s = client_->OpenTable(kTableName, &table);
+ ASSERT_OK(client_->OpenTable(kTableName, &table));
int first_column_id = FindColumnId(buf.ToString());
ASSERT_NE(first_column_id, -1) << "Column ID not found in the schema";
string partition_schema =
@@ -436,12 +481,11 @@ TEST_F(RestCatalogTest, TestPutTableEndpointAddColumn) {
ConstructTableSchema(first_column_id, true),
partition_schema,
table->owner()));
- ASSERT_TRUE(s.ok());
const KuduSchema& schema = table->schema();
- ASSERT_EQ(schema.num_columns(), 3);
- ASSERT_EQ(schema.Column(0).name(), "key");
- ASSERT_EQ(schema.Column(1).name(), "int_val");
- ASSERT_EQ(schema.Column(2).name(), "new_column");
+ ASSERT_EQ(3, schema.num_columns());
+ ASSERT_EQ("key", schema.Column(0).name());
+ ASSERT_EQ("int_val", schema.Column(1).name());
+ ASSERT_EQ("new_column", schema.Column(2).name());
}
TEST_F(RestCatalogTest, TestPutTableEndpointRenameColumn) {
@@ -451,10 +495,10 @@ TEST_F(RestCatalogTest, TestPutTableEndpointRenameColumn)
{
EasyCurl c;
faststring buf;
c.set_custom_method("PUT");
- Status s = c.PostToURL(Substitute("http://$0/api/v1/tables/$1",
-
cluster_->mini_master()->bound_http_addr().ToString(),
- table_id),
- R"({
+ ASSERT_OK(c.PostToURL(Substitute("http://$0/api/v1/tables/$1",
+
cluster_->mini_master()->bound_http_addr().ToString(),
+ table_id),
+ R"({
"table": {
"table_name": "test_table"
},
@@ -469,15 +513,13 @@ TEST_F(RestCatalogTest, TestPutTableEndpointRenameColumn)
{
]
}
)",
- &buf);
- ASSERT_STR_CONTAINS(s.ToString(), "OK");
+ &buf));
shared_ptr<KuduTable> table;
- s = client_->OpenTable(kTableName, &table);
- ASSERT_TRUE(s.ok());
+ ASSERT_OK(client_->OpenTable(kTableName, &table));
const KuduSchema& schema = table->schema();
- ASSERT_EQ(schema.num_columns(), 2);
- ASSERT_EQ(schema.Column(0).name(), "key");
- ASSERT_EQ(schema.Column(1).name(), "new_int_val");
+ ASSERT_EQ(2, schema.num_columns());
+ ASSERT_EQ("key", schema.Column(0).name());
+ ASSERT_EQ("new_int_val", schema.Column(1).name());
}
TEST_F(RestCatalogTest, TestPutTableEndpointDropColumn) {
@@ -487,10 +529,10 @@ TEST_F(RestCatalogTest, TestPutTableEndpointDropColumn) {
EasyCurl c;
faststring buf;
c.set_custom_method("PUT");
- Status s = c.PostToURL(Substitute("http://$0/api/v1/tables/$1",
-
cluster_->mini_master()->bound_http_addr().ToString(),
- table_id),
- R"({
+ ASSERT_OK(c.PostToURL(Substitute("http://$0/api/v1/tables/$1",
+
cluster_->mini_master()->bound_http_addr().ToString(),
+ table_id),
+ R"({
"table": {
"table_name": "test_table"
},
@@ -504,14 +546,12 @@ TEST_F(RestCatalogTest, TestPutTableEndpointDropColumn) {
]
}
)",
- &buf);
- ASSERT_STR_CONTAINS(s.ToString(), "OK");
+ &buf));
shared_ptr<KuduTable> table;
- s = client_->OpenTable(kTableName, &table);
- ASSERT_TRUE(s.ok());
+ ASSERT_OK(client_->OpenTable(kTableName, &table));
const KuduSchema& schema = table->schema();
- ASSERT_EQ(schema.num_columns(), 1);
- ASSERT_EQ(schema.Column(0).name(), "key");
+ ASSERT_EQ(1, schema.num_columns());
+ ASSERT_EQ("key", schema.Column(0).name());
}
TEST_F(RestCatalogTest, TestPutTableEndpointChangeOwner) {
@@ -521,22 +561,20 @@ TEST_F(RestCatalogTest, TestPutTableEndpointChangeOwner) {
EasyCurl c;
faststring buf;
c.set_custom_method("PUT");
- Status s = c.PostToURL(Substitute("http://$0/api/v1/tables/$1",
-
cluster_->mini_master()->bound_http_addr().ToString(),
- table_id),
- R"({
+ ASSERT_OK(c.PostToURL(Substitute("http://$0/api/v1/tables/$1",
+
cluster_->mini_master()->bound_http_addr().ToString(),
+ table_id),
+ R"({
"table": {
"table_name": "test_table"
},
"new_table_owner": "new_owner"
}
)",
- &buf);
- ASSERT_STR_CONTAINS(s.ToString(), "OK");
+ &buf));
shared_ptr<KuduTable> table;
- s = client_->OpenTable(kTableName, &table);
- ASSERT_TRUE(s.ok());
- ASSERT_EQ(table->owner(), "new_owner");
+ ASSERT_OK(client_->OpenTable(kTableName, &table));
+ ASSERT_EQ("new_owner", table->owner());
}
} // namespace master
diff --git a/src/kudu/master/rest_catalog_path_handlers.cc
b/src/kudu/master/rest_catalog_path_handlers.cc
index 078c98beb..ee5ecd626 100644
--- a/src/kudu/master/rest_catalog_path_handlers.cc
+++ b/src/kudu/master/rest_catalog_path_handlers.cc
@@ -20,10 +20,12 @@
#include <functional>
#include <optional>
#include <string>
+#include <unordered_map>
+#include <utility>
-#include "google/protobuf/util/json_util.h"
#include <gflags/gflags.h>
#include <google/protobuf/stubs/status.h>
+#include <google/protobuf/util/json_util.h>
#include "kudu/common/common.pb.h"
#include "kudu/gutil/ref_counted.h"
@@ -35,6 +37,7 @@
#include "kudu/util/flag_tags.h"
#include "kudu/util/jsonwriter.h"
#include "kudu/util/monotime.h"
+#include "kudu/util/status.h"
#include "kudu/util/web_callback_registry.h"
// We only use macros here to maintain cohesion with the existing
RETURN_NOT_OK-style pattern.
@@ -52,6 +55,9 @@
return retval; \
}
+#define RETURN_JSON_ERROR_FROM_STATUS(jw, status, status_code) \
+ RETURN_JSON_ERROR_VAL(jw, (status).ToString(), status_code,
GetHttpCodeFromStatus(status), void())
+
DEFINE_int32(rest_catalog_default_request_timeout_ms, 30 * 1000, "Default
request timeout in ms");
TAG_FLAG(rest_catalog_default_request_timeout_ms, advanced);
TAG_FLAG(rest_catalog_default_request_timeout_ms, runtime);
@@ -66,30 +72,45 @@ using strings::Substitute;
namespace kudu {
namespace master {
-RestCatalogPathHandlers::~RestCatalogPathHandlers() {}
-
-static bool CheckIsInitializedAndIsLeader(JsonWriter* jw,
+static bool CheckIsInitializedAndIsLeader(JsonWriter& jw, // NOLINT
JsonWriter cannot be const
const
CatalogManager::ScopedLeaderSharedLock& l,
- HttpStatusCode* status_code) {
+ HttpStatusCode& status_code //
NOLINT
+) {
if (!l.catalog_status().ok()) {
- RETURN_JSON_ERROR_VAL(*jw,
- l.catalog_status().ToString(),
- *status_code,
- HttpStatusCode::ServiceUnavailable,
- false);
+ RETURN_JSON_ERROR_VAL(
+ jw, l.catalog_status().ToString(), status_code,
HttpStatusCode::ServiceUnavailable, false);
}
if (!l.leader_status().ok()) {
RETURN_JSON_ERROR_VAL(
- *jw, "Master is not the leader", *status_code,
HttpStatusCode::ServiceUnavailable, false);
+ jw, "Master is not the leader", status_code,
HttpStatusCode::InternalServerError, false);
}
return true;
}
+static HttpStatusCode GetHttpCodeFromStatus(const Status& status) {
+ DCHECK(!status.ok());
+ if (status.IsInvalidArgument() || status.IsAlreadyPresent()) {
+ return HttpStatusCode::BadRequest;
+ }
+ if (status.IsNotFound()) {
+ return HttpStatusCode::NotFound;
+ }
+ if (status.IsIllegalState() || status.IsServiceUnavailable()) {
+ return HttpStatusCode::ServiceUnavailable;
+ }
+ return HttpStatusCode::InternalServerError;
+}
+
void RestCatalogPathHandlers::HandleApiTableEndpoint(const
Webserver::WebRequest& req,
Webserver::PrerenderedWebResponse* resp) {
- string table_id = req.path_params.at("table_id");
ostringstream* output = &resp->output;
JsonWriter jw(output, JsonWriter::COMPACT);
+ string table_id;
+ auto table_id_it = req.path_params.find("table_id");
+ if (table_id_it == req.path_params.end()) {
+ RETURN_JSON_ERROR(jw, "Table ID not provided", resp->status_code,
HttpStatusCode::BadRequest);
+ }
+ table_id = table_id_it->second;
if (table_id.length() != 32) {
RETURN_JSON_ERROR(jw,
@@ -98,14 +119,14 @@ void RestCatalogPathHandlers::HandleApiTableEndpoint(const
Webserver::WebRequest
HttpStatusCode::BadRequest);
}
CatalogManager::ScopedLeaderSharedLock l(master_->catalog_manager());
- if (!CheckIsInitializedAndIsLeader(&jw, l, &resp->status_code)) {
+ if (!CheckIsInitializedAndIsLeader(jw, l, resp->status_code)) {
return;
}
scoped_refptr<TableInfo> table;
Status status = master_->catalog_manager()->GetTableInfo(table_id, &table);
if (!status.ok()) {
- RETURN_JSON_ERROR(jw, status.ToString(), resp->status_code,
HttpStatusCode::ServiceUnavailable);
+ RETURN_JSON_ERROR_FROM_STATUS(jw, status, resp->status_code);
}
if (!table) {
@@ -129,7 +150,7 @@ void RestCatalogPathHandlers::HandleApiTablesEndpoint(const
Webserver::WebReques
ostringstream* output = &resp->output;
JsonWriter jw(output, JsonWriter::COMPACT);
CatalogManager::ScopedLeaderSharedLock l(master_->catalog_manager());
- if (!CheckIsInitializedAndIsLeader(&jw, l, &resp->status_code)) {
+ if (!CheckIsInitializedAndIsLeader(jw, l, resp->status_code)) {
return;
}
@@ -143,17 +164,17 @@ void
RestCatalogPathHandlers::HandleApiTablesEndpoint(const Webserver::WebReques
}
}
-void RestCatalogPathHandlers::HandleGetTables(std::ostringstream* output,
+void RestCatalogPathHandlers::HandleGetTables(ostringstream* output,
const Webserver::WebRequest& req,
HttpStatusCode* status_code) {
ListTablesRequestPB request;
ListTablesResponsePB response;
- std::optional<std::string> user = req.authn_principal.empty() ? "default" :
req.authn_principal;
+ optional<string> user = req.authn_principal.empty() ? "default" :
req.authn_principal;
Status status = master_->catalog_manager()->ListTables(&request, &response,
user);
JsonWriter jw(output, JsonWriter::COMPACT);
if (!status.ok()) {
- RETURN_JSON_ERROR(jw, status.ToString(), *status_code,
HttpStatusCode::ServiceUnavailable);
+ RETURN_JSON_ERROR_FROM_STATUS(jw, status, *status_code);
}
jw.StartObject();
jw.String("tables");
@@ -172,7 +193,7 @@ void
RestCatalogPathHandlers::HandleGetTables(std::ostringstream* output,
*status_code = HttpStatusCode::Ok;
}
-void RestCatalogPathHandlers::HandlePostTables(std::ostringstream* output,
+void RestCatalogPathHandlers::HandlePostTables(ostringstream* output,
const Webserver::WebRequest&
req,
HttpStatusCode* status_code) {
CreateTableRequestPB request;
@@ -190,11 +211,11 @@ void
RestCatalogPathHandlers::HandlePostTables(std::ostringstream* output,
*status_code,
HttpStatusCode::BadRequest);
}
- std::optional<std::string> user = req.authn_principal.empty() ? "default" :
req.authn_principal;
+ optional<string> user = req.authn_principal.empty() ? "default" :
req.authn_principal;
Status status = master_->catalog_manager()->CreateTableWithUser(&request,
&response, user);
if (!status.ok()) {
- RETURN_JSON_ERROR(jw, status.ToString(), *status_code,
HttpStatusCode::ServiceUnavailable);
+ RETURN_JSON_ERROR_FROM_STATUS(jw, status, *status_code);
}
IsCreateTableDoneRequestPB check_req;
@@ -207,18 +228,18 @@ void
RestCatalogPathHandlers::HandlePostTables(std::ostringstream* output,
status = master_->catalog_manager()->IsCreateTableDone(&check_req,
&check_resp, user);
if (!status.ok()) {
- RETURN_JSON_ERROR(jw, status.ToString(), *status_code,
HttpStatusCode::ServiceUnavailable);
+ RETURN_JSON_ERROR_FROM_STATUS(jw, status, *status_code);
}
if (check_resp.has_error()) {
RETURN_JSON_ERROR(jw,
check_resp.error().ShortDebugString(),
*status_code,
- HttpStatusCode::ServiceUnavailable);
+ HttpStatusCode::InternalServerError);
}
if (check_resp.done()) {
- PrintTableObject(output, response.table_id());
+ PrintTableObject(output, response.table_id(), status_code);
*status_code = HttpStatusCode::Created;
return;
}
@@ -227,18 +248,18 @@ void
RestCatalogPathHandlers::HandlePostTables(std::ostringstream* output,
RETURN_JSON_ERROR(jw,
"Create table timed out while waiting for operation
completion",
*status_code,
- HttpStatusCode::ServiceUnavailable);
+ HttpStatusCode::InternalServerError);
}
-void RestCatalogPathHandlers::HandleGetTable(std::ostringstream* output,
+void RestCatalogPathHandlers::HandleGetTable(ostringstream* output,
const Webserver::WebRequest& req,
HttpStatusCode* status_code) {
string table_id = req.path_params.at("table_id");
- PrintTableObject(output, table_id);
+ PrintTableObject(output, table_id, status_code);
*status_code = HttpStatusCode::Ok;
}
-void RestCatalogPathHandlers::HandlePutTable(std::ostringstream* output,
+void RestCatalogPathHandlers::HandlePutTable(ostringstream* output,
const Webserver::WebRequest& req,
HttpStatusCode* status_code) {
string table_id = req.path_params.at("table_id");
@@ -259,11 +280,11 @@ void
RestCatalogPathHandlers::HandlePutTable(std::ostringstream* output,
HttpStatusCode::BadRequest);
}
- std::optional<std::string> user = req.authn_principal.empty() ? "default" :
req.authn_principal;
+ optional<string> user = req.authn_principal.empty() ? "default" :
req.authn_principal;
Status status = master_->catalog_manager()->AlterTableWithUser(request,
&response, user);
if (!status.ok()) {
- RETURN_JSON_ERROR(jw, status.ToString(), *status_code,
HttpStatusCode::ServiceUnavailable);
+ RETURN_JSON_ERROR_FROM_STATUS(jw, status, *status_code);
}
IsAlterTableDoneRequestPB check_req;
@@ -276,18 +297,18 @@ void
RestCatalogPathHandlers::HandlePutTable(std::ostringstream* output,
status = master_->catalog_manager()->IsAlterTableDone(&check_req,
&check_resp, user);
if (!status.ok()) {
- RETURN_JSON_ERROR(jw, status.ToString(), *status_code,
HttpStatusCode::ServiceUnavailable);
+ RETURN_JSON_ERROR_FROM_STATUS(jw, status, *status_code);
}
if (check_resp.has_error()) {
RETURN_JSON_ERROR(jw,
check_resp.error().ShortDebugString(),
*status_code,
- HttpStatusCode::ServiceUnavailable);
+ HttpStatusCode::InternalServerError);
}
if (check_resp.done()) {
- PrintTableObject(output, table_id);
+ PrintTableObject(output, table_id, status_code);
*status_code = HttpStatusCode::Ok;
return;
}
@@ -296,10 +317,10 @@ void
RestCatalogPathHandlers::HandlePutTable(std::ostringstream* output,
RETURN_JSON_ERROR(jw,
"Alter table timed out while waiting for operation
completion",
*status_code,
- HttpStatusCode::ServiceUnavailable);
+ HttpStatusCode::InternalServerError);
}
-void RestCatalogPathHandlers::HandleDeleteTable(std::ostringstream* output,
+void RestCatalogPathHandlers::HandleDeleteTable(ostringstream* output,
const Webserver::WebRequest&
req,
HttpStatusCode* status_code) {
string table_id = req.path_params.at("table_id");
@@ -307,20 +328,25 @@ void
RestCatalogPathHandlers::HandleDeleteTable(std::ostringstream* output,
DeleteTableResponsePB response;
request.mutable_table()->set_table_id(table_id);
JsonWriter jw(output, JsonWriter::COMPACT);
- std::optional<std::string> user = req.authn_principal.empty() ? "default" :
req.authn_principal;
+ optional<string> user = req.authn_principal.empty() ? "default" :
req.authn_principal;
Status status = master_->catalog_manager()->DeleteTableWithUser(request,
&response, user);
if (status.ok()) {
*status_code = HttpStatusCode::NoContent;
} else {
- RETURN_JSON_ERROR(jw, status.ToString(), *status_code,
HttpStatusCode::ServiceUnavailable);
+ RETURN_JSON_ERROR_FROM_STATUS(jw, status, *status_code);
}
}
-void RestCatalogPathHandlers::PrintTableObject(std::ostringstream* output,
const string& table_id) {
+void RestCatalogPathHandlers::PrintTableObject(ostringstream* output,
+ const string& table_id,
+ HttpStatusCode* status_code) {
scoped_refptr<TableInfo> table;
Status status = master_->catalog_manager()->GetTableInfo(table_id, &table);
JsonWriter jw(output, JsonWriter::COMPACT);
+ if (!status.ok()) {
+ RETURN_JSON_ERROR_FROM_STATUS(jw, status, *status_code);
+ }
jw.StartObject();
{
@@ -343,7 +369,7 @@ void
RestCatalogPathHandlers::PrintTableObject(std::ostringstream* output, const
jw.EndObject();
}
-Status RestCatalogPathHandlers::Register(Webserver* server) {
+void RestCatalogPathHandlers::Register(Webserver* server) {
server->RegisterPrerenderedPathHandler(
"/api/v1/tables/<table_id>",
"",
@@ -360,7 +386,6 @@ Status RestCatalogPathHandlers::Register(Webserver* server)
{
},
StyleMode::JSON,
false);
- return Status::OK();
}
} // namespace master
diff --git a/src/kudu/master/rest_catalog_path_handlers.h
b/src/kudu/master/rest_catalog_path_handlers.h
index 079c000d2..a4f0d97ce 100644
--- a/src/kudu/master/rest_catalog_path_handlers.h
+++ b/src/kudu/master/rest_catalog_path_handlers.h
@@ -20,9 +20,10 @@
#include <iosfwd>
#include <string>
+#include <glog/logging.h>
+
#include "kudu/gutil/macros.h"
#include "kudu/server/webserver.h"
-#include "kudu/util/status.h"
#include "kudu/util/web_callback_registry.h"
namespace kudu {
@@ -31,13 +32,13 @@ namespace master {
class Master;
-class RestCatalogPathHandlers {
+class RestCatalogPathHandlers final {
public:
- explicit RestCatalogPathHandlers(Master* master) : master_(master) {}
+ explicit RestCatalogPathHandlers(Master* master) :
master_(DCHECK_NOTNULL(master)) {}
- ~RestCatalogPathHandlers();
+ ~RestCatalogPathHandlers() = default;
- Status Register(Webserver* server);
+ void Register(Webserver* server);
private:
void HandleApiTableEndpoint(const Webserver::WebRequest& req,
@@ -63,7 +64,9 @@ class RestCatalogPathHandlers {
HttpStatusCode* status_code);
// Print a JSON object representing a table to 'output'.
- void PrintTableObject(std::ostringstream* output, const std::string&
table_id);
+ void PrintTableObject(std::ostringstream* output,
+ const std::string& table_id,
+ HttpStatusCode* status_code);
Master* master_;
DISALLOW_COPY_AND_ASSIGN(RestCatalogPathHandlers);
diff --git a/src/kudu/master/rest_catalog_test_base.h
b/src/kudu/master/rest_catalog_test_base.h
index 4047c9532..6522f8ac1 100644
--- a/src/kudu/master/rest_catalog_test_base.h
+++ b/src/kudu/master/rest_catalog_test_base.h
@@ -40,50 +40,46 @@
#include "kudu/util/test_macros.h"
#include "kudu/util/test_util.h"
-DECLARE_bool(enable_rest_api);
-
namespace kudu {
namespace master {
class RestCatalogTestBase : public KuduTest {
protected:
- kudu::Status CreateTestTable(const std::string& owner = "") {
- kudu::client::KuduSchema schema;
- kudu::client::KuduSchemaBuilder b;
-
b.AddColumn("key")->Type(kudu::client::KuduColumnSchema::INT32)->NotNull()->PrimaryKey();
-
b.AddColumn("int_val")->Type(kudu::client::KuduColumnSchema::INT32)->NotNull();
+ Status CreateTestTable(const std::string& owner = "") {
+ client::KuduSchema schema;
+ client::KuduSchemaBuilder b;
+
b.AddColumn("key")->Type(client::KuduColumnSchema::INT32)->NotNull()->PrimaryKey();
+ b.AddColumn("int_val")->Type(client::KuduColumnSchema::INT32)->NotNull();
RETURN_NOT_OK(b.Build(&schema));
- std::vector<std::string> columnNames;
- columnNames.emplace_back("key");
+ const std::vector<std::string> column_names{ "key" };
// Set the schema and range partition columns.
- std::unique_ptr<kudu::client::KuduTableCreator>
tableCreator(client_->NewTableCreator());
-
tableCreator->table_name(kTableName).schema(&schema).set_range_partition_columns(columnNames);
+ std::unique_ptr<client::KuduTableCreator>
table_creator(client_->NewTableCreator());
+
table_creator->table_name(kTableName).schema(&schema).set_range_partition_columns(column_names);
// Generate and add the range partition splits for the table.
- int32_t increment = 1000 / 10;
+ constexpr int32_t increment = 1000 / 10;
for (int32_t i = 1; i < 10; i++) {
- kudu::KuduPartialRow* row = schema.NewRow();
+ KuduPartialRow* row = schema.NewRow();
KUDU_CHECK_OK(row->SetInt32(0, i * increment));
- tableCreator->add_range_partition_split(row);
+ table_creator->add_range_partition_split(row);
}
- tableCreator->num_replicas(1);
+ table_creator->num_replicas(1);
if (!owner.empty()) {
- tableCreator->set_owner(owner);
+ table_creator->set_owner(owner);
}
- kudu::Status s = tableCreator->Create();
- return s;
+ return table_creator->Create();
}
Status GetTableId(const std::string& table_name, std::string* table_id) {
DCHECK(table_id);
- kudu::client::sp::shared_ptr<kudu::client::KuduTable> table;
+ client::sp::shared_ptr<client::KuduTable> table;
RETURN_NOT_OK(client_->OpenTable(table_name, &table));
*table_id = table->id();
return Status::OK();
}
- kudu::client::sp::shared_ptr<kudu::client::KuduClient> client_;
+ client::sp::shared_ptr<client::KuduClient> client_;
std::string kTableName = "test_table";
};
diff --git a/src/kudu/master/spnego_rest_catalog-test.cc
b/src/kudu/master/spnego_rest_catalog-test.cc
index e1e0e898b..4ceab0eec 100644
--- a/src/kudu/master/spnego_rest_catalog-test.cc
+++ b/src/kudu/master/spnego_rest_catalog-test.cc
@@ -18,6 +18,7 @@
#include <memory>
#include <string>
#include <type_traits>
+#include <utility>
#include <gflags/gflags_declare.h>
#include <gtest/gtest.h>
@@ -64,15 +65,15 @@ class SpnegoRestCatalogTest : public RestCatalogTestBase {
ASSERT_OK(kdc_->SetKrb5Environment());
string kt_path;
ASSERT_OK(kdc_->CreateServiceKeytabWithName("HTTP/127.0.0.1",
"spnego.dedicated", &kt_path));
- ASSERT_OK(kdc_->CreateUserPrincipal("alice"));
+ ASSERT_OK(kdc_->CreateUserPrincipal(kDefaultPrincipal));
FLAGS_spnego_keytab_file = kt_path;
FLAGS_webserver_require_spnego = true;
FLAGS_enable_rest_api = true;
- auto opts = InternalMiniClusterOptions();
+ InternalMiniClusterOptions opts;
opts.bind_mode = BindMode::LOOPBACK;
- cluster_.reset(new InternalMiniCluster(env_, opts));
+ cluster_.reset(new InternalMiniCluster(env_, std::move(opts)));
ASSERT_OK(cluster_->Start());
ASSERT_OK(KuduClientBuilder()
@@ -83,12 +84,13 @@ class SpnegoRestCatalogTest : public RestCatalogTestBase {
protected:
unique_ptr<MiniKdc> kdc_;
unique_ptr<InternalMiniCluster> cluster_;
+ const string kDefaultPrincipal = "alice";
Status CreateTestTableAsAlice() { return
CreateTestTable("[email protected]"); }
};
TEST_F(SpnegoRestCatalogTest, TestGetTablesOneTable) {
- ASSERT_OK(kdc_->Kinit("alice"));
+ ASSERT_OK(kdc_->Kinit(kDefaultPrincipal));
ASSERT_OK(CreateTestTableAsAlice());
EasyCurl c;
faststring buf;
@@ -104,7 +106,7 @@ TEST_F(SpnegoRestCatalogTest, TestGetTablesOneTable) {
}
TEST_F(SpnegoRestCatalogTest, TestGetTableWithUser) {
- ASSERT_OK(kdc_->Kinit("alice"));
+ ASSERT_OK(kdc_->Kinit(kDefaultPrincipal));
ASSERT_OK(CreateTestTableAsAlice());
string table_id;
ASSERT_OK(GetTableId(kTableName, &table_id));
@@ -121,12 +123,12 @@ TEST_F(SpnegoRestCatalogTest, TestGetTableWithUser) {
}
TEST_F(SpnegoRestCatalogTest, TestPostTableWithUser) {
- ASSERT_OK(kdc_->Kinit("alice"));
+ ASSERT_OK(kdc_->Kinit(kDefaultPrincipal));
EasyCurl c;
faststring buf;
c.set_custom_method("POST");
c.set_auth(CurlAuthType::SPNEGO);
- Status s = c.PostToURL(
+ ASSERT_OK(c.PostToURL(
Substitute("http://$0/api/v1/tables",
cluster_->mini_master()->bound_http_addr().ToString()),
R"({
"name": "test_table",
@@ -143,17 +145,16 @@ TEST_F(SpnegoRestCatalogTest, TestPostTableWithUser) {
},
"num_replicas": 1
})",
- &buf);
- ASSERT_STR_CONTAINS(s.ToString(), "OK");
+ &buf));
string table_id;
ASSERT_OK(GetTableId(kTableName, &table_id));
shared_ptr<KuduTable> table;
ASSERT_OK(client_->OpenTable(kTableName, &table));
- ASSERT_STR_CONTAINS(table->owner(), "alice");
+ ASSERT_STR_CONTAINS(table->owner(), kDefaultPrincipal);
}
TEST_F(SpnegoRestCatalogTest, TestDeleteTableWithUser) {
- ASSERT_OK(kdc_->Kinit("alice"));
+ ASSERT_OK(kdc_->Kinit(kDefaultPrincipal));
ASSERT_OK(CreateTestTableAsAlice());
string table_id;
ASSERT_OK(GetTableId(kTableName, &table_id));
@@ -171,7 +172,7 @@ TEST_F(SpnegoRestCatalogTest, TestDeleteTableWithUser) {
}
TEST_F(SpnegoRestCatalogTest, TestAlterTableWithUser) {
- ASSERT_OK(kdc_->Kinit("alice"));
+ ASSERT_OK(kdc_->Kinit(kDefaultPrincipal));
ASSERT_OK(CreateTestTableAsAlice());
string table_id;
ASSERT_OK(GetTableId(kTableName, &table_id));
@@ -203,8 +204,8 @@ TEST_F(SpnegoRestCatalogTest, TestAlterTableWithUser) {
&buf));
shared_ptr<KuduTable> table;
ASSERT_OK(client_->OpenTable(kTableName, &table));
- ASSERT_EQ(table->schema().num_columns(), 3);
- ASSERT_STR_CONTAINS(table->owner(), "alice");
+ ASSERT_EQ(3, table->schema().num_columns());
+ ASSERT_STR_CONTAINS(table->owner(), kDefaultPrincipal);
}
TEST_F(SpnegoRestCatalogTest, TestInvalidHeaders) {
@@ -226,6 +227,9 @@ TEST_F(SpnegoRestCatalogTest, TestInvalidHeaders) {
ASSERT_STR_CONTAINS(s.ToString(), "HTTP 401");
}
+// The following tests are skipped on macOS due to inconsistent behavior of
SPNEGO.
+// macOS heimdal kerberos caches the KDC port number, which can cause
subsequent tests to fail.
+// For more details, refer to KUDU-3533
(https://issues.apache.org/jira/browse/KUDU-3533)
#ifndef __APPLE__
TEST_F(SpnegoRestCatalogTest, TestNoKinitGetTables) {
@@ -255,7 +259,7 @@ TEST_F(SpnegoRestCatalogTest, TestNoKinitGetTable) {
}
TEST_F(SpnegoRestCatalogTest, TestUnauthenticatedBadKeytab) {
- ASSERT_OK(kdc_->Kinit("alice"));
+ ASSERT_OK(kdc_->Kinit(kDefaultPrincipal));
ASSERT_OK(kdc_->RandomizePrincipalKey("HTTP/127.0.0.1"));
EasyCurl c;
faststring buf;