Skip to content

Commit

Permalink
feat: support to modify configs without restart (XiaoMi#682)
Browse files Browse the repository at this point in the history
  • Loading branch information
levy5307 authored and zhangyifan27 committed Dec 25, 2020
1 parent 36c650c commit 1351aab
Show file tree
Hide file tree
Showing 10 changed files with 328 additions and 2 deletions.
22 changes: 22 additions & 0 deletions include/dsn/utility/flags.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
#include <string>
#include <cstdint>
#include <functional>
#include "errors.h"
#include "utils.h"

enum class flag_tag
{
FT_MUTABLE = 0, /** flag data is mutable */
};

// Example:
// DSN_DEFINE_string("core", filename, "my_file.txt", "The file to read");
Expand Down Expand Up @@ -52,6 +59,10 @@
dassert(FLAGS_VALIDATOR_FN_##name(FLAGS_##name), "validation failed: %s", #name); \
})

#define DSN_TAG_VARIABLE(name, tag) \
COMPILE_ASSERT(sizeof(decltype(FLAGS_##name)), exist_##name##_##tag); \
static dsn::flag_tagger FLAGS_TAGGER_##name##_##tag(#name, flag_tag::tag)

namespace dsn {

// An utility class that registers a flag upon initialization.
Expand All @@ -74,7 +85,18 @@ class flag_validator
flag_validator(const char *name, std::function<void()>);
};

class flag_tagger
{
public:
flag_tagger(const char *name, const flag_tag &tag);
};

// Loads all the flags from configuration.
extern void flags_initialize();

// update the specified flag to val
extern error_s update_flag(const std::string &name, const std::string &val);

// determine if the tag is exist for the specified flag
extern bool has_tag(const std::string &name, const flag_tag &tag);
} // namespace dsn
5 changes: 5 additions & 0 deletions include/dsn/utility/string_conv.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ inline bool buf2int64(string_view buf, int64_t &result)
return internal::buf2signed(buf, result);
}

inline bool buf2uint32(string_view buf, uint32_t &result)
{
return internal::buf2unsigned(buf, result);
}

inline bool buf2uint64(string_view buf, uint64_t &result)
{
return internal::buf2unsigned(buf, result);
Expand Down
24 changes: 22 additions & 2 deletions include/dsn/utility/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,26 @@

#define TIME_MS_MAX 0xffffffff

// The COMPILE_ASSERT macro can be used to verify that a compile time
// expression is true. For example, you could use it to verify the
// size of a static array:
//
// COMPILE_ASSERT(ARRAYSIZE(content_type_names) == CONTENT_NUM_TYPES,
// content_type_names_incorrect_size);
//
// or to make sure a struct is smaller than a certain size:
//
// COMPILE_ASSERT(sizeof(foo) < 128, foo_too_large);
//
// The second argument to the macro is the name of the variable. If
// the expression is false, most compilers will issue a warning/error
// containing the name of the variable.
struct CompileAssert
{
};

#define COMPILE_ASSERT(expr, msg) static const CompileAssert msg[bool(expr) ? 1 : -1]

namespace dsn {
namespace utils {

Expand Down Expand Up @@ -88,5 +108,5 @@ bool hostname(const dsn::rpc_address &address, std::string *hostname_result);
// valid_ip_network_order -> return TRUE && hostname_result=hostname |
// invalid_ip_network_order -> return FALSE
bool hostname_from_ip(uint32_t ip, std::string *hostname_result);
}
}
} // namespace utils
} // namespace dsn
5 changes: 5 additions & 0 deletions src/http/builtin_http_calls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ namespace dsn {
get_perf_counter_handler(req, resp);
})
.with_help("Gets the value of a perf counter");

register_http_call("updateConfig")
.with_callback(
[](const http_request &req, http_response &resp) { update_config(req, resp); })
.with_help("Updates the value of a config");
}

} // namespace dsn
2 changes: 2 additions & 0 deletions src/http/builtin_http_calls.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,6 @@ extern void get_help_handler(const http_request &req, http_response &resp);

extern void get_recent_start_time_handler(const http_request &req, http_response &resp);

extern void update_config(const http_request &req, http_response &resp);

} // namespace dsn
40 changes: 40 additions & 0 deletions src/http/config_http_service.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// 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 <dsn/http/http_server.h>
#include <dsn/utility/flags.h>
#include <dsn/utility/output_utils.h>

namespace dsn {
void update_config(const http_request &req, http_response &resp)
{
if (req.query_args.size() != 1) {
resp.status_code = http_status_code::bad_request;
return;
}

auto iter = req.query_args.begin();
auto res = update_flag(iter->first, iter->second);

utils::table_printer tp;
tp.add_row_name_and_data("update_status", res.description());
std::ostringstream out;
tp.output(out, dsn::utils::table_printer::output_format::kJsonCompact);
resp.body = out.str();
resp.status_code = http_status_code::ok;
}
} // namespace dsn
78 changes: 78 additions & 0 deletions src/utils/flags.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
#include <dsn/utility/flags.h>
#include <dsn/utility/config_api.h>
#include <dsn/utility/singleton.h>
#include <dsn/utility/errors.h>
#include <dsn/utility/string_conv.h>
#include <dsn/c/api_utilities.h>
#include <boost/optional/optional.hpp>
#include <fmt/format.h>

#include <map>

Expand Down Expand Up @@ -37,6 +40,19 @@ class flag_data
} \
break

#define FLAG_DATA_UPDATE_CASE(type, type_enum, suffix) \
case type_enum: \
type tmpval_##type_enum; \
if (!dsn::buf2##suffix(val, tmpval_##type_enum)) { \
return error_s::make(ERR_INVALID_PARAMETERS, fmt::format("{} in invalid", val)); \
} \
value<type>() = tmpval_##type_enum; \
break

#define FLAG_DATA_UPDATE_STRING() \
case FV_STRING: \
return error_s::make(ERR_INVALID_PARAMETERS, "string modifications are not supported")

void load()
{
switch (_type) {
Expand All @@ -55,9 +71,30 @@ class flag_data
{
}

error_s update(const std::string &val)
{
if (!has_tag(flag_tag::FT_MUTABLE)) {
return error_s::make(ERR_INVALID_PARAMETERS, fmt::format("{} is not mutable", _name));
}

switch (_type) {
FLAG_DATA_UPDATE_CASE(int32_t, FV_INT32, int32);
FLAG_DATA_UPDATE_CASE(int64_t, FV_INT64, int64);
FLAG_DATA_UPDATE_CASE(uint32_t, FV_UINT32, uint32);
FLAG_DATA_UPDATE_CASE(uint64_t, FV_UINT64, uint64);
FLAG_DATA_UPDATE_CASE(bool, FV_BOOL, bool);
FLAG_DATA_UPDATE_CASE(double, FV_DOUBLE, double);
FLAG_DATA_UPDATE_STRING();
}
return error_s::make(ERR_OK);
}

void set_validator(validator_fn &validator) { _validator = std::move(validator); }
const validator_fn &validator() const { return _validator; }

void add_tag(const flag_tag &tag) { _tags.insert(tag); }
bool has_tag(const flag_tag &tag) const { return _tags.find(tag) != _tags.end(); }

private:
template <typename T>
T &value()
Expand All @@ -72,13 +109,23 @@ class flag_data
const char *_name;
const char *_desc;
validator_fn _validator;
std::unordered_set<flag_tag> _tags;
};

class flag_registry : public utils::singleton<flag_registry>
{
public:
void add_flag(const char *name, flag_data flag) { _flags.emplace(name, flag); }

error_s update_flag(const std::string &name, const std::string &val)
{
auto it = _flags.find(name);
if (it == _flags.end()) {
return error_s::make(ERR_OBJECT_NOT_FOUND, fmt::format("{} is not found", name));
}
return it->second.update(val);
}

void add_validator(const char *name, validator_fn &validator)
{
auto it = _flags.find(name);
Expand All @@ -97,6 +144,22 @@ class flag_registry : public utils::singleton<flag_registry>
}
}

void add_tag(const char *name, const flag_tag &tag)
{
auto it = _flags.find(name);
dassert(it != _flags.end(), "flag \"%s\" does not exist", name);
it->second.add_tag(tag);
}

bool has_tag(const std::string &name, const flag_tag &tag) const
{
auto it = _flags.find(name);
if (it == _flags.end()) {
return false;
}
return it->second.has_tag(tag);
}

private:
friend class utils::singleton<flag_registry>;
flag_registry() = default;
Expand Down Expand Up @@ -125,6 +188,21 @@ flag_validator::flag_validator(const char *name, validator_fn validator)
flag_registry::instance().add_validator(name, validator);
}

