/* * Copyright (C) 2015 ScyllaDB * */ /* * This file is part of Scylla. * * Scylla is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Scylla is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Scylla. If not, see . */ #include #include #include #include #include "config.hh" #include "core/file.hh" #include "core/reactor.hh" #include "core/shared_ptr.hh" #include "core/fstream.hh" #include "core/do_with.hh" #include "core/print.hh" #include "log.hh" #include static logging::logger clogger("config"); db::config::config() : // Initialize members to defaults. #define _mk_init(name, type, deflt, status, desc, ...) \ name(deflt), _make_config_values(_mk_init) _dummy(0) {} namespace bpo = boost::program_options; namespace YAML { /* * Add converters as needed here... */ template<> struct convert { static Node encode(sstring rhs) { auto p = rhs.c_str(); return convert::encode(p); } static bool decode(const Node& node, sstring& rhs) { std::string tmp; if (!convert::decode(node, tmp)) { return false; } rhs = tmp; return true; } }; template <> struct convert { static Node encode(const db::config::string_list& rhs) { Node node(NodeType::Sequence); for (auto& s : rhs) { node.push_back(convert::encode(s)); } return node; } static bool decode(const Node& node, db::config::string_list& rhs) { if (!node.IsSequence()) { return false; } rhs.clear(); for (auto& n : node) { sstring tmp; if (!convert::decode(n,tmp)) { return false; } rhs.push_back(tmp); } return true; } }; template<> struct convert{ static Node encode(const seastar::program_options::string_map& rhs) { Node node(NodeType::Map); for (auto& p : rhs) { node.force_insert(p.first, p.second); } return node; } static bool decode(const Node& node, seastar::program_options::string_map& rhs) { if (!node.IsMap()) { return false; } rhs.clear(); for (auto& n : node) { rhs[n.first.as()] = n.second.as(); } return true; } }; template<> struct convert { static Node encode(const db::config::seed_provider_type&) { throw std::runtime_error("should not reach"); } static bool decode(const Node& node, db::config::seed_provider_type& rhs) { if (!node.IsSequence()) { return false; } rhs = db::config::seed_provider_type(); for (auto& n : node) { if (!n.IsMap()) { continue; } for (auto& n2 : n) { if (n2.first.as() == "class_name") { rhs.class_name = n2.second.as(); } if (n2.first.as() == "parameters") { auto v = n2.second.as>(); if (!v.empty()) { rhs.parameters = v.front(); } } } } return true; } }; } /* * Helper type to do compile time exclusion of Unused/invalid options from * command line. * * Only opts marked "used" should get a boost::opt * */ template struct do_value_opt; template struct do_value_opt { template void operator()(Func&& func, const char* name, const T& dflt, T * dst, db::config::config_source & src, const char* desc) const { func(name, db::config::value_status::Used, dflt, dst, src, desc); } }; template struct do_value_opt { template void operator()(Func&& func, const char* name, const T& dflt, T* dst, db::config::config_source& src, const char* desc) const { func(name, db::config::value_status::UsedFromSeastar, dflt, dst, src, desc); } }; template<> struct do_value_opt { using seed_provider_type = db::config::seed_provider_type; template void operator()(Func&& func, const char* name, const seed_provider_type& dflt, seed_provider_type * dst, db::config::config_source & src, const char* desc) const { func((sstring(name) + "_class_name").c_str(), db::config::value_status::Used, dflt.class_name, &dst->class_name, src, desc); func((sstring(name) + "_parameters").c_str(), db::config::value_status::Used, dflt.parameters, &dst->parameters, src, desc); } }; template struct do_value_opt { template void operator()(Args&&...) const {} }; template struct do_value_opt { template void operator()(Args&&...) const {} }; /* * Our own bpo::typed_valye. * Only difference is that we _don't_ apply defaults (they are already applied) and therefore, the notifier doesn't * execute for default values (only for explicitly provided ones). * Needed to make aliases and usage-checking work properly. */ template class typed_value_ex : public bpo::typed_value { public: typedef bpo::typed_value _Super; typed_value_ex(T* store_to) : _Super(store_to) {} bool apply_default(boost::any&) const override { return false; } }; template inline typed_value_ex* value_ex(T* v) { typed_value_ex* r = new typed_value_ex(v); return r; } template inline typed_value_ex>* value_ex(std::vector* v) { auto r = new typed_value_ex>(v); r->multitoken(); return r; } static sstring replace_underscores_with_hyphens(const char* v) { sstring result(v); std::replace(result.begin(), result.end(), '_', '-'); return result; } bpo::options_description_easy_init db::config::add_options(bpo::options_description& opts) { bpo::options_description_easy_init init = opts.add_options(); auto opt_add = [&init, &opts](const char* name, const value_status& status, const auto& dflt, auto* dst, auto& src, const char* desc) { const auto hyphenated_name = replace_underscores_with_hyphens(name); switch (status) { case value_status::Used: { init(hyphenated_name.c_str(), value_ex(dst)->default_value(dflt)->notifier([&src](auto) { src = config_source::CommandLine; }), desc); break; } case value_status::UsedFromSeastar: { auto* seastar_opt = opts.find_nothrow(hyphenated_name, false); if (!seastar_opt) { throw std::runtime_error(seastar::sprint("Expected Seastar to define a command-line option '%s'", hyphenated_name)); } // // Just a type-check. // using value_type = typename std::remove_pointer::type; auto value = boost::dynamic_pointer_cast>(seastar_opt->semantic()); if (value == nullptr) { throw std::runtime_error( sprint( "The type of the Seastar-defined option '%s' does not match its declared type " "in Scylla's configuration", hyphenated_name)); } break; } case value_status::Invalid: case value_status::Unused: // Nothing. break; } }; // Add all used opts as command line opts #define _add_boost_opt(name, type, deflt, status, desc, ...) \ do_value_opt()(opt_add, #name, type( deflt ), &name._value, name._source, desc); _make_config_values(_add_boost_opt) auto alias_add = [&init](const char* name, auto& dst, const char* desc) { init(name, value_ex(&dst._value)->notifier([&dst](auto&) { dst._source = config_source::CommandLine; }), desc); }; // Handle "old" syntax with "aliases" alias_add("datadir", data_file_directories, "alias for 'data-file-directories'"); alias_add("thrift-port", rpc_port, "alias for 'rpc-port'"); alias_add("cql-port", native_transport_port, "alias for 'native-transport-port'"); return opts.add_options(); } void db::config::apply_seastar_options(boost::program_options::variables_map& vars) { const auto do_apply = [&vars](const char* name, const db::config::value_status& status, auto* dst, auto& src) { // This could be done at compile-time, but this is more clear and it's not a bottleneck. if (status == db::config::value_status::UsedFromSeastar) { const auto hyphenated_name = replace_underscores_with_hyphens(name); using value_type = typename std::remove_pointer::type; const auto& variable_value = vars[hyphenated_name]; *dst = variable_value.as(); if (!variable_value.defaulted()) { src = db::config::config_source::CommandLine; } } }; #define _add_seastar_opt(name, type, deflt, status, desc, ...) \ do_apply(#name, db::config::value_status::status, &(name._value), name._source); _make_config_values(_add_seastar_opt) } // Virtual dispatch to convert yaml->data type. struct handle_yaml { virtual ~handle_yaml() = default; virtual void operator()(const YAML::Node&) = 0; virtual db::config::value_status status() const = 0; virtual db::config::config_source source() const = 0; }; template struct handle_yaml_impl : public handle_yaml { typedef db::config::value value_type; handle_yaml_impl(value_type& v, db::config::config_source& src) : _dst(v), _src(src) {} void operator()(const YAML::Node& node) override { _dst(node.as()); _src = db::config::config_source::SettingsFile; } db::config::value_status status() const override { return _dst.status(); } db::config::config_source source() const override { return _src; } value_type& _dst; db::config::config_source& _src; }; void db::config::read_from_yaml(const sstring& yaml) { read_from_yaml(yaml.c_str()); } void db::config::read_from_yaml(const char* yaml) { std::unordered_map> values; #define _add_yaml_opt(name, type, deflt, status, desc, ...) \ values.emplace(#name, std::make_unique>(name, name._source)); _make_config_values(_add_yaml_opt) /* * Note: this is not very "half-fault" tolerant. I.e. there could be * yaml syntax errors that origin handles and still sets the options * where as we don't... * There are no exhaustive attempts at converting, we rely on syntax of * file mapping to the data type... */ auto doc = YAML::Load(yaml); for (auto node : doc) { auto label = node.first.as(); auto i = values.find(label); if (i == values.end()) { clogger.warn("Unknown option {} ignored.", label); continue; } if (i->second->source() > config_source::SettingsFile) { clogger.debug("Option {} already set by commandline. ignored.", label); continue; } switch (i->second->status()) { case value_status::Invalid: clogger.warn("Option {} is not applicable. Ignoring.", label); continue; case value_status::Unused: clogger.warn("Option {} is not (yet) used.", label); break; default: break; } if (node.second.IsNull()) { clogger.debug("Option {}, empty value. Skipping.", label); continue; } // Still, a syntax error is an error warning, not a fail try { (*i->second)(node.second); } catch (...) { clogger.error("Option {}, exception while converting value.", label); } } for (auto& p : values) { if (p.second->status() > value_status::Used) { continue; } if (p.second->source() > config_source::None) { continue; } clogger.debug("Option {} not set", p.first); } } future<> db::config::read_from_file(file f) { return f.size().then([this, f](size_t s) { return do_with(make_file_input_stream(f), [this, s](input_stream& in) { return in.read_exactly(s).then([this](temporary_buffer buf) { read_from_yaml(sstring(buf.begin(), buf.end())); }); }); }); } future<> db::config::read_from_file(const sstring& filename) { return open_file_dma(filename, open_flags::ro).then([this](file f) { return read_from_file(std::move(f)); }); } boost::filesystem::path db::config::get_conf_dir() { using namespace boost::filesystem; path confdir; auto* cd = std::getenv("SCYLLA_CONF"); if (cd != nullptr) { confdir = path(cd); } else { auto* p = std::getenv("SCYLLA_HOME"); if (p != nullptr) { confdir = path(p); } confdir /= "conf"; } return confdir; } void db::config::check_experimental(const sstring& what) const { if (!experimental()) { throw std::runtime_error(sprint("%s is currently disabled. Start Scylla with --experimental=on to enable.", what)); } }