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 <lotzgabrie...@gmail.com>
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 <ale...@apache.org>
    Reviewed-by: Zoltan Chovan <zcho...@cloudera.com>
    Tested-by: Marton Greber <greber...@gmail.com>
---
 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("al...@krbtest.com"); }
 };
 
 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;


Reply via email to