flag_tagger::flag_tagger(const char *name, const flag_tag &tag)
{
flag_registry::instance().add_tag(name, tag);
}

/*extern*/ void flags_initialize() { flag_registry::instance().load_from_config(); }

/*extern*/ error_s update_flag(const std::string &name, const std::string &val)
{
return flag_registry::instance().update_flag(name, val);
}

/*extern*/ bool has_tag(const std::string &name, const flag_tag &tag)
{
return flag_registry::instance().has_tag(name, tag);
}

} // namespace dsn
105 changes: 105 additions & 0 deletions src/utils/test/flag_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// 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 <gtest/gtest.h>
#include <dsn/utility/flags.h>

namespace dsn {
namespace utils {

DSN_DEFINE_int32("flag_test", test_int32, 5, "");
DSN_TAG_VARIABLE(test_int32, FT_MUTABLE);

DSN_DEFINE_uint32("flag_test", test_uint32, 5, "");
DSN_TAG_VARIABLE(test_uint32, FT_MUTABLE);

DSN_DEFINE_int64("flag_test", test_int64, 5, "");
DSN_TAG_VARIABLE(test_int64, FT_MUTABLE);

DSN_DEFINE_uint64("flag_test", test_uint64, 5, "");
DSN_TAG_VARIABLE(test_uint64, FT_MUTABLE);

DSN_DEFINE_double("flag_test", test_double, 5.0, "");
DSN_TAG_VARIABLE(test_double, FT_MUTABLE);

DSN_DEFINE_bool("flag_test", test_bool, true, "");
DSN_TAG_VARIABLE(test_bool, FT_MUTABLE);

DSN_DEFINE_string("flag_test", test_string_immutable, "immutable_string", "");

TEST(flag_test, update_config)
{
auto res = update_flag("test_int32", "3");
ASSERT_TRUE(res.is_ok());
ASSERT_EQ(FLAGS_test_int32, 3);

res = update_flag("test_uint32", "3");
ASSERT_TRUE(res.is_ok());
ASSERT_EQ(FLAGS_test_uint32, 3);

res = update_flag("test_int64", "3");
ASSERT_TRUE(res.is_ok());
ASSERT_EQ(FLAGS_test_int64, 3);

res = update_flag("test_uint64", "3");
ASSERT_TRUE(res.is_ok());
ASSERT_EQ(FLAGS_test_uint64, 3);

res = update_flag("test_double", "3.0");
ASSERT_TRUE(res.is_ok());
ASSERT_EQ(FLAGS_test_double, 3.0);

res = update_flag("test_bool", "false");
ASSERT_TRUE(res.is_ok());
ASSERT_FALSE(FLAGS_test_bool);

// string modifications are not supported
res = update_flag("test_string_immutable", "update_string");
ASSERT_EQ(res.code(), ERR_INVALID_PARAMETERS);
ASSERT_EQ(strcmp(FLAGS_test_string_immutable, "immutable_string"), 0);

// test flag is not exist
res = update_flag("test_not_exist", "test_string");
ASSERT_EQ(res.code(), ERR_OBJECT_NOT_FOUND);

// test to update invalid value
res = update_flag("test_int32", "3ab");
ASSERT_EQ(res.code(), ERR_INVALID_PARAMETERS);
ASSERT_EQ(FLAGS_test_int32, 3);
}

DSN_DEFINE_int32("flag_test", has_tag, 5, "");
DSN_TAG_VARIABLE(has_tag, FT_MUTABLE);

DSN_DEFINE_int32("flag_test", no_tag, 5, "");

TEST(flag_test, tag_flag)
{
// has tag
auto res = has_tag("has_tag", flag_tag::FT_MUTABLE);
ASSERT_TRUE(res);

// doesn't has tag
res = has_tag("no_tag", flag_tag::FT_MUTABLE);
ASSERT_FALSE(res);

// flag is not exist
res = has_tag("no_flag", flag_tag::FT_MUTABLE);
ASSERT_FALSE(res);
}
} // namespace utils
} // namespace dsn
Loading

0 comments on commit 1351aab

Please sign in to comment.