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:
@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user