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 18a132a9f Add /api/v1/leader endpoint to REST API endpoints
18a132a9f is described below

commit 18a132a9f377cb1ee49cf7fc84d0cda3ff781e43
Author: gabriellalotz <lotzgabrie...@gmail.com>
AuthorDate: Mon Apr 14 12:48:01 2025 +0000

    Add /api/v1/leader endpoint to REST API endpoints
    
    To support leader discovery without relying on HTTP redirects, which are
    incompatible with SPNEGO authentication, a new REST API endpoint
    /api/v1/leader has been introduced.
    
    This endpoint returns the current leader master's HTTP or HTTPS address
    in a compact JSON format, e.g.:
    { "leader": "http://hostname:port"; }
    
    Change-Id: If74db14bee2d273529ebfb02e72c6675bb094bce
    Reviewed-on: http://gerrit.cloudera.org:8080/22779
    Tested-by: Marton Greber <greber...@gmail.com>
    Reviewed-by: Marton Greber <greber...@gmail.com>
    Reviewed-by: Zoltan Chovan <zcho...@cloudera.com>
---
 src/kudu/master/rest_catalog-test.cc          | 53 ++++++++++++++++++++++++
 src/kudu/master/rest_catalog_path_handlers.cc | 58 ++++++++++++++++++++++++++-
 src/kudu/master/rest_catalog_path_handlers.h  |  2 +
 3 files changed, 112 insertions(+), 1 deletion(-)

diff --git a/src/kudu/master/rest_catalog-test.cc 
b/src/kudu/master/rest_catalog-test.cc
index 6e17ff9c7..de97544ac 100644
--- a/src/kudu/master/rest_catalog-test.cc
+++ b/src/kudu/master/rest_catalog-test.cc
@@ -16,6 +16,7 @@
 // under the License.
 
 #include <memory>
+#include <set>
 #include <string>
 #include <type_traits>
 #include <vector>
@@ -25,6 +26,7 @@
 
 #include "kudu/client/client.h"
 #include "kudu/client/schema.h"
+#include "kudu/gutil/strings/join.h"
 #include "kudu/gutil/strings/substitute.h"
 #include "kudu/master/mini_master.h"
 #include "kudu/master/rest_catalog_test_base.h"
@@ -44,6 +46,7 @@ using kudu::client::KuduTable;
 using kudu::client::sp::shared_ptr;
 using kudu::cluster::InternalMiniCluster;
 using kudu::cluster::InternalMiniClusterOptions;
+using std::set;
 using std::string;
 using std::unique_ptr;
 using std::vector;
@@ -577,5 +580,55 @@ TEST_F(RestCatalogTest, TestPutTableEndpointChangeOwner) {
   ASSERT_EQ("new_owner", table->owner());
 }
 
+class MultiMasterTest : public RestCatalogTestBase {
+ public:
+  void SetUp() override {
+    KuduTest::SetUp();
+    FLAGS_enable_rest_api = true;
+
+    InternalMiniClusterOptions opts;
+    opts.num_masters = 3;
+    cluster_.reset(new InternalMiniCluster(env_, opts));
+    ASSERT_OK(cluster_->Start());
+    KuduClientBuilder client_builder;
+    ASSERT_OK(cluster_->CreateClient(&client_builder, &client_));
+  }
+
+ protected:
+  unique_ptr<InternalMiniCluster> cluster_;
+};
+
+TEST_F(MultiMasterTest, TestGetLeaderEndpoint) {
+  set<string> leader_addresses;
+
+  static KuduRegex re("\"leader\":\"([^\"]+)\"", 1);
+
+  for (int i = 0; i < cluster_->num_masters(); i++) {
+    EasyCurl c;
+    faststring buf;
+    ASSERT_OK(c.FetchURL(Substitute("http://$0/api/v1/leader";,
+                                    
cluster_->mini_master(i)->bound_http_addr().ToString()),
+                         &buf));
+    vector<string> matches;
+    ASSERT_TRUE(re.Match(buf.ToString(), &matches));
+    ASSERT_FALSE(matches.empty()) << "No leader match in: " << buf.ToString();
+
+    leader_addresses.insert(matches[0]);
+  }
+
+  // All masters should report the same leader
+  ASSERT_EQ(1, leader_addresses.size())
+      << JoinStrings(leader_addresses, ", ");
+
+  string leader_addr = *leader_addresses.begin();
+  ASSERT_FALSE(leader_addr.empty());
+  EasyCurl leader_curl;
+  faststring leader_buf;
+  leader_curl.set_verbose(true);
+  ASSERT_OK(leader_curl.FetchURL(Substitute("$0/api/v1/tables", leader_addr),
+                                &leader_buf));
+  ASSERT_STR_CONTAINS(leader_buf.ToString(), "{\"tables\":[]}");
+}
+
 }  // namespace master
 }  // namespace kudu
