MXS-2346 Provide new configuration mechanism
The configuration mechanism consists of the following concepts: Specification Specifies the available configuration parameters of a module, their names and their types. Param Specifies a parameter, its name and its type. Type Specifies the type of a configuration parameters; Bool, Size, Count, etc. Configuration Specifies the configuration values of a particular instance of the module. Configuration walks hand in hand with Specification, the latter specifies what the former should contain. A Specification is capable of configuring a Configuration from a MXS_CONFIG_PARAMETER, checking in the process that all parameters are of the correct type and that the required parameters are present. A Specification is capable of persisting itself so that it later can be read back. The mechanism is closed for modification but open for extension in the sense that if a module requires a custom parameter, all it needs to do is to derive one class from Param and another from Type. The canonical way for using this mechanism is as follows. Consider a module xyx that has three parameters; a parameter called "enabled" that is of boolean type, a parameter called "period" that is of duration type, and a parameter "cache" that is of size type. That would be declared as follows: // xyz.hh class XYZSession; class XYZ : public maxscale::Filter<XYZ, XYZSession> { public: static XYZ* create(const char* zName, MXS_CONFIG_PARAMETER* pParams); private: XYZ(); static config::Specification s_specification; static config::ParamBool s_enabled; static config::ParamDuration<std::chrono::seconds> s_period; static config::ParamSize s_cache; config::Configuration m_configuration; config::Bool m_enabled; config::Duration<std::chrono::seconds> m_period; config::Size m_cache; }; // xyz.cc config::Specification XYZ::s_specification(MXS_MODULE_NAME); config::ParamBool XYZ::s_enabled( &s_specification, "enabled", "Specifies whether ... should be enabled or not." ); config::ParamDuration<std::chrono::seconds> XYZ::s_period( &s_specification, "period", "Specifies the period. Rounded to the nearest second." ); config::ParamSize XYZ::s_cache( &s_specification, "cache", "Specifies the size of the internal cache." ); XYZ::XYZ() : m_configuration(&s_specification) , m_enabled(&m_configuration, &s_enabled) , m_period(&m_configuration, &s_period) , m_cache(&m_configuration, &s_cache) { } XYZ* XYZ::create(const char* zName, MXS_CONFIG_PARAMETER* pParams) { XYZ* pXyz = new XYZ; if (!s_specification.configure(pXyz->m_configuration, pParams)) { delete pXyz; pXyz = nullptr; } return pXyz; }
This commit is contained in:
parent
d2c71472b0
commit
09702ab0a0
@ -665,3 +665,25 @@ inline bool config_is_valid_name(const std::string& name, std::string* reason =
|
||||
{
|
||||
return config_is_valid_name(name.c_str());
|
||||
}
|
||||
|
||||
// TEMPORARILY EXPOSED.
|
||||
bool check_path_parameter(const MXS_MODULE_PARAM* params, const char* value);
|
||||
|
||||
/**
|
||||
* Converts a string into a duration, intepreting in a case-insensitive manner
|
||||
* an 'h'-suffix to indicate hours, an 'm'-suffix to indicate minutes, an
|
||||
* 's'-suffix to indicate seconds and an 'ms'-suffix to indicate milliseconds.
|
||||
*
|
||||
* @param zValue A numerical string, possibly suffixed by 'h', 'm',
|
||||
* 's' or 'ms'.
|
||||
* @param interpretation How a value lacking a specific suffix should be interpreted.
|
||||
* @param pDuration Pointer, if non-NULL, where the result is stored.
|
||||
* @param pUnit Pointer, if non-NULL, where the detected unit is stored.
|
||||
*
|
||||
* @return True on success, false on invalid input in which case @c pUnit and
|
||||
* @c pDuration will not be modified.
|
||||
*/
|
||||
bool get_suffixed_duration(const char* zValue,
|
||||
mxs::config::DurationInterpretation interpretation,
|
||||
std::chrono::milliseconds* pDuration,
|
||||
mxs::config::DurationUnit* pUnit = nullptr);
|
||||
|
1349
include/maxscale/config2.hh
Normal file
1349
include/maxscale/config2.hh
Normal file
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@ add_library(maxscale-common SHARED
|
||||
backend.cc
|
||||
buffer.cc
|
||||
config.cc
|
||||
config2.cc
|
||||
config_runtime.cc
|
||||
dcb.cc
|
||||
encryption.cc
|
||||
|
@ -4218,7 +4218,8 @@ bool config_is_ssl_parameter(const char* key)
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool check_path_parameter(const MXS_MODULE_PARAM* params, const char* value)
|
||||
// TEMPORARILY EXPOSED
|
||||
/*static*/ bool check_path_parameter(const MXS_MODULE_PARAM* params, const char* value)
|
||||
{
|
||||
bool valid = false;
|
||||
|
||||
|
718
server/core/config2.cc
Normal file
718
server/core/config2.cc
Normal file
@ -0,0 +1,718 @@
|
||||
/*
|
||||
* Copyright (c) 2018 MariaDB Corporation Ab
|
||||
*
|
||||
* Use of this software is governed by the Business Source License included
|
||||
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
|
||||
*
|
||||
* Change Date: 2022-01-01
|
||||
*
|
||||
* On the date above, in accordance with the Business Source License, use
|
||||
* of this software will be governed by version 2 or later of the General
|
||||
* Public License.
|
||||
*/
|
||||
|
||||
#include <maxscale/config2.hh>
|
||||
#include "internal/config.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace config
|
||||
{
|
||||
|
||||
/**
|
||||
* class Specification
|
||||
*/
|
||||
Specification::Specification(const char* zModule)
|
||||
: m_module(zModule)
|
||||
{
|
||||
}
|
||||
|
||||
Specification::~Specification()
|
||||
{
|
||||
}
|
||||
|
||||
const string& Specification::module() const
|
||||
{
|
||||
return m_module;
|
||||
}
|
||||
|
||||
const Param* Specification::find_param(const string& name) const
|
||||
{
|
||||
auto it = m_params.find(name);
|
||||
|
||||
return it != m_params.end() ? it->second : nullptr;
|
||||
}
|
||||
|
||||
ostream& Specification::document(ostream& out) const
|
||||
{
|
||||
for (const auto& entry : m_params)
|
||||
{
|
||||
out << entry.second->documentation() << endl;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
bool Specification::validate(const MXS_CONFIG_PARAMETER& params) const
|
||||
{
|
||||
bool valid = true;
|
||||
|
||||
set<string> provided;
|
||||
|
||||
for (const auto& param : params)
|
||||
{
|
||||
const auto& name = param.first;
|
||||
const auto& value = param.second;
|
||||
|
||||
const Param* pParam = find_param(name.c_str());
|
||||
|
||||
if (pParam)
|
||||
{
|
||||
string message;
|
||||
|
||||
if (!pParam->validate(value.c_str(), &message))
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!message.empty())
|
||||
{
|
||||
if (valid)
|
||||
{
|
||||
MXS_WARNING("%s: %s", name.c_str(), message.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("%s: %s", name.c_str(), message.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
provided.insert(name);
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_WARNING("%s: The parameter '%s' is unrecognized.", m_module.c_str(), name.c_str());
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& entry : m_params)
|
||||
{
|
||||
const Param* pParam = entry.second;
|
||||
|
||||
if (pParam->is_mandatory() && (provided.find(pParam->name()) == provided.end()))
|
||||
{
|
||||
MXS_ERROR("%s: The mandatory parameter '%s' is not provided.",
|
||||
m_module.c_str(), pParam->name().c_str());
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
bool Specification::configure(Configuration& configuration, const MXS_CONFIG_PARAMETER& params) const
|
||||
{
|
||||
mxb_assert(validate(params));
|
||||
mxb_assert(size() == configuration.size());
|
||||
|
||||
bool configured = true;
|
||||
|
||||
for (const auto& param : params)
|
||||
{
|
||||
const auto& name = param.first;
|
||||
const auto& value = param.second;
|
||||
|
||||
const Param* pParam = find_param(name.c_str());
|
||||
config::Type* pValue = configuration.find_value(name.c_str());
|
||||
|
||||
mxb_assert(pValue && pParam); // Should have been validated.
|
||||
mxb_assert(&pValue->parameter() == pParam);
|
||||
|
||||
if (pParam && pValue)
|
||||
{
|
||||
if (!pParam->set(*pValue, value.c_str()))
|
||||
{
|
||||
mxb_assert(!true);
|
||||
configured = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("%s: The parameter '%s' is unrecognized.", m_module.c_str(), name.c_str());
|
||||
configured = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (configured)
|
||||
{
|
||||
configured = configuration.configure();
|
||||
}
|
||||
|
||||
return configured;
|
||||
}
|
||||
|
||||
void Specification::populate(MXS_MODULE& module) const
|
||||
{
|
||||
MXS_MODULE_PARAM* pModule_param = &module.parameters[0];
|
||||
|
||||
for (const auto& entry : m_params)
|
||||
{
|
||||
const Param* pParam = entry.second;
|
||||
|
||||
pParam->populate(*pModule_param);
|
||||
++pModule_param;
|
||||
}
|
||||
}
|
||||
|
||||
size_t Specification::size() const
|
||||
{
|
||||
return m_params.size();
|
||||
}
|
||||
|
||||
void Specification::insert(Param* pParam)
|
||||
{
|
||||
mxb_assert(m_params.find(pParam->name()) == m_params.end());
|
||||
|
||||
m_params.insert(make_pair(pParam->name(), pParam));
|
||||
}
|
||||
|
||||
void Specification::remove(Param* pParam)
|
||||
{
|
||||
auto it = m_params.find(pParam->name());
|
||||
mxb_assert(it != m_params.end());
|
||||
|
||||
m_params.erase(it);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* class Param
|
||||
*/
|
||||
Param::Param(Specification* pSpecification,
|
||||
const char* zName,
|
||||
const char* zDescription,
|
||||
Kind kind,
|
||||
mxs_module_param_type legacy_type)
|
||||
: m_specification(*pSpecification)
|
||||
, m_name(zName)
|
||||
, m_description(zDescription)
|
||||
, m_kind(kind)
|
||||
, m_legacy_type(legacy_type)
|
||||
{
|
||||
m_specification.insert(this);
|
||||
}
|
||||
|
||||
Param::~Param()
|
||||
{
|
||||
m_specification.remove(this);
|
||||
}
|
||||
|
||||
const string& Param::name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
const string& Param::description() const
|
||||
{
|
||||
return m_description;
|
||||
}
|
||||
|
||||
std::string Param::documentation() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
ss << m_name << " (" << type() << ", ";
|
||||
|
||||
if (is_mandatory())
|
||||
{
|
||||
ss << "mandatory";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "optional, default: " << default_to_string();
|
||||
}
|
||||
|
||||
ss << "): " << m_description;
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
Param::Kind Param::kind() const
|
||||
{
|
||||
return m_kind;
|
||||
}
|
||||
|
||||
bool Param::is_mandatory() const
|
||||
{
|
||||
return m_kind == MANDATORY;
|
||||
}
|
||||
|
||||
bool Param::is_optional() const
|
||||
{
|
||||
return m_kind == OPTIONAL;
|
||||
}
|
||||
|
||||
bool Param::has_default_value() const
|
||||
{
|
||||
return is_optional();
|
||||
}
|
||||
|
||||
void Param::populate(MXS_MODULE_PARAM& param) const
|
||||
{
|
||||
param.type = m_legacy_type;
|
||||
param.name = MXS_STRDUP_A(name().c_str());
|
||||
|
||||
if (has_default_value())
|
||||
{
|
||||
string s = default_to_string().c_str();
|
||||
|
||||
if ((s.length() >= 2) && (s.at(0) == '"') && (s.at(s.length() - 1) == '"'))
|
||||
{
|
||||
s = s.substr(1, s.length() - 2);
|
||||
}
|
||||
|
||||
param.default_value = MXS_STRDUP_A(s.c_str());
|
||||
}
|
||||
|
||||
if (is_mandatory())
|
||||
{
|
||||
param.options |= MXS_MODULE_OPT_REQUIRED;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* class Configuration
|
||||
*/
|
||||
Configuration::Configuration(const config::Specification* pSpecification)
|
||||
: m_specification(*pSpecification)
|
||||
{
|
||||
}
|
||||
|
||||
const config::Specification& Configuration::specification() const
|
||||
{
|
||||
return m_specification;
|
||||
}
|
||||
|
||||
Type* Configuration::find_value(const string& name)
|
||||
{
|
||||
auto it = m_values.find(name);
|
||||
|
||||
return it != m_values.end() ? it->second : nullptr;
|
||||
}
|
||||
|
||||
const Type* Configuration::find_value(const string& name) const
|
||||
{
|
||||
return const_cast<Configuration*>(this)->find_value(name);
|
||||
}
|
||||
|
||||
ostream& Configuration::persist(ostream& out) const
|
||||
{
|
||||
for (const auto& entry : m_values)
|
||||
{
|
||||
Type* pValue = entry.second;
|
||||
pValue->persist(out) << "\n";
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void Configuration::insert(Type* pValue)
|
||||
{
|
||||
mxb_assert(m_values.find(pValue->parameter().name()) == m_values.end());
|
||||
|
||||
m_values.insert(make_pair(pValue->parameter().name(), pValue));
|
||||
}
|
||||
|
||||
void Configuration::remove(Type* pValue)
|
||||
{
|
||||
auto it = m_values.find(pValue->parameter().name());
|
||||
|
||||
mxb_assert(it != m_values.end());
|
||||
m_values.erase(it);
|
||||
}
|
||||
|
||||
bool Configuration::configure()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t Configuration::size() const
|
||||
{
|
||||
return m_values.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* class Type
|
||||
*/
|
||||
Type::Type(Configuration* pConfiguration, const config::Param* pParam)
|
||||
: m_configuration(*pConfiguration)
|
||||
, m_param(*pParam)
|
||||
{
|
||||
m_configuration.insert(this);
|
||||
}
|
||||
|
||||
Type::~Type()
|
||||
{
|
||||
m_configuration.remove(this);
|
||||
}
|
||||
|
||||
const config::Param& Type::parameter() const
|
||||
{
|
||||
return m_param;
|
||||
}
|
||||
|
||||
ostream& Type::persist(ostream& out) const
|
||||
{
|
||||
out << m_param.name() << "=" << to_string();
|
||||
return out;
|
||||
}
|
||||
|
||||
bool Type::set(const string& value_as_string)
|
||||
{
|
||||
return m_param.set(*this, value_as_string);
|
||||
}
|
||||
|
||||
/**
|
||||
* ParamBool
|
||||
*/
|
||||
std::string ParamBool::type() const
|
||||
{
|
||||
return "boolean";
|
||||
}
|
||||
|
||||
std::string ParamBool::default_to_string() const
|
||||
{
|
||||
return to_string(m_default_value);
|
||||
}
|
||||
|
||||
bool ParamBool::validate(const std::string& value_as_string, std::string* pMessage) const
|
||||
{
|
||||
value_type value;
|
||||
return from_string(value_as_string, &value, pMessage);
|
||||
}
|
||||
|
||||
bool ParamBool::set(Type& value, const std::string& value_as_string) const
|
||||
{
|
||||
mxb_assert(&value.parameter() == this);
|
||||
|
||||
Bool& bool_value = static_cast<Bool&>(value);
|
||||
|
||||
value_type x;
|
||||
bool valid = from_string(value_as_string, &x);
|
||||
|
||||
if (valid)
|
||||
{
|
||||
bool_value.set(x);
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
bool ParamBool::from_string(const string& value_as_string, value_type* pValue, string* pMessage) const
|
||||
{
|
||||
int rv = config_truth_value(value_as_string.c_str());
|
||||
|
||||
if (rv == 1)
|
||||
{
|
||||
*pValue = true;
|
||||
}
|
||||
else if (rv == 0)
|
||||
{
|
||||
*pValue = false;
|
||||
}
|
||||
else if (pMessage)
|
||||
{
|
||||
mxb_assert(rv == -1);
|
||||
|
||||
*pMessage = "Invalid boolean: ";
|
||||
*pMessage += value_as_string;
|
||||
}
|
||||
|
||||
return rv != -1;
|
||||
}
|
||||
|
||||
string ParamBool::to_string(value_type value) const
|
||||
{
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
|
||||
/**
|
||||
* ParamCount
|
||||
*/
|
||||
std::string ParamCount::type() const
|
||||
{
|
||||
return "count";
|
||||
}
|
||||
|
||||
std::string ParamCount::default_to_string() const
|
||||
{
|
||||
return to_string(m_default_value);
|
||||
}
|
||||
|
||||
bool ParamCount::validate(const std::string& value_as_string, std::string* pMessage) const
|
||||
{
|
||||
value_type value;
|
||||
return from_string(value_as_string, &value, pMessage);
|
||||
}
|
||||
|
||||
bool ParamCount::set(Type& value, const std::string& value_as_string) const
|
||||
{
|
||||
mxb_assert(&value.parameter() == this);
|
||||
|
||||
Count& count_value = static_cast<Count&>(value);
|
||||
|
||||
value_type x;
|
||||
bool valid = from_string(value_as_string, &x);
|
||||
|
||||
if (valid)
|
||||
{
|
||||
count_value.set(x);
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
bool ParamCount::from_string(const std::string& value_as_string,
|
||||
value_type* pValue,
|
||||
std::string* pMessage) const
|
||||
{
|
||||
const char* zValue = value_as_string.c_str();
|
||||
char* zEnd;
|
||||
long l = strtol(zValue, &zEnd, 10);
|
||||
bool valid = (l >= 0 && zEnd != zValue && *zEnd == 0);
|
||||
|
||||
if (valid)
|
||||
{
|
||||
*pValue = l;
|
||||
}
|
||||
else if (pMessage)
|
||||
{
|
||||
*pMessage = "Invalid count: ";
|
||||
*pMessage += value_as_string;
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
std::string ParamCount::to_string(value_type value) const
|
||||
{
|
||||
return std::to_string(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* ParamPath
|
||||
*/
|
||||
std::string ParamPath::type() const
|
||||
{
|
||||
return "path";
|
||||
}
|
||||
|
||||
std::string ParamPath::default_to_string() const
|
||||
{
|
||||
return to_string(m_default_value);
|
||||
}
|
||||
|
||||
bool ParamPath::validate(const std::string& value_as_string, std::string* pMessage) const
|
||||
{
|
||||
value_type value;
|
||||
return from_string(value_as_string, &value, pMessage);
|
||||
}
|
||||
|
||||
bool ParamPath::set(Type& value, const std::string& value_as_string) const
|
||||
{
|
||||
mxb_assert(&value.parameter() == this);
|
||||
|
||||
Path& path_value = static_cast<Path&>(value);
|
||||
|
||||
value_type x;
|
||||
bool valid = from_string(value_as_string, &x);
|
||||
|
||||
if (valid)
|
||||
{
|
||||
path_value.set(x);
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
bool ParamPath::from_string(const std::string& value_as_string,
|
||||
value_type* pValue,
|
||||
std::string* pMessage) const
|
||||
{
|
||||
MXS_MODULE_PARAM param {};
|
||||
param.options = m_options;
|
||||
|
||||
bool valid = check_path_parameter(¶m, value_as_string.c_str());
|
||||
|
||||
if (valid)
|
||||
{
|
||||
*pValue = value_as_string;
|
||||
}
|
||||
else if (pMessage)
|
||||
{
|
||||
*pMessage = "Invalid path (does not exist, required permissions are not granted, ";
|
||||
*pMessage += "or cannot be created): ";
|
||||
*pMessage += value_as_string;
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
std::string ParamPath::to_string(const value_type& value) const
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
void ParamPath::populate(MXS_MODULE_PARAM& param) const
|
||||
{
|
||||
Param::populate(param);
|
||||
|
||||
param.options |= m_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* ParamSize
|
||||
*/
|
||||
std::string ParamSize::type() const
|
||||
{
|
||||
return "size";
|
||||
}
|
||||
|
||||
std::string ParamSize::default_to_string() const
|
||||
{
|
||||
return to_string(m_default_value);
|
||||
}
|
||||
|
||||
bool ParamSize::validate(const std::string& value_as_string, std::string* pMessage) const
|
||||
{
|
||||
value_type value;
|
||||
return from_string(value_as_string, &value, pMessage);
|
||||
}
|
||||
|
||||
bool ParamSize::set(Type& value, const std::string& value_as_string) const
|
||||
{
|
||||
mxb_assert(&value.parameter() == this);
|
||||
|
||||
Size& size_value = static_cast<Size&>(value);
|
||||
|
||||
value_type x;
|
||||
bool valid = from_string(value_as_string, &x);
|
||||
|
||||
if (valid)
|
||||
{
|
||||
size_value.set(x);
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
bool ParamSize::from_string(const std::string& value_as_string,
|
||||
value_type* pValue,
|
||||
std::string* pMessage) const
|
||||
{
|
||||
bool valid = get_suffixed_size(value_as_string.c_str(), pValue);
|
||||
|
||||
if (!valid && pMessage)
|
||||
{
|
||||
*pMessage = "Invalid size: ";
|
||||
*pMessage += value_as_string;
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
std::string ParamSize::to_string(value_type value) const
|
||||
{
|
||||
// TODO: Use largest possible unit.
|
||||
return std::to_string(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* ParamString
|
||||
*/
|
||||
std::string ParamString::type() const
|
||||
{
|
||||
return "string";
|
||||
}
|
||||
|
||||
std::string ParamString::default_to_string() const
|
||||
{
|
||||
return to_string(m_default_value);
|
||||
}
|
||||
|
||||
bool ParamString::validate(const std::string& value_as_string, std::string* pMessage) const
|
||||
{
|
||||
value_type value;
|
||||
return from_string(value_as_string, &value, pMessage);
|
||||
}
|
||||
|
||||
bool ParamString::set(Type& value, const std::string& value_as_string) const
|
||||
{
|
||||
mxb_assert(&value.parameter() == this);
|
||||
|
||||
String& string_value = static_cast<String&>(value);
|
||||
|
||||
value_type x;
|
||||
bool valid = from_string(value_as_string, &x);
|
||||
|
||||
if (valid)
|
||||
{
|
||||
string_value.set(x);
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
bool ParamString::from_string(const std::string& value_as_string,
|
||||
value_type* pValue,
|
||||
std::string* pMessage) const
|
||||
{
|
||||
bool valid = true;
|
||||
|
||||
char b = value_as_string.empty() ? 0 : value_as_string.front();
|
||||
char e = value_as_string.empty() ? 0 : value_as_string.back();
|
||||
|
||||
if (b != '"' && b != '\'')
|
||||
{
|
||||
if (pMessage)
|
||||
{
|
||||
*pMessage = "A string value should be enclosed in quotes: ";
|
||||
*pMessage += value_as_string;
|
||||
}
|
||||
}
|
||||
|
||||
string s = value_as_string;
|
||||
|
||||
if (b == '"' || b == '\'')
|
||||
{
|
||||
valid = (b == e);
|
||||
|
||||
if (valid)
|
||||
{
|
||||
s = s.substr(1, s.length() - 2);
|
||||
}
|
||||
else if (pMessage)
|
||||
{
|
||||
*pMessage = "A quoted string must end with the same quote: ";
|
||||
*pMessage += value_as_string;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid)
|
||||
{
|
||||
*pValue = s;
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
std::string ParamString::to_string(value_type value) const
|
||||
{
|
||||
stringstream ss;
|
||||
ss << "\"" << value << "\"";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
}
|
@ -214,25 +214,6 @@ bool is_normal_server_parameter(const char* param);
|
||||
*/
|
||||
bool get_suffixed_size(const char* value, uint64_t* dest);
|
||||
|
||||
/**
|
||||
* Converts a string into a duration, intepreting in a case-insensitive manner
|
||||
* an 'h'-suffix to indicate hours, an 'm'-suffix to indicate minutes, an
|
||||
* 's'-suffix to indicate seconds and an 'ms'-suffix to indicate milliseconds.
|
||||
*
|
||||
* @param zValue A numerical string, possibly suffixed by 'h', 'm',
|
||||
* 's' or 'ms'.
|
||||
* @param interpretation How a value lacking a specific suffix should be interpreted.
|
||||
* @param pDuration Pointer, if non-NULL, where the result is stored.
|
||||
* @param pUnit Pointer, if non-NULL, where the detected unit is stored.
|
||||
*
|
||||
* @return True on success, false on invalid input in which case @c pUnit and
|
||||
* @c pDuration will not be modified.
|
||||
*/
|
||||
bool get_suffixed_duration(const char* zValue,
|
||||
mxs::config::DurationInterpretation interpretation,
|
||||
std::chrono::milliseconds* pDuration,
|
||||
mxs::config::DurationUnit* pUnit = nullptr);
|
||||
|
||||
/**
|
||||
* Generate configuration file contents out of module configuration parameters. Only parameters defined
|
||||
* in the parameter definition arrays are printed. Printing is in the order the parameters are given in
|
||||
|
@ -3,6 +3,7 @@ add_executable(test_adminusers test_adminusers.cc)
|
||||
add_executable(test_atomic test_atomic.cc)
|
||||
add_executable(test_buffer test_buffer.cc)
|
||||
add_executable(test_config test_config.cc)
|
||||
add_executable(test_config2 test_config2.cc)
|
||||
add_executable(test_dcb test_dcb.cc)
|
||||
add_executable(test_event test_event.cc)
|
||||
add_executable(test_filter test_filter.cc)
|
||||
@ -31,6 +32,7 @@ target_link_libraries(test_adminusers maxscale-common)
|
||||
target_link_libraries(test_atomic maxscale-common)
|
||||
target_link_libraries(test_buffer maxscale-common)
|
||||
target_link_libraries(test_config maxscale-common)
|
||||
target_link_libraries(test_config2 maxscale-common)
|
||||
target_link_libraries(test_dcb maxscale-common)
|
||||
target_link_libraries(test_event maxscale-common)
|
||||
target_link_libraries(test_filter maxscale-common)
|
||||
@ -58,6 +60,7 @@ add_test(test_adminusers test_adminusers)
|
||||
add_test(test_atomic test_atomic)
|
||||
add_test(test_buffer test_buffer)
|
||||
add_test(test_config test_config)
|
||||
add_test(test_config2 test_config2)
|
||||
add_test(test_dcb test_dcb)
|
||||
add_test(test_event test_event)
|
||||
add_test(test_filter test_filter)
|
||||
|
318
server/core/test/test_config2.cc
Normal file
318
server/core/test/test_config2.cc
Normal file
@ -0,0 +1,318 @@
|
||||
/*
|
||||
* Copyright (c) 2016 MariaDB Corporation Ab
|
||||
*
|
||||
* Use of this software is governed by the Business Source License included
|
||||
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
|
||||
*
|
||||
* Change Date: 2022-01-01
|
||||
*
|
||||
* On the date above, in accordance with the Business Source License, use
|
||||
* of this software will be governed by version 2 or later of the General
|
||||
* Public License.
|
||||
*/
|
||||
|
||||
// To ensure that ss_info_assert asserts also when builing in non-debug mode.
|
||||
#ifndef SS_DEBUG
|
||||
#define SS_DEBUG
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
#include <maxbase/log.hh>
|
||||
#include <maxscale/config2.hh>
|
||||
|
||||
using namespace std;
|
||||
|
||||
inline ostream& operator << (ostream& out, const std::chrono::seconds& x)
|
||||
{
|
||||
out << x.count();
|
||||
return out;
|
||||
}
|
||||
|
||||
inline ostream& operator << (ostream& out, const std::chrono::milliseconds& x)
|
||||
{
|
||||
out << x.count();
|
||||
return out;
|
||||
}
|
||||
|
||||
config::Specification specification("test_module");
|
||||
|
||||
config::ParamBool
|
||||
param_bool(&specification,
|
||||
"boolean_parameter",
|
||||
"Specifies whether something is enabled.");
|
||||
|
||||
config::ParamCount
|
||||
param_count(&specification,
|
||||
"count_parameter",
|
||||
"Specifies the cardinality of something.");
|
||||
|
||||
config::ParamDuration<std::chrono::seconds>
|
||||
param_duration_1(&specification,
|
||||
"duration_parameter_1",
|
||||
"Specifies the duration of something.",
|
||||
mxs::config::INTERPRET_AS_SECONDS);
|
||||
|
||||
config::ParamDuration<std::chrono::milliseconds>
|
||||
param_duration_2(&specification,
|
||||
"duration_parameter_2",
|
||||
"Specifies the duration of something.",
|
||||
mxs::config::INTERPRET_AS_MILLISECONDS);
|
||||
|
||||
enum Enum
|
||||
{
|
||||
ENUM_ONE,
|
||||
ENUM_TWO
|
||||
};
|
||||
|
||||
config::ParamEnum<Enum>
|
||||
param_enum(&specification,
|
||||
"enum_parameter",
|
||||
"Specifies a range of values.",
|
||||
{
|
||||
{ ENUM_ONE, "one" },
|
||||
{ ENUM_TWO, "two" }
|
||||
});
|
||||
|
||||
config::ParamPath
|
||||
param_path(&specification,
|
||||
"path_parameter",
|
||||
"Specifies the path of something.",
|
||||
config::ParamPath::F);
|
||||
|
||||
config::ParamSize
|
||||
param_size(&specification,
|
||||
"size_parameter",
|
||||
"Specifies the size of something.");
|
||||
|
||||
config::ParamString
|
||||
param_string(&specification,
|
||||
"string_parameter",
|
||||
"Specifies the name of something.");
|
||||
|
||||
template<class T>
|
||||
struct TestEntry
|
||||
{
|
||||
const char* zText;
|
||||
bool valid;
|
||||
T value;
|
||||
};
|
||||
|
||||
#define elements_in_array(x) (sizeof(x)/sizeof(x[0]))
|
||||
|
||||
template<class T>
|
||||
int test(T& value, const TestEntry<typename T::value_type>* pEntries, int nEntries)
|
||||
{
|
||||
const config::Param& param = value.parameter();
|
||||
|
||||
cout << "Testing " << param.type() << " parameter " << param.name() << "." << endl;
|
||||
|
||||
int nErrors = 0;
|
||||
|
||||
for (int i = 0; i < nEntries; ++i)
|
||||
{
|
||||
const auto& entry = pEntries[i];
|
||||
|
||||
std::string message;
|
||||
bool validated = param.validate(entry.zText, &message);
|
||||
|
||||
if (entry.valid && validated)
|
||||
{
|
||||
param.set(value, entry.zText);
|
||||
|
||||
if (value != entry.value)
|
||||
{
|
||||
cout << value.to_string() << " != " << entry.value << endl;
|
||||
++nErrors;
|
||||
}
|
||||
}
|
||||
else if (entry.valid && !validated)
|
||||
{
|
||||
cout << "Expected \"" << entry.zText << "\" to BE valid for " << param.type()
|
||||
<< " parameter " << param.name() << ", but it was NOT validated: " << message << endl;
|
||||
++nErrors;
|
||||
}
|
||||
else if (!entry.valid && validated)
|
||||
{
|
||||
cout << "Expected \"" << entry.zText << "\" NOT to be valid for " << param.type()
|
||||
<< " parameter " << param.name() << ", but it WAS validated." << endl;
|
||||
++nErrors;
|
||||
}
|
||||
}
|
||||
|
||||
return nErrors;
|
||||
}
|
||||
|
||||
int test_bool(config::Bool& value)
|
||||
{
|
||||
static const TestEntry<config::Bool::value_type> entries[] =
|
||||
{
|
||||
{ "1", true, true },
|
||||
{ "0", true, false },
|
||||
{ "true", true, true },
|
||||
{ "false", true, false },
|
||||
{ "on", true, true },
|
||||
{ "off", true, false },
|
||||
|
||||
{ "2", false },
|
||||
{ "truth", false },
|
||||
{ "%&", false },
|
||||
{ "-1", false },
|
||||
};
|
||||
|
||||
return test(value, entries, elements_in_array(entries));
|
||||
}
|
||||
|
||||
int test_count(config::Count& value)
|
||||
{
|
||||
static const TestEntry<config::Count::value_type> entries[] =
|
||||
{
|
||||
{ "1", true, 1 },
|
||||
{ "9999", true, 9999 },
|
||||
{ "0", true, 0 },
|
||||
|
||||
{ "0x45", false },
|
||||
{ "blah", false },
|
||||
{ "-1", false },
|
||||
};
|
||||
|
||||
return test(value, entries, elements_in_array(entries));
|
||||
}
|
||||
|
||||
int test_duration(config::Duration<std::chrono::seconds>& value)
|
||||
{
|
||||
static const TestEntry<config::Duration<std::chrono::seconds>::value_type> entries[] =
|
||||
{
|
||||
{ "1", true, std::chrono::seconds { 1 } },
|
||||
{ "1ms", true, std::chrono::seconds { 0 } },
|
||||
{ "1s", true, std::chrono::seconds { 1 } },
|
||||
{ "1m", true, std::chrono::seconds { 60 } },
|
||||
{ "1h", true, std::chrono::seconds { 3600 } },
|
||||
|
||||
{ "1x", false },
|
||||
{ "a", false },
|
||||
{ "-", false },
|
||||
{ "second", false }
|
||||
};
|
||||
|
||||
return test(value, entries, elements_in_array(entries));
|
||||
}
|
||||
|
||||
int test_duration(config::Duration<std::chrono::milliseconds>& value)
|
||||
{
|
||||
static const TestEntry<config::Duration<std::chrono::milliseconds>::value_type> entries[] =
|
||||
{
|
||||
{ "1", true, std::chrono::milliseconds { 1 } },
|
||||
{ "1ms", true, std::chrono::milliseconds { 1 } },
|
||||
{ "1s", true, std::chrono::milliseconds { 1000 } },
|
||||
{ "1m", true, std::chrono::milliseconds { 60000 } },
|
||||
{ "1h", true, std::chrono::milliseconds { 3600000 } },
|
||||
|
||||
{ "1x", false },
|
||||
{ "a", false },
|
||||
{ "-", false },
|
||||
{ "second", false }
|
||||
};
|
||||
|
||||
return test(value, entries, elements_in_array(entries));
|
||||
}
|
||||
|
||||
int test_enum(config::Enum<Enum>& value)
|
||||
{
|
||||
static const TestEntry<Enum> entries[] =
|
||||
{
|
||||
{ "one", true, ENUM_ONE },
|
||||
{ "two", true, ENUM_TWO },
|
||||
|
||||
{ "blah", false },
|
||||
{ "1", false },
|
||||
{ "ones", false }
|
||||
};
|
||||
|
||||
return test(value, entries, elements_in_array(entries));
|
||||
}
|
||||
|
||||
int test_path(config::Path& value)
|
||||
{
|
||||
static const TestEntry<config::Path::value_type> entries[] =
|
||||
{
|
||||
{ ".", true, "." },
|
||||
{ "/tmp", true, "/tmp" },
|
||||
|
||||
{ "non-existent", false }
|
||||
};
|
||||
|
||||
return test(value, entries, elements_in_array(entries));
|
||||
}
|
||||
|
||||
int test_size(config::Size& value)
|
||||
{
|
||||
static const TestEntry<config::Size::value_type> entries[] =
|
||||
{
|
||||
{ "0", true, 0 },
|
||||
{ "100", true, 100 },
|
||||
|
||||
{ "-100", false },
|
||||
{ "0x100", false },
|
||||
};
|
||||
|
||||
return test(value, entries, elements_in_array(entries));
|
||||
}
|
||||
|
||||
int test_string(config::String& value)
|
||||
{
|
||||
static const TestEntry<config::String::value_type> entries[] =
|
||||
{
|
||||
{ "blah", true, "blah" },
|
||||
{ "\"blah\"", true, "blah" },
|
||||
{ "'blah'", true, "blah" },
|
||||
{ "123", true, "123" },
|
||||
{ "`blah`", true, "`blah`" },
|
||||
|
||||
{ "'blah\"", false }
|
||||
};
|
||||
|
||||
return test(value, entries, elements_in_array(entries));
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
mxb::Log log;
|
||||
|
||||
for_each(specification.cbegin(), specification.cend(),[](const config::Specification::value_type& p) {
|
||||
cout << p.second->documentation() << endl;
|
||||
});
|
||||
|
||||
cout << endl;
|
||||
|
||||
specification.document(cout);
|
||||
|
||||
config::Configuration configuration(&specification);
|
||||
|
||||
int nErrors = 0;
|
||||
|
||||
config::Bool value_bool(&configuration, ¶m_bool);
|
||||
nErrors += test_bool(value_bool);
|
||||
|
||||
config::Count value_count(&configuration, ¶m_count);
|
||||
nErrors += test_count(value_count);
|
||||
|
||||
config::Duration<std::chrono::seconds> value_duration_1(&configuration, ¶m_duration_1);
|
||||
nErrors += test_duration(value_duration_1);
|
||||
|
||||
config::Duration<std::chrono::milliseconds> value_duration_2(&configuration, ¶m_duration_2);
|
||||
nErrors += test_duration(value_duration_2);
|
||||
|
||||
config::Enum<Enum> value_enum(&configuration, ¶m_enum);
|
||||
nErrors += test_enum(value_enum);
|
||||
|
||||
config::Path value_path(&configuration, ¶m_path);
|
||||
nErrors += test_path(value_path);
|
||||
|
||||
config::Size value_size(&configuration, ¶m_size);
|
||||
nErrors += test_size(value_size);
|
||||
|
||||
config::String value_string(&configuration, ¶m_string);
|
||||
nErrors += test_string(value_string);
|
||||
|
||||
return nErrors ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user