Skip to content

Commit 7784377

Browse files
[feature](meta-service) Support dynamic configuration for meta service
1 parent 1ffecfd commit 7784377

File tree

5 files changed

+210
-3
lines changed

5 files changed

+210
-3
lines changed

cloud/src/common/configbase.cpp

+106
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
#include <algorithm>
1919
#include <cerrno>
2020
#include <cstring>
21+
#include <filesystem>
2122
#include <fstream>
2223
#include <iostream>
2324
#include <list>
2425
#include <map>
26+
#include <mutex>
2527
#include <sstream>
2628

2729
#define __IN_CONFIGBASE_CPP__
@@ -30,9 +32,13 @@
3032

3133
namespace doris::cloud::config {
3234

35+
std::string_view g_conf_path {};
36+
3337
std::map<std::string, Register::Field>* Register::_s_field_map = nullptr;
3438
std::map<std::string, std::function<bool()>>* RegisterConfValidator::_s_field_validator = nullptr;
3539
std::map<std::string, std::string>* full_conf_map = nullptr;
40+
std::mutex mutable_string_config_lock;
41+
std::mutex conf_persist_lock;
3642

3743
// trim string
3844
std::string& trim(std::string& s) {
@@ -224,6 +230,38 @@ bool Properties::load(const char* conf_file, bool must_exist) {
224230
return true;
225231
}
226232

233+
// dump props to conf file
234+
bool Properties::dump(const std::string& conffile) {
235+
std::string conffile_tmp = conffile + ".tmp";
236+
237+
if (std::filesystem::exists(conffile)) {
238+
// copy for modify
239+
std::ifstream in(conffile, std::ios::binary);
240+
std::ofstream out(conffile_tmp, std::ios::binary);
241+
out << in.rdbuf();
242+
in.close();
243+
out.close();
244+
}
245+
246+
std::ofstream file_writer;
247+
248+
file_writer.open(conffile_tmp, std::ios::out | std::ios::app);
249+
250+
file_writer << std::endl;
251+
252+
for (auto const& iter : file_conf_map) {
253+
file_writer << iter.first << " = " << iter.second << std::endl;
254+
}
255+
256+
file_writer.close();
257+
if (!file_writer.good()) {
258+
return false;
259+
}
260+
261+
std::filesystem::rename(conffile_tmp, conffile);
262+
return std::filesystem::exists(conffile);
263+
}
264+
227265
template <typename T>
228266
bool Properties::get_or_default(const char* key, const char* defstr, T& retval,
229267
bool* is_retval_set) const {
@@ -297,6 +335,37 @@ std::ostream& operator<<(std::ostream& out, const std::vector<T>& v) {
297335
continue; \
298336
}
299337

338+
#define UPDATE_FIELD(FIELD, VALUE, TYPE, PERSIST) \
339+
if (strcmp((FIELD).type, #TYPE) == 0) { \
340+
TYPE new_value; \
341+
if (!convert((VALUE), new_value)) { \
342+
std::cerr << "convert " << VALUE << "as" << #TYPE << "failed"; \
343+
return false; \
344+
} \
345+
TYPE& ref_conf_value = *reinterpret_cast<TYPE*>((FIELD).storage); \
346+
TYPE old_value = ref_conf_value; \
347+
if (RegisterConfValidator::_s_field_validator != nullptr) { \
348+
auto validator = RegisterConfValidator::_s_field_validator->find((FIELD).name); \
349+
if (validator != RegisterConfValidator::_s_field_validator->end() && \
350+
!(validator->second)()) { \
351+
ref_conf_value = old_value; \
352+
std::cerr << "validate " << (FIELD).name << "=" << new_value << " failed" \
353+
<< std::endl; \
354+
return false; \
355+
} \
356+
} \
357+
ref_conf_value = new_value; \
358+
if (full_conf_map != nullptr) { \
359+
std::ostringstream oss; \
360+
oss << new_value; \
361+
(*full_conf_map)[(FIELD).name] = oss.str(); \
362+
} \
363+
if (PERSIST) { \
364+
props.set_force(std::string((FIELD).name), VALUE); \
365+
} \
366+
return true; \
367+
}
368+
300369
// init conf fields
301370
bool init(const char* conf_file, bool fill_conf_map, bool must_exist, bool set_to_default) {
302371
Properties props;
@@ -328,4 +397,41 @@ bool init(const char* conf_file, bool fill_conf_map, bool must_exist, bool set_t
328397
return true;
329398
}
330399

400+
bool do_set_config(const Register::Field& feild, const std::string& value, bool need_persist,
401+
Properties& props) {
402+
UPDATE_FIELD(feild, value, bool, need_persist);
403+
UPDATE_FIELD(feild, value, int16_t, need_persist);
404+
UPDATE_FIELD(feild, value, int32_t, need_persist);
405+
UPDATE_FIELD(feild, value, int64_t, need_persist);
406+
UPDATE_FIELD(feild, value, double, need_persist);
407+
{
408+
// add lock to ensure thread safe
409+
std::lock_guard<std::mutex> lock(mutable_string_config_lock);
410+
UPDATE_FIELD(feild, value, std::string, need_persist);
411+
}
412+
return false;
413+
}
414+
415+
bool set_config(const std::string& field, const std::string& value, bool need_persist) {
416+
auto it = Register::_s_field_map->find(field);
417+
if (it == Register::_s_field_map->end()) {
418+
return false;
419+
}
420+
if (!it->second.valmutable) {
421+
return false;
422+
}
423+
Properties props;
424+
if (!do_set_config(it->second, value, need_persist, props)) {
425+
std::cerr << "not supported to modify: " << field << "=" << value << std::endl;
426+
return false;
427+
}
428+
429+
if (need_persist) {
430+
// lock to make sure only one thread can modify the be_custom.conf
431+
std::lock_guard<std::mutex> l(conf_persist_lock);
432+
return props.dump(config::g_conf_path.data());
433+
}
434+
return true;
435+
}
436+
331437
} // namespace doris::cloud::config

cloud/src/common/configbase.h

+3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626

2727
namespace doris::cloud::config {
2828

29+
extern std::string_view g_conf_path;
30+
2931
class Register {
3032
public:
3133
struct Field {
@@ -170,4 +172,5 @@ extern std::map<std::string, std::string>* full_conf_map;
170172
bool init(const char* conf_file, bool fill_conf_map = false, bool must_exist = true,
171173
bool set_to_default = true);
172174

175+
bool set_config(const std::string& field, const std::string& value, bool need_persist = false);
173176
} // namespace doris::cloud::config

cloud/src/main.cpp

+4-3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
#include "common/arg_parser.h"
3535
#include "common/config.h"
36+
#include "common/configbase.h"
3637
#include "common/encryption_util.h"
3738
#include "common/logging.h"
3839
#include "meta-service/mem_txn_kv.h"
@@ -193,9 +194,9 @@ int main(int argc, char** argv) {
193194
return -1;
194195
}
195196

196-
auto conf_file = args.get<std::string>(ARG_CONF);
197-
if (!config::init(conf_file.c_str(), true)) {
198-
std::cerr << "failed to init config file, conf=" << conf_file << std::endl;
197+
config::g_conf_path = args.get<std::string>(ARG_CONF);
198+
if (!config::init(config::g_conf_path.data(), true)) {
199+
std::cerr << "failed to init config file, conf=" << config::g_conf_path << std::endl;
199200
return -1;
200201
}
201202

cloud/src/meta-service/meta_service_http.cpp

+30
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <brpc/controller.h>
2121
#include <brpc/http_status_code.h>
2222
#include <brpc/uri.h>
23+
#include <fmt/core.h>
2324
#include <fmt/format.h>
2425
#include <gen_cpp/cloud.pb.h>
2526
#include <glog/logging.h>
@@ -44,7 +45,9 @@
4445
#include <vector>
4546

4647
#include "common/config.h"
48+
#include "common/configbase.h"
4749
#include "common/logging.h"
50+
#include "common/string_util.h"
4851
#include "meta-service/keys.h"
4952
#include "meta-service/txn_kv.h"
5053
#include "meta-service/txn_kv_error.h"
@@ -448,6 +451,31 @@ static HttpResponse process_query_rate_limit(MetaServiceImpl* service, brpc::Con
448451
return http_json_reply(MetaServiceCode::OK, "", sb.GetString());
449452
}
450453

454+
static HttpResponse process_update_config(MetaServiceImpl* service, brpc::Controller* cntl) {
455+
const auto& uri = cntl->http_request().uri();
456+
bool persist = (http_query(uri, "persist") == "true");
457+
auto configs = std::string {http_query(uri, "configs")};
458+
if (configs.empty()) [[unlikely]] {
459+
return http_json_reply(MetaServiceCode::INVALID_ARGUMENT,
460+
"query param `config` should not be empty");
461+
}
462+
auto conf_list = split(configs, ',');
463+
for (const auto& conf : conf_list) {
464+
auto conf_pair = split(conf, '=');
465+
if (conf_pair.size() != 2) [[unlikely]] {
466+
return http_json_reply(MetaServiceCode::INVALID_ARGUMENT,
467+
fmt::format("config {} is invalid", configs));
468+
}
469+
trim(conf_pair[0]);
470+
trim(conf_pair[1]);
471+
if (!config::set_config(conf_pair[0], conf_pair[1], persist)) {
472+
return http_json_reply(MetaServiceCode::INVALID_ARGUMENT,
473+
fmt::format("set {}={} failed", conf_pair[0], conf_pair[1]));
474+
}
475+
}
476+
return http_json_reply(MetaServiceCode::OK, "");
477+
}
478+
451479
static HttpResponse process_decode_key(MetaServiceImpl*, brpc::Controller* ctrl) {
452480
auto& uri = ctrl->http_request().uri();
453481
std::string_view key = http_query(uri, "key");
@@ -732,12 +760,14 @@ void MetaServiceImpl::http(::google::protobuf::RpcController* controller,
732760
{"alter_iam", process_alter_iam},
733761
{"adjust_rate_limit", process_adjust_rate_limit},
734762
{"list_rate_limit", process_query_rate_limit},
763+
{"update_config", process_update_config},
735764
{"v1/abort_txn", process_abort_txn},
736765
{"v1/abort_tablet_job", process_abort_tablet_job},
737766
{"v1/alter_ram_user", process_alter_ram_user},
738767
{"v1/alter_iam", process_alter_iam},
739768
{"v1/adjust_rate_limit", process_adjust_rate_limit},
740769
{"v1/list_rate_limit", process_query_rate_limit},
770+
{"v1/update_config", process_update_config},
741771
};
742772

743773
auto* cntl = static_cast<brpc::Controller*>(controller);

cloud/test/meta_service_http_test.cpp

+67
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,13 @@
3232
#include <rapidjson/stringbuffer.h>
3333

3434
#include <cstddef>
35+
#include <cstdint>
36+
#include <filesystem>
3537
#include <optional>
38+
#include <string>
3639

3740
#include "common/config.h"
41+
#include "common/configbase.h"
3842
#include "common/logging.h"
3943
#include "common/util.h"
4044
#include "cpp/sync_point.h"
@@ -1625,4 +1629,67 @@ TEST(MetaServiceHttpTest, QueryRateLimit) {
16251629
}
16261630
}
16271631

1632+
TEST(MetaServiceHttpTest, UpdateConfig) {
1633+
HttpContext ctx;
1634+
{
1635+
auto [status_code, content] = ctx.query<std::string>("update_config", "");
1636+
ASSERT_EQ(status_code, 400);
1637+
std::string msg = "query param `config` should not be empty";
1638+
ASSERT_NE(content.find(msg), std::string::npos);
1639+
}
1640+
{
1641+
auto [status_code, content] = ctx.query<std::string>("update_config", "configs=aaa");
1642+
ASSERT_EQ(status_code, 400);
1643+
std::string msg = "config aaa is invalid";
1644+
ASSERT_NE(content.find(msg), std::string::npos);
1645+
}
1646+
{
1647+
auto [status_code, content] = ctx.query<std::string>("update_config", "configs=aaa=bbb");
1648+
ASSERT_EQ(status_code, 400);
1649+
std::string msg = "set aaa=bbb failed";
1650+
ASSERT_NE(content.find(msg), std::string::npos);
1651+
}
1652+
{
1653+
auto [status_code, content] =
1654+
ctx.query<std::string>("update_config", "configs=recycle_interval_seconds=3599");
1655+
ASSERT_EQ(status_code, 200);
1656+
ASSERT_EQ(config::recycle_interval_seconds, 3599);
1657+
}
1658+
{
1659+
auto [status_code, content] = ctx.query<std::string>(
1660+
"update_config", "configs=recycle_interval_seconds=3601,retention_seconds=259201");
1661+
ASSERT_EQ(status_code, 200);
1662+
ASSERT_EQ(config::retention_seconds, 259201);
1663+
ASSERT_EQ(config::recycle_interval_seconds, 3601);
1664+
}
1665+
{
1666+
config::g_conf_path = "./doris_cloud.conf";
1667+
auto [status_code, content] = ctx.query<std::string>(
1668+
"update_config",
1669+
"configs=recycle_interval_seconds=3659,retention_seconds=259219&persist=true");
1670+
ASSERT_EQ(status_code, 200);
1671+
ASSERT_EQ(config::recycle_interval_seconds, 3659);
1672+
ASSERT_EQ(config::retention_seconds, 259219);
1673+
config::Properties props;
1674+
ASSERT_TRUE(props.load(config::g_conf_path.data(), true));
1675+
{
1676+
bool new_val_set = false;
1677+
int64_t recycle_interval_s = 0;
1678+
ASSERT_TRUE(props.get_or_default("recycle_interval_seconds", nullptr,
1679+
recycle_interval_s, &new_val_set));
1680+
ASSERT_TRUE(new_val_set);
1681+
ASSERT_EQ(recycle_interval_s, 3659);
1682+
}
1683+
{
1684+
bool new_val_set = false;
1685+
int64_t retention_s = 0;
1686+
ASSERT_TRUE(
1687+
props.get_or_default("retention_seconds", nullptr, retention_s, &new_val_set));
1688+
ASSERT_TRUE(new_val_set);
1689+
ASSERT_EQ(retention_s, 259219);
1690+
}
1691+
std::filesystem::remove(config::g_conf_path);
1692+
}
1693+
}
1694+
16281695
} // namespace doris::cloud

0 commit comments

Comments
 (0)