This is an automated email from the ASF dual-hosted git repository.

gavinchou pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new e917993c6dc [feat](http) Support update and show config for recycler 
and meta-service (#60816)
e917993c6dc is described below

commit e917993c6dcd65a091e330651105ce7c6c650373
Author: Yixuan Wang <[email protected]>
AuthorDate: Wed Mar 18 17:19:23 2026 +0800

    [feat](http) Support update and show config for recycler and meta-service 
(#60816)
    
    - add `show_config` endpoint to MetaService HTTP API (including v1 path)
    - refactor RecyclerService HTTP handlers and add
    `show_config`/`update_config` support
    - expose recycler thread pool group accessor used by HTTP service
    - add cloud regression test for update_config + show_config roundtrip
    - add `recycleServiceHttpAddress` in cloud_p0 custom regression config
    
    example:
    ```
    curl 
"http://localhost:5678/MetaService/http/show_config?token=greedisgood9999&conf_key=compacted_rowset_retention_seconds";
    {
        "code": "OK",
        "msg": "",
        "result": [
            [
                "compacted_rowset_retention_seconds",
                "int64_t",
                "1080",
                true
            ]
        ]
    }
    
    curl 
"http://localhost:5678/MetaService/http/update_config?configs=compacted_rowset_retention_seconds=108&token=greedisgood9999";
    {
        "code": "OK",
        "msg": ""
    }
    
    curl 
"http://localhost:5678/MetaService/http/show_config?token=greedisgood9999&conf_key=compacted_rowset_retention_seconds";
    {
        "code": "OK",
        "msg": "",
        "result": [
            [
                "compacted_rowset_retention_seconds",
                "int64_t",
                "108",
                true
            ]
        ]
    }
    ```
---
 cloud/src/meta-service/meta_service_http.cpp       |  34 ++
 cloud/src/recycler/recycler.h                      |   2 +
 cloud/src/recycler/recycler_service.cpp            | 400 ++++++++++++---------
 cloud/src/recycler/recycler_service.h              |   6 +-
 .../cloud_p0/conf/regression-conf-custom.groovy    |   1 +
 .../test_update_and_show_cloud_config.groovy       | 111 ++++++
 6 files changed, 375 insertions(+), 179 deletions(-)

diff --git a/cloud/src/meta-service/meta_service_http.cpp 
b/cloud/src/meta-service/meta_service_http.cpp
index 29b4b9287ad..2656b63f8bd 100644
--- a/cloud/src/meta-service/meta_service_http.cpp
+++ b/cloud/src/meta-service/meta_service_http.cpp
@@ -460,6 +460,38 @@ static HttpResponse 
process_query_rate_limit(MetaServiceImpl* service, brpc::Con
     return http_json_reply(MetaServiceCode::OK, "", sb.GetString());
 }
 
+static HttpResponse process_show_config(MetaServiceImpl*, brpc::Controller* 
cntl) {
+    auto& uri = cntl->http_request().uri();
+    std::string_view conf_name = http_query(uri, "conf_key");
+
+    if (config::full_conf_map == nullptr) {
+        return http_json_reply(MetaServiceCode::UNDEFINED_ERR, "config map not 
initialized");
+    }
+
+    rapidjson::Document d;
+    d.SetArray();
+
+    for (auto& [name, field] : *config::Register::_s_field_map) {
+        if (!conf_name.empty() && name != conf_name) {
+            continue;
+        }
+        auto it = config::full_conf_map->find(name);
+        std::string value = (it != config::full_conf_map->end()) ? it->second 
: "";
+
+        rapidjson::Value entry(rapidjson::kArrayType);
+        entry.PushBack(rapidjson::Value(name.c_str(), d.GetAllocator()), 
d.GetAllocator());
+        entry.PushBack(rapidjson::Value(field.type, d.GetAllocator()), 
d.GetAllocator());
+        entry.PushBack(rapidjson::Value(value.c_str(), d.GetAllocator()), 
d.GetAllocator());
+        entry.PushBack(rapidjson::Value(field.valmutable), d.GetAllocator());
+        d.PushBack(entry, d.GetAllocator());
+    }
+
+    rapidjson::StringBuffer sb;
+    rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(sb);
+    d.Accept(writer);
+    return http_json_reply(MetaServiceCode::OK, "", sb.GetString());
+}
+
 static HttpResponse process_update_config(MetaServiceImpl* service, 
brpc::Controller* cntl) {
     const auto& uri = cntl->http_request().uri();
     bool persist = (http_query(uri, "persist") == "true");
@@ -975,6 +1007,7 @@ void 
MetaServiceImpl::http(::google::protobuf::RpcController* controller,
             {"adjust_rate_limit", process_adjust_rate_limit},
             {"list_rate_limit", process_query_rate_limit},
             {"update_config", process_update_config},
+            {"show_config", process_show_config},
             {"v1/abort_txn", process_abort_txn},
             {"v1/abort_tablet_job", process_abort_tablet_job},
             {"v1/alter_ram_user", process_alter_ram_user},
@@ -982,6 +1015,7 @@ void 
MetaServiceImpl::http(::google::protobuf::RpcController* controller,
             {"v1/adjust_rate_limit", process_adjust_rate_limit},
             {"v1/list_rate_limit", process_query_rate_limit},
             {"v1/update_config", process_update_config},
+            {"v1/show_config", process_show_config},
     };
 
     auto* cntl = static_cast<brpc::Controller*>(controller);
diff --git a/cloud/src/recycler/recycler.h b/cloud/src/recycler/recycler.h
index 98b142e4003..c63d15e77ea 100644
--- a/cloud/src/recycler/recycler.h
+++ b/cloud/src/recycler/recycler.h
@@ -88,6 +88,8 @@ public:
 
     bool stopped() const { return stopped_.load(std::memory_order_acquire); }
 
+    RecyclerThreadPoolGroup& thread_pool_group() { return _thread_pool_group; }
+
 private:
     void recycle_callback();
 
diff --git a/cloud/src/recycler/recycler_service.cpp 
b/cloud/src/recycler/recycler_service.cpp
index 4b33ef76c95..d9342f7ee7b 100644
--- a/cloud/src/recycler/recycler_service.cpp
+++ b/cloud/src/recycler/recycler_service.cpp
@@ -22,6 +22,9 @@
 #include <fmt/format.h>
 #include <gen_cpp/cloud.pb.h>
 #include <google/protobuf/util/json_util.h>
+#include <rapidjson/document.h>
+#include <rapidjson/prettywriter.h>
+#include <rapidjson/stringbuffer.h>
 
 #include <algorithm>
 #include <functional>
@@ -32,10 +35,13 @@
 #include <vector>
 
 #include "common/config.h"
+#include "common/configbase.h"
 #include "common/defer.h"
 #include "common/logging.h"
+#include "common/string_util.h"
 #include "common/util.h"
 #include "cpp/s3_rate_limiter.h"
+#include "meta-service/meta_service_http.h"
 #include "meta-store/keys.h"
 #include "meta-store/txn_kv_error.h"
 #include "recycler/checker.h"
@@ -47,8 +53,6 @@
 
 namespace doris::cloud {
 
-extern std::tuple<int, std::string_view> 
convert_ms_code_to_http_code(MetaServiceCode ret);
-
 RecyclerServiceImpl::RecyclerServiceImpl(std::shared_ptr<TxnKv> txn_kv, 
Recycler* recycler,
                                          Checker* checker,
                                          std::shared_ptr<TxnLazyCommitter> 
txn_lazy_committer)
@@ -503,208 +507,248 @@ void check_meta(const std::shared_ptr<TxnKv>& txn_kv, 
const std::string& instanc
 #endif
 }
 
-void RecyclerServiceImpl::http(::google::protobuf::RpcController* controller,
-                               const ::doris::cloud::MetaServiceHttpRequest* 
request,
-                               ::doris::cloud::MetaServiceHttpResponse* 
response,
-                               ::google::protobuf::Closure* done) {
-    auto cntl = static_cast<brpc::Controller*>(controller);
-    LOG(INFO) << "rpc from " << cntl->remote_side() << " request: " << 
request->DebugString();
-    brpc::ClosureGuard closure_guard(done);
+static HttpResponse process_recycle_instance(RecyclerServiceImpl* service, 
brpc::Controller* cntl) {
+    std::string request_body = cntl->request_attachment().to_string();
+    RecycleInstanceRequest req;
+    auto st = google::protobuf::util::JsonStringToMessage(request_body, &req);
+    if (!st.ok()) {
+        std::string msg = "failed to RecycleInstanceRequest, error: " + 
st.message().ToString();
+        LOG(WARNING) << msg;
+        return http_json_reply(MetaServiceCode::INVALID_ARGUMENT, msg);
+    }
+    RecycleInstanceResponse res;
+    service->recycle_instance(cntl, &req, &res, nullptr);
+    return http_text_reply(res.status(), res.status().msg());
+}
+
+static HttpResponse process_statistics_recycle(RecyclerServiceImpl* service,
+                                               brpc::Controller* cntl) {
+    std::string request_body = cntl->request_attachment().to_string();
+    StatisticsRecycleRequest req;
+    auto st = google::protobuf::util::JsonStringToMessage(request_body, &req);
+    if (!st.ok()) {
+        std::string msg = "failed to StatisticsRecycleRequest, error: " + 
st.message().ToString();
+        LOG(WARNING) << msg;
+        return http_json_reply(MetaServiceCode::INVALID_ARGUMENT, msg);
+    }
     MetaServiceCode code = MetaServiceCode::OK;
-    int status_code = 200;
-    std::string msg = "OK";
-    std::string req;
-    std::string response_body;
-    std::string request_body;
-    DORIS_CLOUD_DEFER {
-        status_code = std::get<0>(convert_ms_code_to_http_code(code));
-        LOG(INFO) << (code == MetaServiceCode::OK ? "succ to " : "failed to ") 
<< "http"
-                  << " " << cntl->remote_side() << " request=\n"
-                  << req << "\n ret=" << code << " msg=" << msg;
-        cntl->http_response().set_status_code(status_code);
-        cntl->response_attachment().append(response_body);
-        cntl->response_attachment().append("\n");
-    };
+    std::string msg;
+    service->statistics_recycle(req, code, msg);
+    return http_text_reply(code, msg, msg);
+}
 
-    // Prepare input request info
-    auto unresolved_path = cntl->http_request().unresolved_path();
-    auto uri = cntl->http_request().uri();
-    std::stringstream ss;
-    ss << "\nuri_path=" << uri.path();
-    ss << "\nunresolved_path=" << unresolved_path;
-    ss << "\nmethod=" << brpc::HttpMethod2Str(cntl->http_request().method());
-    ss << "\nquery strings:";
-    for (auto it = uri.QueryBegin(); it != uri.QueryEnd(); ++it) {
-        ss << "\n" << it->first << "=" << it->second;
-    }
-    ss << "\nheaders:";
-    for (auto it = cntl->http_request().HeaderBegin(); it != 
cntl->http_request().HeaderEnd();
-         ++it) {
-        ss << "\n" << it->first << ":" << it->second;
-    }
-    req = ss.str();
-    ss.clear();
-    request_body = cntl->request_attachment().to_string(); // Just copy
+static HttpResponse process_recycle_copy_jobs(RecyclerServiceImpl* service,
+                                              brpc::Controller* cntl) {
+    const auto* instance_id = 
cntl->http_request().uri().GetQuery("instance_id");
+    if (instance_id == nullptr || instance_id->empty()) {
+        return http_json_reply(MetaServiceCode::INVALID_ARGUMENT, "no instance 
id");
+    }
+    MetaServiceCode code = MetaServiceCode::OK;
+    std::string msg;
+    recycle_copy_jobs(service->txn_kv(), *instance_id, code, msg,
+                      service->recycler()->thread_pool_group(), 
service->txn_lazy_committer());
+    return http_text_reply(code, msg, msg);
+}
 
-    // Auth
-    auto token = uri.GetQuery("token");
-    if (token == nullptr || *token != config::http_token) {
-        msg = "incorrect token, token=" + (token == nullptr ? 
std::string("(not given)") : *token);
-        response_body = "incorrect token";
-        status_code = 403;
-        return;
+static HttpResponse process_recycle_job_info(RecyclerServiceImpl* service, 
brpc::Controller* cntl) {
+    const auto* instance_id = 
cntl->http_request().uri().GetQuery("instance_id");
+    if (instance_id == nullptr || instance_id->empty()) {
+        return http_json_reply(MetaServiceCode::INVALID_ARGUMENT, "no instance 
id");
     }
+    MetaServiceCode code = MetaServiceCode::OK;
+    std::string msg;
+    std::string key;
+    job_recycle_key({*instance_id}, &key);
+    recycle_job_info(service->txn_kv(), *instance_id, key, code, msg);
+    return http_text_reply(code, msg, msg);
+}
 
-    if (unresolved_path == "recycle_instance") {
-        RecycleInstanceRequest req;
-        auto st = google::protobuf::util::JsonStringToMessage(request_body, 
&req);
-        if (!st.ok()) {
-            msg = "failed to RecycleInstanceRequest, error: " + 
st.message().ToString();
-            response_body = msg;
-            LOG(WARNING) << msg;
-            return;
-        }
-        RecycleInstanceResponse res;
-        recycle_instance(cntl, &req, &res, nullptr);
-        code = res.status().code();
-        msg = res.status().msg();
-        response_body = msg;
-        return;
+static HttpResponse process_check_instance(RecyclerServiceImpl* service, 
brpc::Controller* cntl) {
+    const auto* instance_id = 
cntl->http_request().uri().GetQuery("instance_id");
+    if (instance_id == nullptr || instance_id->empty()) {
+        return http_json_reply(MetaServiceCode::INVALID_ARGUMENT, "no instance 
id");
+    }
+    if (!service->checker()) {
+        return http_json_reply(MetaServiceCode::INVALID_ARGUMENT, "checker not 
enabled");
     }
+    MetaServiceCode code = MetaServiceCode::OK;
+    std::string msg;
+    service->check_instance(*instance_id, code, msg);
+    return http_text_reply(code, msg, msg);
+}
 
-    if (unresolved_path == "statistics_recycle") {
-        StatisticsRecycleRequest req;
-        auto st = google::protobuf::util::JsonStringToMessage(request_body, 
&req);
-        if (!st.ok()) {
-            msg = "failed to StatisticsRecycleRequest, error: " + 
st.message().ToString();
-            response_body = msg;
-            LOG(WARNING) << msg;
-            return;
-        }
-        statistics_recycle(req, code, msg);
-        response_body = msg;
-        return;
+static HttpResponse process_check_job_info(RecyclerServiceImpl* service, 
brpc::Controller* cntl) {
+    const auto* instance_id = 
cntl->http_request().uri().GetQuery("instance_id");
+    if (instance_id == nullptr || instance_id->empty()) {
+        return http_json_reply(MetaServiceCode::INVALID_ARGUMENT, "no instance 
id");
     }
+    MetaServiceCode code = MetaServiceCode::OK;
+    std::string msg;
+    std::string key;
+    job_check_key({*instance_id}, &key);
+    recycle_job_info(service->txn_kv(), *instance_id, key, code, msg);
+    return http_text_reply(code, msg, msg);
+}
 
-    if (unresolved_path == "recycle_copy_jobs") {
-        auto instance_id = uri.GetQuery("instance_id");
-        if (instance_id == nullptr || instance_id->empty()) {
-            msg = "no instance id";
-            response_body = msg;
-            status_code = 400;
-            return;
-        }
-        recycle_copy_jobs(txn_kv_, *instance_id, code, msg, 
recycler_->_thread_pool_group,
-                          txn_lazy_committer_);
+static HttpResponse process_check_meta(RecyclerServiceImpl* service, 
brpc::Controller* cntl) {
+    const auto& uri = cntl->http_request().uri();
+    const auto* instance_id = uri.GetQuery("instance_id");
+    const auto* host = uri.GetQuery("host");
+    const auto* port = uri.GetQuery("port");
+    const auto* user = uri.GetQuery("user");
+    const auto* password = uri.GetQuery("password");
+    if (instance_id == nullptr || instance_id->empty() || host == nullptr || 
host->empty() ||
+        port == nullptr || port->empty() || password == nullptr || user == 
nullptr ||
+        user->empty()) {
+        return http_json_reply(MetaServiceCode::INVALID_ARGUMENT,
+                               "no instance id or mysql conn str info");
+    }
+    LOG(INFO) << " host " << *host << " port " << *port << " user " << *user 
<< " instance "
+              << *instance_id;
+    std::string msg;
+    check_meta(service->txn_kv(), *instance_id, *host, *port, *user, 
*password, msg);
+    return http_text_reply(MetaServiceCode::OK, msg, msg);
+}
 
-        response_body = msg;
-        return;
+static HttpResponse process_adjust_rate_limiter(RecyclerServiceImpl*, 
brpc::Controller* cntl) {
+    const auto& uri = cntl->http_request().uri();
+    const auto* type_string = uri.GetQuery("type");
+    const auto* speed = uri.GetQuery("speed");
+    const auto* burst = uri.GetQuery("burst");
+    const auto* limit = uri.GetQuery("limit");
+    if (type_string == nullptr || type_string->empty() || speed == nullptr || 
burst == nullptr ||
+        limit == nullptr || (*type_string != "get" && *type_string != "put")) {
+        return http_json_reply(MetaServiceCode::INVALID_ARGUMENT, "argument 
not suitable");
+    }
+    auto max_speed = speed->empty() ? 0 : std::stoul(*speed);
+    auto max_burst = burst->empty() ? 0 : std::stoul(*burst);
+    auto max_limit = limit->empty() ? 0 : std::stoul(*limit);
+    if (0 != reset_s3_rate_limiter(string_to_s3_rate_limit_type(*type_string), 
max_speed, max_burst,
+                                   max_limit)) {
+        return http_json_reply(MetaServiceCode::UNDEFINED_ERR, "adjust 
failed");
     }
+    return http_json_reply(MetaServiceCode::OK, "");
+}
 
-    if (unresolved_path == "recycle_job_info") {
-        auto instance_id = uri.GetQuery("instance_id");
-        if (instance_id == nullptr || instance_id->empty()) {
-            msg = "no instance id";
-            response_body = msg;
-            status_code = 400;
-            return;
-        }
-        std::string key;
-        job_recycle_key({*instance_id}, &key);
-        recycle_job_info(txn_kv_, *instance_id, key, code, msg);
-        response_body = msg;
-        return;
+static HttpResponse process_show_config(RecyclerServiceImpl*, 
brpc::Controller* cntl) {
+    const auto* conf_key_ptr = cntl->http_request().uri().GetQuery("conf_key");
+    std::string conf_name = conf_key_ptr ? *conf_key_ptr : "";
+
+    if (config::full_conf_map == nullptr) {
+        return http_json_reply(MetaServiceCode::UNDEFINED_ERR, "config map not 
initialized");
     }
 
-    if (unresolved_path == "check_instance") {
-        auto instance_id = uri.GetQuery("instance_id");
-        if (instance_id == nullptr || instance_id->empty()) {
-            msg = "no instance id";
-            response_body = msg;
-            status_code = 400;
-            return;
-        }
-        if (!checker_) {
-            msg = "checker not enabled";
-            response_body = msg;
-            status_code = 400;
-            return;
+    rapidjson::Document d;
+    d.SetArray();
+    for (auto& [name, field] : *config::Register::_s_field_map) {
+        if (!conf_name.empty() && name != conf_name) {
+            continue;
         }
-        check_instance(*instance_id, code, msg);
-        response_body = msg;
-        return;
+        auto it = config::full_conf_map->find(name);
+        std::string value = (it != config::full_conf_map->end()) ? it->second 
: "";
+
+        rapidjson::Value entry(rapidjson::kArrayType);
+        entry.PushBack(rapidjson::Value(name.c_str(), d.GetAllocator()), 
d.GetAllocator());
+        entry.PushBack(rapidjson::Value(field.type, d.GetAllocator()), 
d.GetAllocator());
+        entry.PushBack(rapidjson::Value(value.c_str(), d.GetAllocator()), 
d.GetAllocator());
+        entry.PushBack(rapidjson::Value(field.valmutable), d.GetAllocator());
+        d.PushBack(entry, d.GetAllocator());
     }
+    rapidjson::StringBuffer sb;
+    rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(sb);
+    d.Accept(writer);
+    return http_json_reply(MetaServiceCode::OK, "", sb.GetString());
+}
 
-    if (unresolved_path == "check_job_info") {
-        auto instance_id = uri.GetQuery("instance_id");
-        if (instance_id == nullptr || instance_id->empty()) {
-            msg = "no instance id";
-            response_body = msg;
-            status_code = 400;
-            return;
+static HttpResponse process_update_config(RecyclerServiceImpl*, 
brpc::Controller* cntl) {
+    const auto& uri = cntl->http_request().uri();
+    bool persist = (uri.GetQuery("persist") != nullptr && 
*uri.GetQuery("persist") == "true");
+    const auto* configs_ptr = uri.GetQuery("configs");
+    const auto* reason_ptr = uri.GetQuery("reason");
+    std::string configs = configs_ptr ? *configs_ptr : "";
+    std::string reason = reason_ptr ? *reason_ptr : "";
+    LOG(INFO) << "modify configs for reason=" << reason << ", configs=" << 
configs
+              << ", persist=" << persist;
+    if (configs.empty()) {
+        LOG(WARNING) << "query param `configs` should not be empty";
+        return http_json_reply(MetaServiceCode::INVALID_ARGUMENT,
+                               "query param `configs` should not be empty");
+    }
+    std::unordered_map<std::string, std::string> conf_map;
+    auto conf_list = split(configs, ',');
+    for (const auto& conf : conf_list) {
+        auto conf_pair = split(conf, '=');
+        if (conf_pair.size() != 2) {
+            LOG(WARNING) << "failed to split config=[" << conf << "] from 
`k=v` pattern";
+            return http_json_reply(MetaServiceCode::INVALID_ARGUMENT,
+                                   fmt::format("config {} is invalid", conf));
         }
-        std::string key;
-        job_check_key({*instance_id}, &key);
-        recycle_job_info(txn_kv_, *instance_id, key, code, msg);
-        response_body = msg;
-        return;
+        trim(conf_pair[0]);
+        trim(conf_pair[1]);
+        conf_map.emplace(std::move(conf_pair[0]), std::move(conf_pair[1]));
+    }
+    if (auto [succ, cause] =
+                config::set_config(std::move(conf_map), persist, 
config::custom_conf_path);
+        !succ) {
+        LOG(WARNING) << cause;
+        return http_json_reply(MetaServiceCode::INVALID_ARGUMENT, cause);
     }
+    return http_json_reply(MetaServiceCode::OK, "");
+}
 
-    if (unresolved_path == "check_meta") {
-        auto instance_id = uri.GetQuery("instance_id");
-        auto host = uri.GetQuery("host");
-        auto port = uri.GetQuery("port");
-        auto user = uri.GetQuery("user");
-        auto password = uri.GetQuery("password");
-        if (instance_id == nullptr || instance_id->empty() || host == nullptr 
|| host->empty() ||
-            port == nullptr || port->empty() || password == nullptr || user == 
nullptr ||
-            user->empty()) {
-            msg = "no instance id or mysql conn str info";
-            response_body = msg;
-            status_code = 400;
-            return;
-        }
-        LOG(INFO) << " host " << *host;
-        LOG(INFO) << " port " << *port;
-        LOG(INFO) << " user " << *user;
-        LOG(INFO) << " instance " << *instance_id;
-        check_meta(txn_kv_, *instance_id, *host, *port, *user, *password, msg);
-        status_code = 200;
-        response_body = msg;
+void RecyclerServiceImpl::http(::google::protobuf::RpcController* controller,
+                               const ::doris::cloud::MetaServiceHttpRequest*,
+                               ::doris::cloud::MetaServiceHttpResponse*,
+                               ::google::protobuf::Closure* done) {
+    using HttpHandler = HttpResponse (*)(RecyclerServiceImpl*, 
brpc::Controller*);
+    static const std::unordered_map<std::string_view, HttpHandler> 
http_handlers {
+            {"recycle_instance", process_recycle_instance},
+            {"statistics_recycle", process_statistics_recycle},
+            {"recycle_copy_jobs", process_recycle_copy_jobs},
+            {"recycle_job_info", process_recycle_job_info},
+            {"check_instance", process_check_instance},
+            {"check_job_info", process_check_job_info},
+            {"check_meta", process_check_meta},
+            {"adjust_rate_limiter", process_adjust_rate_limiter},
+            {"show_config", process_show_config},
+            {"update_config", process_update_config},
+    };
+
+    auto* cntl = static_cast<brpc::Controller*>(controller);
+    LOG(INFO) << "rpc from " << cntl->remote_side()
+              << " request: " << cntl->http_request().uri().path();
+    brpc::ClosureGuard closure_guard(done);
+
+    // Auth
+    const auto* token = cntl->http_request().uri().GetQuery("token");
+    if (token == nullptr || *token != config::http_token) {
+        std::string msg = "incorrect token, token=" +
+                          (token == nullptr ? std::string("(not given)") : 
*token);
+        cntl->http_response().set_status_code(403);
+        cntl->response_attachment().append(msg);
+        cntl->response_attachment().append("\n");
+        LOG(WARNING) << "failed to handle http from " << cntl->remote_side() 
<< " msg: " << msg;
         return;
     }
 
-    if (unresolved_path == "adjust_rate_limiter") {
-        auto type_string = uri.GetQuery("type");
-        auto speed = uri.GetQuery("speed");
-        auto burst = uri.GetQuery("burst");
-        auto limit = uri.GetQuery("limit");
-        if (type_string->empty() || speed->empty() || burst->empty() || 
limit->empty() ||
-            (*type_string != "get" && *type_string != "put")) {
-            msg = "argument not suitable";
-            response_body = msg;
-            status_code = 400;
-            return;
-        }
-        auto max_speed = speed->empty() ? 0 : std::stoul(*speed);
-        auto max_burst = burst->empty() ? 0 : std::stoul(*burst);
-        auto max_limit = burst->empty() ? 0 : std::stoul(*limit);
-        if (0 != 
reset_s3_rate_limiter(string_to_s3_rate_limit_type(*type_string), max_speed,
-                                       max_burst, max_limit)) {
-            msg = "adjust failed";
-            response_body = msg;
-            status_code = 400;
-            return;
-        }
-
-        status_code = 200;
-        response_body = msg;
+    const auto& unresolved_path = cntl->http_request().unresolved_path();
+    auto it = http_handlers.find(unresolved_path);
+    if (it == http_handlers.end()) {
+        std::string msg = "http path " + cntl->http_request().uri().path() +
+                          " not found, it may be not implemented";
+        cntl->http_response().set_status_code(404);
+        cntl->response_attachment().append(msg);
+        cntl->response_attachment().append("\n");
         return;
     }
 
-    status_code = 404;
-    msg = "http path " + uri.path() + " not found, it may be not implemented";
-    response_body = msg;
+    auto [status_code, msg, body] = it->second(this, cntl);
+    cntl->http_response().set_status_code(status_code);
+    cntl->response_attachment().append(body);
+    cntl->response_attachment().append("\n");
+
+    LOG(INFO) << (status_code == 200 ? "succ to " : "failed to ") << 
__PRETTY_FUNCTION__ << " "
+              << cntl->remote_side() << " ret=" << status_code << " msg=" << 
msg;
 }
 
 } // namespace doris::cloud
diff --git a/cloud/src/recycler/recycler_service.h 
b/cloud/src/recycler/recycler_service.h
index 6890d7049bd..a9a9f739f1e 100644
--- a/cloud/src/recycler/recycler_service.h
+++ b/cloud/src/recycler/recycler_service.h
@@ -44,11 +44,15 @@ public:
               ::doris::cloud::MetaServiceHttpResponse* response,
               ::google::protobuf::Closure* done) override;
 
-private:
     void statistics_recycle(StatisticsRecycleRequest& req, MetaServiceCode& 
code, std::string& msg);
 
     void check_instance(const std::string& instance_id, MetaServiceCode& code, 
std::string& msg);
 
+    std::shared_ptr<TxnKv> txn_kv() { return txn_kv_; }
+    Recycler* recycler() { return recycler_; }
+    Checker* checker() { return checker_; }
+    std::shared_ptr<TxnLazyCommitter> txn_lazy_committer() { return 
txn_lazy_committer_; }
+
 private:
     std::shared_ptr<TxnKv> txn_kv_;
     Recycler* recycler_; // Ref
diff --git 
a/regression-test/pipeline/cloud_p0/conf/regression-conf-custom.groovy 
b/regression-test/pipeline/cloud_p0/conf/regression-conf-custom.groovy
index f9f1a3b9183..2cc8ffaa4a2 100644
--- a/regression-test/pipeline/cloud_p0/conf/regression-conf-custom.groovy
+++ b/regression-test/pipeline/cloud_p0/conf/regression-conf-custom.groovy
@@ -108,3 +108,4 @@ enableTrinoConnectorTest = false
 
 s3Source = "aliyun"
 s3Endpoint = "oss-cn-hongkong-internal.aliyuncs.com"
+recycleServiceHttpAddress = "127.0.0.1:6000"
\ No newline at end of file
diff --git 
a/regression-test/suites/cloud_p0/test_update_and_show_cloud_config.groovy 
b/regression-test/suites/cloud_p0/test_update_and_show_cloud_config.groovy
new file mode 100644
index 00000000000..bcbc450e8ab
--- /dev/null
+++ b/regression-test/suites/cloud_p0/test_update_and_show_cloud_config.groovy
@@ -0,0 +1,111 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import org.apache.doris.regression.suite.ClusterOptions
+import groovy.json.JsonSlurper
+
+suite('test_update_and_show_cloud_config') {
+    if (!isCloudMode()) {
+        return
+    }
+
+    def token = context.config.metaServiceToken ?: "greedisgood9999"
+    def configKey = "recycle_interval_seconds"
+    def newValue = "999"
+
+    // Helper: call show_config on a service and return the value for 
configKey, or null if not found.
+    def showConfig = { serviceAddr, servicePath ->
+        def result = null
+        httpTest {
+            endpoint serviceAddr
+            uri 
"${servicePath}/show_config?token=${token}&conf_key=${configKey}"
+            op "get"
+            check { respCode, body ->
+                assertEquals(200, respCode)
+                // Response is {"code":"OK","msg":"","result":[[name, type, 
value, is_mutable], ...]}
+                def parsed = new JsonSlurper().parseText(body.trim())
+                assertEquals("OK", parsed.code, "show_config should return 
code=OK, got: ${body}")
+                assertTrue(parsed.result instanceof List, "show_config result 
should be a JSON array")
+                def entry = parsed.result.find { it[0] == configKey }
+                assertNotNull(entry, "Config key '${configKey}' not found in 
show_config response")
+                result = entry[2]  // value is the 3rd element
+            }
+        }
+        return result
+    }
+
+    // Helper: call update_config on a service and assert success.
+    def updateConfig = { serviceAddr, servicePath, value ->
+        httpTest {
+            endpoint serviceAddr
+            uri 
"${servicePath}/update_config?token=${token}&configs=${configKey}=${value}"
+            op "get"
+            check { respCode, body ->
+                logger.info("update_config response: respCode=${respCode}, 
body=${body}")
+                assertEquals(200, respCode)
+                def parsed = new JsonSlurper().parseText(body.trim())
+                assertEquals("OK", parsed.code, "update_config should return 
code=OK, got: ${body}")
+            }
+        }
+    }
+
+    // ── Meta Service 
──────────────────────────────────────────────────────────
+    def msAddr = context.config.metaServiceHttpAddress
+    def msPath = "/MetaService/http"
+
+    // 1. Read the original value so we can restore it afterwards.
+    def originalMsValue = showConfig(msAddr, msPath)
+    logger.info("meta-service original ${configKey}=${originalMsValue}")
+
+    try {
+        // 2. Update to newValue.
+        updateConfig(msAddr, msPath, newValue)
+
+        // 3. Verify show_config reflects the new value.
+        def updatedMsValue = showConfig(msAddr, msPath)
+        logger.info("meta-service updated ${configKey}=${updatedMsValue}")
+        assertEquals(newValue, updatedMsValue,
+                "meta-service: show_config should return updated value 
${newValue}, got ${updatedMsValue}")
+    } finally {
+        // 4. Restore original value.
+        if (originalMsValue != null) {
+            updateConfig(msAddr, msPath, originalMsValue)
+            logger.info("meta-service restored 
${configKey}=${originalMsValue}")
+        }
+    }
+
+    // ── Recycler Service 
──────────────────────────────────────────────────────
+    def recyclerAddr = context.config.recycleServiceHttpAddress
+    def recyclerPath = "/RecyclerService/http"
+
+    def originalRecyclerValue = showConfig(recyclerAddr, recyclerPath)
+    logger.info("recycler original ${configKey}=${originalRecyclerValue}")
+
+    try {
+        updateConfig(recyclerAddr, recyclerPath, newValue)
+
+        def updatedRecyclerValue = showConfig(recyclerAddr, recyclerPath)
+        logger.info("recycler updated ${configKey}=${updatedRecyclerValue}")
+        assertEquals(newValue, updatedRecyclerValue,
+                "recycler: show_config should return updated value 
${newValue}, got ${updatedRecyclerValue}")
+    } finally {
+        if (originalRecyclerValue != null) {
+            updateConfig(recyclerAddr, recyclerPath, originalRecyclerValue)
+            logger.info("recycler restored 
${configKey}=${originalRecyclerValue}")
+        }
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to