From c116452c25c9288c85b2ca9d9a57b036ed65fa28 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 12 Feb 2019 11:59:22 +0200 Subject: [PATCH] MXS-2253 Add MXS_MODULE_PARAM_DURATION Added a new module parameter type to be used for parameters that specify a duration. With the suffixes 'h', 'm', 's' and 'ms' the duration can be specified in hours, minutes, seconds or milliseconds, respectively. Irrespective of how the duration is specified, it is always returned as milliseconds. For backward compatibility, when a duration value is read it must be specifed how a value *not* defined using a suffix should be interpreted; as seconds or milliseconds. value = param->get_duration(name, mxs::config::INTERPRET_AS_SECONDS); --- include/maxscale/config.hh | 38 ++++++++++ include/maxscale/modinfo.h | 3 +- server/core/config.cc | 123 ++++++++++++++++++++++++++++++++ server/core/internal/config.hh | 19 +++++ server/core/test/test_config.cc | 29 +++++--- 5 files changed, 203 insertions(+), 9 deletions(-) diff --git a/include/maxscale/config.hh b/include/maxscale/config.hh index e657d09c4..55545bbf4 100644 --- a/include/maxscale/config.hh +++ b/include/maxscale/config.hh @@ -215,6 +215,31 @@ extern const char CN_MAXLOG[]; extern const char CN_LOG_AUGMENTATION[]; extern const char CN_LOG_TO_SHM[]; +namespace maxscale +{ + +namespace config +{ + +enum DurationInterpretation +{ + INTERPRET_AS_SECONDS, + INTERPRET_AS_MILLISECONDS +}; + +enum DurationUnit +{ + DURATION_IN_HOURS, + DURATION_IN_MINUTES, + DURATION_IN_SECONDS, + DURATION_IN_MILLISECONDS, + DURATION_IN_DEFAULT +}; + +} + +} + /** * Config parameter container. Typically includes all parameters of a single configuration file section * such as a server or filter. @@ -294,6 +319,19 @@ public: */ uint64_t get_size(const std::string& key) const; + /** + * @brief Get a duration. + * + * Should be used for MXS_MODULE_PARAM_DURATION parameter types. + * + * @param key Parameter name. + * @param interpretation How a value NOT having a unit suffix should be interpreted. + * + * @return Duration in milliseconds; 0 if the parameter is not found. + */ + std::chrono::milliseconds get_duration(const std::string& key, + mxs::config::DurationInterpretation interpretation) const; + /** * @brief Get a service value * diff --git a/include/maxscale/modinfo.h b/include/maxscale/modinfo.h index a8bd30aa4..4dafa1a68 100644 --- a/include/maxscale/modinfo.h +++ b/include/maxscale/modinfo.h @@ -85,7 +85,8 @@ enum mxs_module_param_type MXS_MODULE_PARAM_SERVICE, /**< Service name */ MXS_MODULE_PARAM_SERVER, /**< Server name */ MXS_MODULE_PARAM_SERVERLIST, /**< List of server names, separated by ',' */ - MXS_MODULE_PARAM_REGEX /**< A regex string enclosed in '/' */ + MXS_MODULE_PARAM_REGEX, /**< A regex string enclosed in '/' */ + MXS_MODULE_PARAM_DURATION, /**< Duration in milliseconds */ }; /** Maximum and minimum values for integer types */ diff --git a/server/core/config.cc b/server/core/config.cc index 596cb5093..726c4537a 100644 --- a/server/core/config.cc +++ b/server/core/config.cc @@ -227,6 +227,8 @@ static pcre2_code* compile_regex_string(const char* regex_string, bool jit_enabled, uint32_t options, uint32_t* output_ovector_size); +static bool duration_is_valid(const char* zValue, mxs::config::DurationUnit* pUnit); + int config_get_ifaddr(unsigned char* output); static int config_get_release_string(char* release); @@ -1750,6 +1752,17 @@ uint64_t MXS_CONFIG_PARAMETER::get_size(const std::string& key) const return intval; } +std::chrono::milliseconds +MXS_CONFIG_PARAMETER::get_duration(const std::string& key, + mxs::config::DurationInterpretation interpretation) const +{ + string value = get_string(key); + std::chrono::milliseconds duration { 0 }; + MXB_AT_DEBUG(bool rval = ) get_suffixed_duration(value.c_str(), interpretation, &duration); + mxb_assert(rval); // When this function is called, the validity of the value should have been checked. + return duration; +} + int64_t MXS_CONFIG_PARAMETER::get_enum(const std::string& key, const MXS_ENUM_VALUE* enum_mapping) const { string param_value = get_string(key); @@ -4322,6 +4335,26 @@ bool config_param_is_valid(const MXS_MODULE_PARAM* params, } break; + case MXS_MODULE_PARAM_DURATION: + { + mxs::config::DurationUnit unit; + + if (duration_is_valid(value, &unit)) + { + valid = true; + + if (unit == mxs::config::DURATION_IN_DEFAULT) + { + MXS_WARNING("Specifying durations without a suffix denoting the unit " + "has been deprecated: '%s=%s'. Use the suffixes 'h' (hour), " + "'m' (minute) 's' (second) or 'ms' (milliseconds). " + "For instance, '%s=%ss' or '%s=%sms.", + key, value, key, value, key, value); + } + } + } + break; + case MXS_MODULE_PARAM_BOOL: if (config_truth_value(value) != -1) { @@ -4818,6 +4851,96 @@ bool get_suffixed_size(const char* value, uint64_t* dest) return rval; } +bool get_suffixed_duration(const char* zValue, + mxs::config::DurationInterpretation interpretation, + std::chrono::milliseconds* pDuration, + mxs::config::DurationUnit* pUnit) +{ + if (!isdigit(*zValue)) + { + // This will also catch negative values + return false; + } + + bool rval = false; + char* zEnd; + uint64_t value = strtoll(zValue, &zEnd, 10); + + std::chrono::milliseconds duration; + mxs::config::DurationUnit unit = mxs::config::DURATION_IN_DEFAULT; + + switch (*zEnd) + { + case 'H': + case 'h': + unit = mxs::config::DURATION_IN_HOURS; + duration = std::chrono::duration_cast(std::chrono::hours(value)); + ++zEnd; + break; + + case 'M': + case 'm': + if (*(zEnd + 1) == 's' || *(zEnd + 1) == 'S') + { + unit = mxs::config::DURATION_IN_MILLISECONDS; + duration = std::chrono::milliseconds(value); + ++zEnd; + } + else + { + unit = mxs::config::DURATION_IN_MINUTES; + duration = std::chrono::duration_cast(std::chrono::minutes(value)); + } + ++zEnd; + break; + + case 'S': + case 's': + unit = mxs::config::DURATION_IN_SECONDS; + duration = std::chrono::duration_cast(std::chrono::seconds(value)); + ++zEnd; + break; + + case 0: + if (interpretation == mxs::config::INTERPRET_AS_SECONDS) + { + duration = std::chrono::duration_cast(std::chrono::seconds(value)); + } + else + { + duration = std::chrono::milliseconds(value); + } + break; + + default: + break; + }; + + if (*zEnd == 0) + { + rval = true; + + if (pDuration) + { + *pDuration = duration; + } + + if (pUnit) + { + *pUnit = unit; + } + } + + return rval; +} + +static bool duration_is_valid(const char* zValue, mxs::config::DurationUnit* pUnit) +{ + // When the validity is checked, it does not matter how the value + // should be interpreted, so any mxs::config::DurationInterpretation is fine. + return get_suffixed_duration(zValue, mxs::config::INTERPRET_AS_SECONDS, nullptr, pUnit); +} + bool config_parse_disk_space_threshold(SERVER::DiskSpaceLimits* pDisk_space_threshold, const char* zDisk_space_threshold) { diff --git a/server/core/internal/config.hh b/server/core/internal/config.hh index f744492c9..dfb2eff80 100644 --- a/server/core/internal/config.hh +++ b/server/core/internal/config.hh @@ -219,6 +219,25 @@ 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); + // Dump a parameter list into a file as `key=value` pairs void dump_param_list(int file, MXS_CONFIG_PARAMETER* list, diff --git a/server/core/test/test_config.cc b/server/core/test/test_config.cc index 6ea25b904..81a6d00c7 100644 --- a/server/core/test/test_config.cc +++ b/server/core/test/test_config.cc @@ -35,14 +35,15 @@ int test_validity() MXS_MODULE_PARAM params[] = { - {"p1", MXS_MODULE_PARAM_INT, "-123" }, - {"p2", MXS_MODULE_PARAM_COUNT, "123" }, - {"p3", MXS_MODULE_PARAM_BOOL, "true" }, - {"p4", MXS_MODULE_PARAM_STRING, "default" }, - {"p5", MXS_MODULE_PARAM_ENUM, "a", MXS_MODULE_OPT_NONE, enum_values}, - {"p6", MXS_MODULE_PARAM_PATH, "/tmp", MXS_MODULE_OPT_PATH_F_OK}, - {"p7", MXS_MODULE_PARAM_SERVICE, "my-service" }, - {"p8", MXS_MODULE_PARAM_ENUM, "a", MXS_MODULE_OPT_ENUM_UNIQUE, enum_values}, + {"p1", MXS_MODULE_PARAM_INT, "-123" }, + {"p2", MXS_MODULE_PARAM_COUNT, "123" }, + {"p3", MXS_MODULE_PARAM_BOOL, "true" }, + {"p4", MXS_MODULE_PARAM_STRING, "default" }, + {"p5", MXS_MODULE_PARAM_ENUM, "a", MXS_MODULE_OPT_NONE, enum_values}, + {"p6", MXS_MODULE_PARAM_PATH, "/tmp", MXS_MODULE_OPT_PATH_F_OK}, + {"p7", MXS_MODULE_PARAM_SERVICE, "my-service" }, + {"p8", MXS_MODULE_PARAM_ENUM, "a", MXS_MODULE_OPT_ENUM_UNIQUE, enum_values}, + {"p9", MXS_MODULE_PARAM_DURATION, "4711s" }, {MXS_END_MODULE_PARAMS} }; @@ -96,6 +97,18 @@ int test_validity() TEST(config_param_is_valid(params, "p6", "/tmp", &ctx)); TEST(!config_param_is_valid(params, "p6", "This is not a valid path", &ctx)); + /** Duration parameter */ + TEST(config_param_is_valid(params, "p9", "4711", &ctx)); + TEST(config_param_is_valid(params, "p9", "4711h", &ctx)); + TEST(config_param_is_valid(params, "p9", "4711m", &ctx)); + TEST(config_param_is_valid(params, "p9", "4711s", &ctx)); + TEST(config_param_is_valid(params, "p9", "4711ms", &ctx)); + TEST(config_param_is_valid(params, "p9", "4711H", &ctx)); + TEST(config_param_is_valid(params, "p9", "4711M", &ctx)); + TEST(config_param_is_valid(params, "p9", "4711S", &ctx)); + TEST(config_param_is_valid(params, "p9", "4711MS", &ctx)); + TEST(!config_param_is_valid(params, "p9", "4711q", &ctx)); + /** Service parameter */ CONFIG_CONTEXT svc = {}; svc.object = (char*)"test-service";