/* * Copyright (C) 2017-present ScyllaDB * */ /* * SPDX-License-Identifier: AGPL-3.0-or-later */ #pragma once #include #include #include #include #include #include #include #include #include #include "utils/updateable_value.hh" #include "seastarx.hh" namespace seastar { class file; } namespace seastar::json { class json_return_type; } namespace YAML { class Node; } namespace utils { namespace bpo = boost::program_options; class config_type { std::string_view _name; std::function _to_json; private: template std::function make_to_json(json::json_return_type (*func)(const NativeType&)) { return [func] (const void* value) { return func(*static_cast(value)); }; } public: template config_type(std::string_view name, json::json_return_type (*to_json)(const NativeType&)) : _name(name), _to_json(make_to_json(to_json)) {} std::string_view name() const { return _name; } json::json_return_type to_json(const void* value) const; }; template extern const config_type config_type_for; class config_file { static thread_local unsigned s_shard_id; struct any_value { virtual ~any_value() = default; virtual std::unique_ptr clone() const = 0; virtual void update_from(const any_value* source) = 0; }; std::vector>> _per_shard_values { 1 }; public: typedef std::unordered_map string_map; typedef std::vector string_list; enum class value_status { Used, Unused, Invalid, }; enum class liveness { LiveUpdate, MustRestart, }; enum class config_source : uint8_t { None, SettingsFile, CommandLine, CQL, Internal }; struct config_src { config_file* _cf; std::string_view _name, _alias, _desc; const config_type* _type; size_t _per_shard_values_offset; protected: virtual const void* current_value() const = 0; public: config_src(config_file* cf, std::string_view name, const config_type* type, std::string_view desc) : _cf(cf) , _name(name) , _desc(desc) , _type(type) {} config_src(config_file* cf, std::string_view name, std::string_view alias, const config_type* type, std::string_view desc) : _cf(cf) , _name(name) , _alias(alias) , _desc(desc) , _type(type) {} virtual ~config_src() {} const std::string_view & name() const { return _name; } std::string_view alias() const { return _alias; } const std::string_view & desc() const { return _desc; } std::string_view type_name() const { return _type->name(); } config_file * get_config_file() const { return _cf; } bool matches(std::string_view name) const; virtual void add_command_line_option(bpo::options_description_easy_init&) = 0; virtual void set_value(const YAML::Node&) = 0; virtual bool set_value(sstring, config_source = config_source::Internal) = 0; virtual value_status status() const noexcept = 0; virtual config_source source() const noexcept = 0; sstring source_name() const noexcept; json::json_return_type value_as_json() const; }; template struct named_value : public config_src { private: friend class config; config_source _source = config_source::None; value_status _value_status; struct the_value_type final : any_value { the_value_type(T value) : value(std::move(value)) {} utils::updateable_value_source value; virtual std::unique_ptr clone() const override { return std::make_unique(value()); } virtual void update_from(const any_value* source) override { auto typed_source = static_cast(source); value.set(typed_source->value()); } }; liveness _liveness; std::vector _allowed_values; protected: updateable_value_source& the_value() { any_value* av =_cf->_per_shard_values[_cf->s_shard_id][_per_shard_values_offset].get(); return static_cast(av)->value; } const updateable_value_source& the_value() const { return const_cast(this)->the_value(); } virtual const void* current_value() const override { return &the_value().get(); } public: typedef T type; typedef named_value MyType; named_value(config_file* file, std::string_view name, std::string_view alias, liveness liveness_, value_status vs, const T& t = T(), std::string_view desc = {}, std::initializer_list allowed_values = {}) : config_src(file, name, alias, &config_type_for, desc) , _value_status(vs) , _liveness(liveness_) , _allowed_values(std::move(allowed_values)) { file->add(*this, std::make_unique(std::move(t))); } named_value(config_file* file, std::string_view name, liveness liveness_, value_status vs, const T& t = T(), std::string_view desc = {}, std::initializer_list allowed_values = {}) : named_value(file, name, {}, liveness_, vs, t, desc) { } named_value(config_file* file, std::string_view name, std::string_view alias, value_status vs, const T& t = T(), std::string_view desc = {}, std::initializer_list allowed_values = {}) : named_value(file, name, alias, liveness::MustRestart, vs, t, desc, allowed_values) { } named_value(config_file* file, std::string_view name, value_status vs, const T& t = T(), std::string_view desc = {}, std::initializer_list allowed_values = {}) : named_value(file, name, {}, liveness::MustRestart, vs, t, desc, allowed_values) { } value_status status() const noexcept override { return _value_status; } config_source source() const noexcept override { return _source; } bool is_set() const { return _source > config_source::None; } MyType & operator()(const T& t, config_source src = config_source::Internal) { if (!_allowed_values.empty() && std::find(_allowed_values.begin(), _allowed_values.end(), t) == _allowed_values.end()) { throw std::invalid_argument(format("Invalid value for {}: got {} which is not inside the set of allowed values {}", name(), t, _allowed_values)); } the_value().set(t); if (src > config_source::None) { _source = src; } return *this; } MyType & operator()(T&& t, config_source src = config_source::Internal) { if (!_allowed_values.empty() && std::find(_allowed_values.begin(), _allowed_values.end(), t) == _allowed_values.end()) { throw std::invalid_argument(format("Invalid value for {}: got {} which is not inside the set of allowed values {}", name(), t, _allowed_values)); } the_value().set(std::move(t)); if (src > config_source::None) { _source = src; } return *this; } void set(T&& t, config_source src = config_source::None) { operator()(std::move(t), src); } const T& operator()() const { return the_value().get(); } operator updateable_value() const & { return updateable_value(the_value()); } observer observe(std::function callback) const { return the_value().observe(std::move(callback)); } void add_command_line_option(bpo::options_description_easy_init&) override; void set_value(const YAML::Node&) override; bool set_value(sstring, config_source = config_source::Internal) override; }; typedef std::reference_wrapper cfg_ref; config_file(std::initializer_list = {}); config_file(const config_file&) = delete; void add(cfg_ref, std::unique_ptr value); void add(std::initializer_list); void add(const std::vector &); boost::program_options::options_description get_options_description(); boost::program_options::options_description get_options_description(boost::program_options::options_description); boost::program_options::options_description_easy_init& add_options(boost::program_options::options_description_easy_init&); /** * Default behaviour for yaml parser is to throw on * unknown stuff, invalid opts or conversion errors. * * Error handling function allows overriding this. * * error: