blob: 12992802eb0bc2f779fd5d0129468a2960af88fd [file]
// 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.
#include "service/http/action/peer_cache_action.h"
#include <rapidjson/document.h>
#include <rapidjson/prettywriter.h>
#include <rapidjson/stringbuffer.h>
#include <string>
#include "cloud/cloud_storage_engine.h"
#include "cloud/cloud_warm_up_manager.h"
#include "service/http/action/action_constants.h"
#include "service/http/http_channel.h"
#include "service/http/http_headers.h"
#include "service/http/http_request.h"
#include "service/http/http_status.h"
namespace doris {
// Serialize a single TabletPeerCandidates into a rapidjson object
static void tablet_peer_to_json(int64_t tablet_id, const TabletPeerCandidates& tpc,
rapidjson::Writer<rapidjson::StringBuffer>& writer) {
writer.StartObject();
writer.Key("tablet_id");
writer.Int64(tablet_id);
writer.Key("candidates");
writer.StartArray();
for (const auto& c : tpc.candidates) {
writer.StartObject();
writer.Key("host");
writer.String(c.host.c_str());
writer.Key("brpc_port");
writer.Int(c.brpc_port);
writer.Key("compute_group_id");
writer.String(c.compute_group_id.c_str());
writer.Key("last_access_time_ms");
writer.Int64(c.last_access_time_ms);
writer.Key("consecutive_rpc_failures");
writer.Int(c.consecutive_rpc_failures);
writer.EndObject();
}
writer.EndArray();
writer.Key("last_successful_compute_group_id");
writer.String(tpc.last_successful_compute_group_id.c_str());
writer.Key("fetching_from_fe");
writer.Bool(tpc.fetching_from_fe);
writer.Key("consecutive_all_miss");
writer.Int(tpc.consecutive_all_miss);
writer.Key("cooldown_until_ms");
writer.Int64(tpc.cooldown_until_ms);
writer.EndObject();
}
static int64_t parse_tablet_id(HttpRequest* req) {
const std::string& tablet_id_str = req->param("tablet_id");
if (tablet_id_str.empty()) {
return -1;
}
try {
return std::stoll(tablet_id_str);
} catch (...) {
return -1;
}
}
Status PeerCacheAction::_handle_show(HttpRequest* req, std::string* result) {
int64_t tablet_id = parse_tablet_id(req);
if (tablet_id < 0) {
return Status::InvalidArgument("missing or invalid parameter: tablet_id");
}
auto& mgr = _engine.cloud_warm_up_manager();
auto opt = mgr.get_tablet_peer_info(tablet_id);
if (!opt.has_value()) {
return Status::NotFound("tablet_id {} has no peer candidates", tablet_id);
}
rapidjson::StringBuffer buf;
rapidjson::Writer<rapidjson::StringBuffer> writer(buf);
tablet_peer_to_json(tablet_id, opt.value(), writer);
*result = buf.GetString();
return Status::OK();
}
Status PeerCacheAction::_handle_show_all(HttpRequest* req, std::string* result) {
int64_t limit = 1000; // default
const std::string& limit_str = req->param("limit");
if (!limit_str.empty()) {
try {
limit = std::stoll(limit_str);
} catch (...) {
return Status::InvalidArgument("invalid parameter: limit");
}
}
auto& mgr = _engine.cloud_warm_up_manager();
auto all = mgr.get_all_peer_info(limit);
rapidjson::StringBuffer buf;
rapidjson::Writer<rapidjson::StringBuffer> writer(buf);
writer.StartObject();
writer.Key("total");
writer.Int64(static_cast<int64_t>(all.size()));
writer.Key("tablets");
writer.StartArray();
for (const auto& [tid, tpc] : all) {
tablet_peer_to_json(tid, tpc, writer);
}
writer.EndArray();
writer.EndObject();
*result = buf.GetString();
return Status::OK();
}
Status PeerCacheAction::_handle_set(HttpRequest* req, std::string* result) {
int64_t tablet_id = parse_tablet_id(req);
if (tablet_id < 0) {
return Status::InvalidArgument("missing or invalid parameter: tablet_id");
}
const std::string& body = req->get_request_body();
if (body.empty()) {
return Status::InvalidArgument("missing request body (JSON expected)");
}
rapidjson::Document doc;
doc.Parse(body.c_str(), body.size());
if (doc.HasParseError()) {
return Status::InvalidArgument("invalid JSON body: parse error at offset {}",
doc.GetErrorOffset());
}
TabletPeerCandidates tpc;
// Parse candidates array
if (doc.HasMember("candidates") && doc["candidates"].IsArray()) {
for (const auto& item : doc["candidates"].GetArray()) {
PeerCandidate c;
if (item.HasMember("host") && item["host"].IsString()) {
c.host = item["host"].GetString();
}
if (item.HasMember("brpc_port") && item["brpc_port"].IsInt()) {
c.brpc_port = item["brpc_port"].GetInt();
}
if (item.HasMember("compute_group_id") && item["compute_group_id"].IsString()) {
c.compute_group_id = item["compute_group_id"].GetString();
}
if (item.HasMember("last_access_time_ms") && item["last_access_time_ms"].IsInt64()) {
c.last_access_time_ms = item["last_access_time_ms"].GetInt64();
}
if (item.HasMember("consecutive_rpc_failures") &&
item["consecutive_rpc_failures"].IsInt()) {
c.consecutive_rpc_failures = item["consecutive_rpc_failures"].GetInt();
}
// If last_access_time_ms not provided, set to current time
if (c.last_access_time_ms == 0) {
c.last_access_time_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
}
tpc.candidates.push_back(std::move(c));
}
}
if (doc.HasMember("last_successful_compute_group_id") &&
doc["last_successful_compute_group_id"].IsString()) {
tpc.last_successful_compute_group_id = doc["last_successful_compute_group_id"].GetString();
}
if (doc.HasMember("consecutive_all_miss") && doc["consecutive_all_miss"].IsInt()) {
tpc.consecutive_all_miss = doc["consecutive_all_miss"].GetInt();
}
if (doc.HasMember("cooldown_until_ms") && doc["cooldown_until_ms"].IsInt64()) {
tpc.cooldown_until_ms = doc["cooldown_until_ms"].GetInt64();
}
auto& mgr = _engine.cloud_warm_up_manager();
mgr.set_tablet_peer_candidates(tablet_id, std::move(tpc));
*result = R"({"status":"OK","msg":"peer candidates set successfully"})";
return Status::OK();
}
Status PeerCacheAction::_handle_remove(HttpRequest* req, std::string* result) {
int64_t tablet_id = parse_tablet_id(req);
if (tablet_id < 0) {
return Status::InvalidArgument("missing or invalid parameter: tablet_id");
}
auto& mgr = _engine.cloud_warm_up_manager();
mgr.remove_balanced_tablet(tablet_id);
*result = R"({"status":"OK","msg":"peer candidates removed"})";
return Status::OK();
}
Status PeerCacheAction::_handle_reset_cooldown(HttpRequest* req, std::string* result) {
int64_t tablet_id = parse_tablet_id(req);
if (tablet_id < 0) {
return Status::InvalidArgument("missing or invalid parameter: tablet_id");
}
auto& mgr = _engine.cloud_warm_up_manager();
auto opt = mgr.get_tablet_peer_info(tablet_id);
if (!opt.has_value()) {
return Status::NotFound("tablet_id {} has no peer candidates", tablet_id);
}
// Reset cooldown fields and write back
auto tpc = std::move(opt.value());
tpc.consecutive_all_miss = 0;
tpc.cooldown_until_ms = 0;
mgr.set_tablet_peer_candidates(tablet_id, std::move(tpc));
*result = R"({"status":"OK","msg":"cooldown reset"})";
return Status::OK();
}
void PeerCacheAction::handle(HttpRequest* req) {
req->add_output_header(HttpHeaders::CONTENT_TYPE, HEADER_JSON.data());
const std::string& op = req->param("op");
std::string result;
Status st;
if (op == "show") {
st = _handle_show(req, &result);
} else if (op == "show_all") {
st = _handle_show_all(req, &result);
} else if (op == "set") {
st = _handle_set(req, &result);
} else if (op == "remove") {
st = _handle_remove(req, &result);
} else if (op == "reset_cooldown") {
st = _handle_reset_cooldown(req, &result);
} else {
st = Status::InvalidArgument(
"unknown op '{}', supported: show, show_all, set, remove, reset_cooldown", op);
}
if (st.ok()) {
HttpChannel::send_reply(req, HttpStatus::OK, result);
} else {
HttpChannel::send_reply(req, HttpStatus::INTERNAL_SERVER_ERROR, st.to_json());
}
}
} // namespace doris