diff --git a/src/kudu/master/rest_catalog_path_handlers.cc 
b/src/kudu/master/rest_catalog_path_handlers.cc
index ee5ecd626..8ae1f3e25 100644
--- a/src/kudu/master/rest_catalog_path_handlers.cc
+++ b/src/kudu/master/rest_catalog_path_handlers.cc
@@ -22,12 +22,15 @@
 #include <string>
 #include <unordered_map>
 #include <utility>
+#include <vector>
 
 #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/common/wire_protocol.pb.h"
+#include "kudu/consensus/metadata.pb.h"
 #include "kudu/gutil/ref_counted.h"
 #include "kudu/gutil/strings/substitute.h"
 #include "kudu/master/catalog_manager.h"
@@ -64,9 +67,11 @@ TAG_FLAG(rest_catalog_default_request_timeout_ms, runtime);
 
 using google::protobuf::util::JsonParseOptions;
 using google::protobuf::util::JsonStringToMessage;
+using kudu::consensus::RaftPeerPB;
 using std::optional;
 using std::ostringstream;
 using std::string;
+using std::vector;
 using strings::Substitute;
 
 namespace kudu {
@@ -164,7 +169,50 @@ void 
RestCatalogPathHandlers::HandleApiTablesEndpoint(const Webserver::WebReques
   }
 }
 
-void RestCatalogPathHandlers::HandleGetTables(ostringstream* output,
+void RestCatalogPathHandlers::HandleLeaderEndpoint(const 
Webserver::WebRequest& req,
+                                                   
Webserver::PrerenderedWebResponse* resp) {
+  ostringstream* output = &resp->output;
+  JsonWriter jw(output, JsonWriter::COMPACT);
+
+  if (req.request_method != "GET") {
+    RETURN_JSON_ERROR(
+        jw, "Method not allowed", resp->status_code, 
HttpStatusCode::MethodNotAllowed);
+  }
+  CatalogManager::ScopedLeaderSharedLock l(master_->catalog_manager());
+  vector<ServerEntryPB> masters;
+  Status s = master_->ListMasters(&masters,
+                                  /*use_external_addr=*/false);
+
+  if (!s.ok()) {
+    RETURN_JSON_ERROR(
+        jw, "unable to list masters", resp->status_code, 
HttpStatusCode::InternalServerError);
+  }
+
+  for (const auto& master : masters) {
+    if (master.has_error() || master.role() != RaftPeerPB::LEADER) {
+      continue;
+    }
+
+    const ServerRegistrationPB& reg = master.registration();
+
+    if (reg.http_addresses().empty()) {
+      RETURN_JSON_ERROR(
+          jw, "leader master has no http address", resp->status_code, 
HttpStatusCode::NotFound);
+    }
+    jw.StartObject();
+    jw.String("leader");
+    jw.String(Substitute("$0://$1:$2",
+                         reg.https_enabled() ? "https" : "http",
+                         reg.http_addresses(0).host(),
+                         reg.http_addresses(0).port()));
+    jw.EndObject();
+    resp->status_code = HttpStatusCode::Ok;
+    return;
+  }
+  RETURN_JSON_ERROR(jw, "No leader master found", resp->status_code, 
HttpStatusCode::NotFound);
+}
+
+void RestCatalogPathHandlers::HandleGetTables(std::ostringstream* output,
                                               const Webserver::WebRequest& req,
                                               HttpStatusCode* status_code) {
   ListTablesRequestPB request;
@@ -386,6 +434,14 @@ void RestCatalogPathHandlers::Register(Webserver* server) {
       },
       StyleMode::JSON,
       false);
+  server->RegisterPrerenderedPathHandler(
+      "/api/v1/leader",
+      "",
+      [this](const Webserver::WebRequest& req, 
Webserver::PrerenderedWebResponse* resp) {
+        this->HandleLeaderEndpoint(req, resp);
+      },
+      StyleMode::JSON,
+      false);
 }
 
 }  // namespace master
diff --git a/src/kudu/master/rest_catalog_path_handlers.h 
b/src/kudu/master/rest_catalog_path_handlers.h
index a4f0d97ce..89c51065a 100644
--- a/src/kudu/master/rest_catalog_path_handlers.h
+++ b/src/kudu/master/rest_catalog_path_handlers.h
@@ -45,6 +45,8 @@ class RestCatalogPathHandlers final {
                               Webserver::PrerenderedWebResponse* resp);
   void HandleApiTablesEndpoint(const Webserver::WebRequest& req,
                                Webserver::PrerenderedWebResponse* resp);
+  void HandleLeaderEndpoint(const Webserver::WebRequest& req,
+                            Webserver::PrerenderedWebResponse* resp);
 
   // Handles REST API endpoints based on the request method and path.
   void HandleGetTables(std::ostringstream* output,

Reply via email to