blob: b49526280dfb25316175259f9a794807680181d2 [file] [log] [blame]
// 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 <boost/algorithm/string/join.hpp>
#include <fmt/core.h>
#include <algorithm>
#include <iosfwd>
#include <iterator>
#include <list>
#include <map>
#include <unordered_set>
#include <utility>
#include <vector>
#include "utils/config_api.h"
#include "utils/enum_helper.h"
#include "utils/error_code.h"
#include "utils/errors.h"
#include "utils/flags.h"
#include "utils/fmt_logging.h"
#include "utils/join_point.h"
#include "utils/output_utils.h"
#include "utils/singleton.h"
#include "utils/string_conv.h"
namespace dsn {
enum value_type
{
FV_BOOL = 0,
FV_INT32 = 1,
FV_UINT32 = 2,
FV_INT64 = 3,
FV_UINT64 = 4,
FV_DOUBLE = 5,
FV_STRING = 6,
FV_MAX_INDEX = 6,
};
ENUM_BEGIN(value_type, FV_MAX_INDEX)
ENUM_REG(FV_BOOL)
ENUM_REG(FV_INT32)
ENUM_REG(FV_UINT32)
ENUM_REG(FV_INT64)
ENUM_REG(FV_UINT64)
ENUM_REG(FV_DOUBLE)
ENUM_REG(FV_STRING)
ENUM_END(value_type)
class flag_data
{
public:
#define FLAG_DATA_LOAD_CASE(type, type_enum, suffix) \
case type_enum: \
value<type>() = dsn_config_get_value_##suffix(_section, _name, value<type>(), _desc); \
if (_validator) { \
CHECK(_validator(), "validation failed: {}", _name); \
} \
break
#define FLAG_DATA_UPDATE_CASE(type, type_enum, suffix) \
case type_enum: { \
type old_val = value<type>(), tmpval_##type_enum; \
if (!dsn::buf2##suffix(val, tmpval_##type_enum)) { \
return error_s::make(ERR_INVALID_PARAMETERS, fmt::format("{} is invalid", val)); \
} \
value<type>() = tmpval_##type_enum; \
if (_validator && !_validator()) { \
value<type>() = old_val; \
return error_s::make(ERR_INVALID_PARAMETERS, "value validation failed"); \
} \
std::string total_message; \
if (!on_update_value.execute(&total_message, true)) { \
value<type>() = old_val; \
return error_s::make(ERR_INVALID_PARAMETERS, total_message.c_str()); \
} \
} 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) {
FLAG_DATA_LOAD_CASE(int32_t, FV_INT32, int64);
FLAG_DATA_LOAD_CASE(int64_t, FV_INT64, int64);
FLAG_DATA_LOAD_CASE(uint32_t, FV_UINT32, uint64);
FLAG_DATA_LOAD_CASE(uint64_t, FV_UINT64, uint64);
FLAG_DATA_LOAD_CASE(bool, FV_BOOL, bool);
FLAG_DATA_LOAD_CASE(double, FV_DOUBLE, double);
FLAG_DATA_LOAD_CASE(const char *, FV_STRING, string);
}
}
flag_data(const char *section, const char *name, const char *desc, value_type type, void *val)
: on_update_value("flag.data.value.update"),
_type(type),
_val(val),
_section(section),
_name(name),
_desc(desc)
{
// For historical reason, check the 'section' parameter doesn't start with '"'.
CHECK_NE_MSG(*section, '"', "config section({}) should not start with '\"'", section);
}
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(); }
void to_table_printer(utils::table_printer &tp) const
{
#define TABLE_PRINTER_ADD_VALUE(type, type_enum) \
case type_enum: \
tp.add_row_name_and_data("value", value<type>()); \
break;
tp.add_row_name_and_data("name", _name);
tp.add_row_name_and_data("section", _section);
tp.add_row_name_and_data("type", enum_to_string(_type));
tp.add_row_name_and_data("tags", tags_str());
tp.add_row_name_and_data("description", _desc);
switch (_type) {
TABLE_PRINTER_ADD_VALUE(bool, FV_BOOL);
TABLE_PRINTER_ADD_VALUE(int32_t, FV_INT32);
TABLE_PRINTER_ADD_VALUE(uint32_t, FV_UINT32);
TABLE_PRINTER_ADD_VALUE(int64_t, FV_INT64);
TABLE_PRINTER_ADD_VALUE(uint64_t, FV_UINT64);
TABLE_PRINTER_ADD_VALUE(double, FV_DOUBLE);
TABLE_PRINTER_ADD_VALUE(const char *, FV_STRING);
}
}
std::string to_json() const
{
utils::table_printer tp;
to_table_printer(tp);
std::ostringstream out;
tp.output(out, utils::table_printer::output_format::kJsonCompact);
return out.str();
}
public:
join_point<bool, std::string *> on_update_value;
private:
template <typename T>
T &value() const
{
return *reinterpret_cast<T *>(_val);
}
std::string tags_str() const
{
std::string tags_str;
for (const auto &tag : _tags) {
tags_str += enum_to_string(tag);
tags_str += ",";
}
if (!tags_str.empty()) {
tags_str.pop_back();
}
return tags_str;
}
private:
const value_type _type;
void *const _val;
const char *_section;
const char *_name;
const char *_desc;
validator_fn _validator;
std::unordered_set<flag_tag> _tags;
};
class flag_registry : public utils::singleton<flag_registry>
{
public:
bool run_group_validators(std::map<std::string, std::string> &validate_messages)
{
bool valid = true;
for (const auto &validator : _group_flag_validators) {
std::string message;
if (!validator.second(message)) {
valid = false;
validate_messages[validator.first] = message;
}
}
return valid;
}
bool run_group_validators(std::string *total_message)
{
std::map<std::string, std::string> validate_messages;
bool valid = run_group_validators(validate_messages);
if (!valid && total_message != nullptr) {
std::vector<std::string> messages;
std::transform(validate_messages.begin(),
validate_messages.end(),
std::back_inserter(messages),
[](const std::pair<std::string, std::string> &message) {
std::string base(
fmt::format("group validator \"{}\" failed", message.first));
if (message.second.empty()) {
return base;
}
return fmt::format("{}: \"{}\"", base, message.second);
});
*total_message = boost::join(messages, "; ");
}
return valid;
}
void add_flag(const char *name, flag_data flag)
{
// We should run all group validators to find the potential inconsistency
auto group_validators_runner = std::bind<bool (flag_registry::*)(std::string *)>(
&flag_registry::run_group_validators, this, std::placeholders::_1);
flag.on_update_value.put_native(group_validators_runner);
_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);
CHECK(it != _flags.end(), "flag '{}' does not exist", name);
flag_data &flag = it->second;
if (!flag.validator()) {
flag.set_validator(validator);
}
}
void add_group_validator(const char *name, group_validator_fn &validator)
{
CHECK(_group_flag_validators.find(name) == _group_flag_validators.end(),
"duplicate group flag validator '{}'",
name);
_group_flag_validators[name] = validator;
}
void load_from_config()
{
for (auto &kv : _flags) {
flag_data &flag = kv.second;
flag.load();
}
std::string total_message;
if (!run_group_validators(&total_message)) {
CHECK(false, "{}", total_message);
}
}
void add_tag(const char *name, const flag_tag &tag)
{
auto it = _flags.find(name);
CHECK(it != _flags.end(), "flag '{}' 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);
}
error_with<std::string> get_flag_str(const std::string &name) const
{
const auto iter = _flags.find(name);
if (iter == _flags.end()) {
return error_s::make(ERR_OBJECT_NOT_FOUND, fmt::format("{} is not found", name));
}
return iter->second.to_json();
}
std::string list_all_flags() const
{
utils::multi_table_printer mtp;
for (const auto &flag : _flags) {
utils::table_printer tp(flag.first);
flag.second.to_table_printer(tp);
mtp.add(std::move(tp));
}
std::ostringstream out;
mtp.output(out, utils::table_printer::output_format::kJsonCompact);
return out.str();
}
private:
friend class utils::singleton<flag_registry>;
flag_registry() = default;
~flag_registry() = default;
private:
std::map<std::string, flag_data> _flags;
std::map<std::string, group_validator_fn> _group_flag_validators;
};
#define FLAG_REG_CONSTRUCTOR(type, type_enum) \
flag_registerer::flag_registerer( \
const char *section, const char *name, const char *desc, type *val) \
{ \
flag_registry::instance().add_flag(name, flag_data(section, name, desc, type_enum, val)); \
}
FLAG_REG_CONSTRUCTOR(int32_t, FV_INT32);
FLAG_REG_CONSTRUCTOR(uint32_t, FV_UINT32);
FLAG_REG_CONSTRUCTOR(int64_t, FV_INT64);
FLAG_REG_CONSTRUCTOR(uint64_t, FV_UINT64);
FLAG_REG_CONSTRUCTOR(bool, FV_BOOL);
FLAG_REG_CONSTRUCTOR(double, FV_DOUBLE);
FLAG_REG_CONSTRUCTOR(const char *, FV_STRING);
flag_validator::flag_validator(const char *name, validator_fn validator)
{
flag_registry::instance().add_validator(name, validator);
}
group_flag_validator::group_flag_validator(const char *name, group_validator_fn validator)
{
flag_registry::instance().add_group_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);
}
/*extern*/ error_with<std::string> get_flag_str(const std::string &flag_name)
{
return flag_registry::instance().get_flag_str(flag_name);
}
/*extern*/ std::string list_all_flags() { return flag_registry::instance().list_all_flags(); }
} // namespace dsn