diff --git a/Documentation/Filters/Cache.md b/Documentation/Filters/Cache.md index a55765567..c983165e1 100644 --- a/Documentation/Filters/Cache.md +++ b/Documentation/Filters/Cache.md @@ -290,8 +290,29 @@ where, * the _op_ can be `=`, `!=`, `like` or `unlike`, and * the _value_ a string. -If _op_ is `=` or `!=` then _value_ is used verbatim; if it is `like` -or `unlike`, then _value_ is interpreted as a _pcre2_ regular expression. +If _op_ is `=` or `!=` then _value_ is interpreted as a MariaDB account +string, that is, `%` means indicates wildcard, but if _op_ is `like` or +`unlike` it is simply assumed _value_ is a pcre2 regular expression. + +For instance, the following are equivalent: + +``` +{ + "attribute": "user", + "op": "=", + "value": "'bob'@'%'" +} + +{ + "attribute": "user", + "op": "like", + "value": "bob@.*" +} + +Note that if _op_ is `=` or `!=` then the usual assumptions apply, +that is, a value of `bob` is equivalent with `'bob'@'%'`. If _like_ +or _unlike_ is used, then no assumptions apply, but the string is +used verbatim as a regular expression. The objects in the `use` array are processed in order. If the result of a comparison is _true_, no further processing will be made and the @@ -307,7 +328,8 @@ rule in the `store` array. ### Examples -Use data from the cache for all users except `admin`. +Use data from the cache for all users except `admin` (actually `'admin'@'%'`), +regardless of what host the `admin` user comes from. ``` { "use": [ @@ -319,6 +341,7 @@ Use data from the cache for all users except `admin`. ] } ``` + # Storage ## Storage RocksDB diff --git a/server/include/hk_heartbeat.h b/server/include/hk_heartbeat.h index 9a3ffb8dd..ab9172074 100644 --- a/server/include/hk_heartbeat.h +++ b/server/include/hk_heartbeat.h @@ -1,6 +1,19 @@ #ifndef _HK_HEARTBEAT_H #define _HK_HEARTBEAT_H +/* + * 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/bsl. + * + * Change Date: 2019-07-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. + */ + /** * The global housekeeper heartbeat value. This value is incremented * every 100 milliseconds and may be used for crude timing etc. diff --git a/server/include/test_utils.h b/server/include/test_utils.h index 894d96e58..7800ce1b3 100644 --- a/server/include/test_utils.h +++ b/server/include/test_utils.h @@ -1,5 +1,19 @@ #ifndef TEST_UTILS_H #define TEST_UTILS_H + +/* + * 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/bsl. + * + * Change Date: 2019-07-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 #include #include diff --git a/server/modules/filter/cache/CMakeLists.txt b/server/modules/filter/cache/CMakeLists.txt index 8f9b71333..89db9c15b 100644 --- a/server/modules/filter/cache/CMakeLists.txt +++ b/server/modules/filter/cache/CMakeLists.txt @@ -5,3 +5,7 @@ set_target_properties(cache PROPERTIES LINK_FLAGS -Wl,-z,defs) install_module(cache experimental) add_subdirectory(storage) + +if(BUILD_TESTS) + add_subdirectory(test) +endif() diff --git a/server/modules/filter/cache/rules.c b/server/modules/filter/cache/rules.c index f09a157b0..27f769f54 100644 --- a/server/modules/filter/cache/rules.c +++ b/server/modules/filter/cache/rules.c @@ -63,10 +63,8 @@ static struct cache_attribute_mapping cache_use_attributes[] = static bool cache_rule_attribute_get(struct cache_attribute_mapping *mapping, const char *s, cache_rule_attribute_t *attribute); -static const char *cache_rule_attribute_to_string(cache_rule_attribute_t attribute); static bool cache_rule_op_get(const char *s, cache_rule_op_t *op); -static const char *cache_rule_op_to_string(cache_rule_op_t op); static bool cache_rule_compare(CACHE_RULE *rule, const char *value); static bool cache_rule_compare_n(CACHE_RULE *rule, const char *value, size_t length); @@ -104,6 +102,7 @@ static bool cache_rule_matches(CACHE_RULE *rule, const char *default_db, const G static void cache_rules_add_store_rule(CACHE_RULES* self, CACHE_RULE* rule); static void cache_rules_add_use_rule(CACHE_RULES* self, CACHE_RULE* rule); +static CACHE_RULES* cache_rules_create_from_json(json_t* root, uint32_t debug); static bool cache_rules_parse_json(CACHE_RULES* self, json_t* root); typedef bool (*cache_rules_parse_element_t)(CACHE_RULES *self, json_t *object, size_t index); @@ -113,10 +112,86 @@ static bool cache_rules_parse_array(CACHE_RULES *self, json_t *store, const char static bool cache_rules_parse_store_element(CACHE_RULES *self, json_t *object, size_t index); static bool cache_rules_parse_use_element(CACHE_RULES *self, json_t *object, size_t index); +static bool dequote_mysql(char *s); + +typedef enum pcre_quote_approach +{ + PCRE_QUOTE_VERBATIM, + PCRE_QUOTE_QUERY +} pcre_quote_approach_t; + +typedef enum mysql_account_kind +{ + MYSQL_ACCOUNT_WITH_WILDCARD, + MYSQL_ACCOUNT_WITHOUT_WILDCARD +} mysql_account_kind_t; + +static mysql_account_kind_t mysql_to_pcre(char *pcre, const char *mysql, pcre_quote_approach_t approach); + /* * API begin */ +/** + * Returns a string representation of a attribute. + * + * @param attribute An attribute type. + * + * @return Corresponding string, not to be freed. + */ +const char *cache_rule_attribute_to_string(cache_rule_attribute_t attribute) +{ + switch (attribute) + { + case CACHE_ATTRIBUTE_COLUMN: + return "column"; + + case CACHE_ATTRIBUTE_DATABASE: + return "database"; + + case CACHE_ATTRIBUTE_QUERY: + return "query"; + + case CACHE_ATTRIBUTE_TABLE: + return "table"; + + case CACHE_ATTRIBUTE_USER: + return "user"; + + default: + ss_dassert(!true); + return ""; + } +} + +/** + * Returns a string representation of an operator. + * + * @param op An operator. + * + * @return Corresponding string, not to be freed. + */ +const char *cache_rule_op_to_string(cache_rule_op_t op) +{ + switch (op) + { + case CACHE_OP_EQ: + return "="; + + case CACHE_OP_NEQ: + return "!="; + + case CACHE_OP_LIKE: + return "like"; + + case CACHE_OP_UNLIKE: + return "unlike"; + + default: + ss_dassert(!true); + return ""; + } +} /** * Create a default cache rules object. @@ -158,17 +233,7 @@ CACHE_RULES *cache_rules_load(const char *path, uint32_t debug) if (root) { - rules = cache_rules_create(debug); - - if (rules) - { - if (!cache_rules_parse_json(rules, root)) - { - cache_rules_free(rules); - rules = NULL; - } - } - + rules = cache_rules_create_from_json(root, debug); json_decref(root); } else @@ -190,6 +255,35 @@ CACHE_RULES *cache_rules_load(const char *path, uint32_t debug) return rules; } +/** + * Parses the caching rules from a string and returns corresponding object. + * + * @param json String containing json. + * @param debug The debug level. + * + * @return The corresponding rules object, or NULL in case of error. + */ +CACHE_RULES *cache_rules_parse(const char *json, uint32_t debug) +{ + CACHE_RULES *rules = NULL; + + json_error_t error; + json_t *root = json_loads(json, JSON_DISABLE_EOF_CHECK, &error); + + if (root) + { + rules = cache_rules_create_from_json(root, debug); + json_decref(root); + } + else + { + MXS_ERROR("Parsing rules failed: (%d:%d): %s", + error.line, error.column, error.text); + } + + return rules; +} + /** * Frees the rules object. * @@ -252,12 +346,27 @@ bool cache_rules_should_use(CACHE_RULES *self, const SESSION *session) CACHE_RULE *rule = self->use_rules; const char *user = session_getUser((SESSION*)session); + const char *host = session_get_remote((SESSION*)session); - if (rule && user) + if (!user) { + user = ""; + } + + if (!host) + { + host = ""; + } + + if (rule) + { + char account[strlen(user) + 1 + strlen(host) + 1]; + sprintf(account, "%s@%s", user, host); + while (rule && !should_use) { - should_use = cache_rule_matches_user(rule, user); + should_use = cache_rule_matches_user(rule, account); + rule = rule->next; } } @@ -301,35 +410,6 @@ static bool cache_rule_attribute_get(struct cache_attribute_mapping *mapping, return false; } -/** - * Returns a string representation of a attribute. - * - * @param attribute An attribute type. - * - * @return Corresponding string, not to be freed. - */ -static const char *cache_rule_attribute_to_string(cache_rule_attribute_t attribute) -{ - switch (attribute) - { - case CACHE_ATTRIBUTE_COLUMN: - return "column"; - - case CACHE_ATTRIBUTE_DATABASE: - return "database"; - - case CACHE_ATTRIBUTE_QUERY: - return "query"; - - case CACHE_ATTRIBUTE_TABLE: - return "table"; - - default: - ss_dassert(!true); - return ""; - } -} - /** * Converts a string to an operator * @@ -367,35 +447,6 @@ static bool cache_rule_op_get(const char *s, cache_rule_op_t *op) return false; } -/** - * Returns a string representation of an operator. - * - * @param op An operator. - * - * @return Corresponding string, not to be freed. - */ -static const char *cache_rule_op_to_string(cache_rule_op_t op) -{ - switch (op) - { - case CACHE_OP_EQ: - return "="; - - case CACHE_OP_NEQ: - return "!="; - - case CACHE_OP_LIKE: - return "like"; - - case CACHE_OP_UNLIKE: - return "unlike"; - - default: - ss_dassert(!true); - return ""; - } -} - /** * Creates a CACHE_RULE object doing regexp matching. * @@ -464,6 +515,99 @@ static CACHE_RULE *cache_rule_create_regexp(cache_rule_attribute_t attribute, return rule; } +static CACHE_RULE *cache_rule_create_user(cache_rule_attribute_t attribute, + cache_rule_op_t op, + const char *cvalue, + uint32_t debug) +{ + ss_dassert((op == CACHE_OP_EQ) || (op == CACHE_OP_NEQ)); + + CACHE_RULE *rule = NULL; + + bool error = false; + size_t len = strlen(cvalue); + + char value[strlen(cvalue) + 1]; + strcpy(value, cvalue); + + char *at = strchr(value, '@'); + char *user = value; + char *host; + + if (at) + { + *at = 0; + host = at + 1; + } + else + { + host = "%"; + } + + if (dequote_mysql(user)) + { + char pcre_user[2 * len + 1]; // Surely enough + + if (*user == 0) + { + strcpy(pcre_user, ".*"); + } + else + { + mysql_to_pcre(pcre_user, user, PCRE_QUOTE_VERBATIM); + } + + if (dequote_mysql(host)) + { + char pcre_host[2 * len + 1]; // Surely enough + + if (mysql_to_pcre(pcre_host, host, PCRE_QUOTE_QUERY) == MYSQL_ACCOUNT_WITH_WILDCARD) + { + op = (op == CACHE_OP_EQ ? CACHE_OP_LIKE : CACHE_OP_UNLIKE); + + char regexp[strlen(pcre_user) + 1 + strlen(pcre_host) + 1]; + + sprintf(regexp, "%s@%s", pcre_user, pcre_host); + + rule = cache_rule_create_regexp(attribute, op, regexp, debug); + } + else + { + // No wildcard, no need to use regexp. + + rule = (CACHE_RULE*)MXS_CALLOC(1, sizeof(CACHE_RULE)); + char *value = MXS_MALLOC(strlen(user) + 1 + strlen(host) + 1); + + if (rule && value) + { + sprintf(value, "%s@%s", user, host); + + rule->attribute = attribute; + rule->op = op; + rule->debug = debug; + rule->value = value; + } + else + { + MXS_FREE(rule); + MXS_FREE(value); + rule = NULL; + } + } + } + else + { + MXS_ERROR("Could not dequote host %s.", cvalue); + } + } + else + { + MXS_ERROR("Could not dequote user %s.", cvalue); + } + + return rule; +} + /** * Creates a CACHE_RULE object doing simple matching. * @@ -481,21 +625,30 @@ static CACHE_RULE *cache_rule_create_simple(cache_rule_attribute_t attribute, { ss_dassert((op == CACHE_OP_EQ) || (op == CACHE_OP_NEQ)); - CACHE_RULE *rule = (CACHE_RULE*)MXS_CALLOC(1, sizeof(CACHE_RULE)); + CACHE_RULE *rule; - char *value = MXS_STRDUP(cvalue); - - if (rule && value) + if (attribute == CACHE_ATTRIBUTE_USER) { - rule->attribute = attribute; - rule->op = op; - rule->value = value; - rule->debug = debug; + rule = cache_rule_create_user(attribute, op, cvalue, debug); } else { - MXS_FREE(value); - MXS_FREE(rule); + rule = (CACHE_RULE*)MXS_CALLOC(1, sizeof(CACHE_RULE)); + char *value = MXS_STRDUP(cvalue); + + if (rule && value) + { + rule->attribute = attribute; + rule->op = op; + rule->debug = debug; + rule->value = value; + } + else + { + MXS_FREE(rule); + MXS_FREE(value); + rule = NULL; + } } return rule; @@ -787,18 +940,42 @@ static bool cache_rule_matches_table(CACHE_RULE *self, const char *default_db, c } /** - * Returns boolean indicating whether the user rule matches the user or not. + * Returns boolean indicating whether the user rule matches the account or not. * - * @param self The CACHE_RULE object. - * @param user The current default db. + * @param self The CACHE_RULE object. + * @param account The account. * * @return True, if the rule matches, false otherwise. */ -static bool cache_rule_matches_user(CACHE_RULE *self, const char *user) +static bool cache_rule_matches_user(CACHE_RULE *self, const char *account) { ss_dassert(self->attribute == CACHE_ATTRIBUTE_USER); - return cache_rule_compare(self, user); + bool matches = cache_rule_compare(self, account); + + + if ((matches && (self->debug & CACHE_DEBUG_MATCHING)) || + (!matches && (self->debug & CACHE_DEBUG_NON_MATCHING))) + { + const char *text; + if (matches) + { + text = "MATCHES"; + } + else + { + text = "does NOT match"; + } + + MXS_NOTICE("Rule { \"attribute\": \"%s\", \"op\": \"%s\", \"value\": \"%s\" } %s \"%s\".", + cache_rule_attribute_to_string(self->attribute), + cache_rule_op_to_string(self->op), + self->value, + text, + account); + } + + return matches; } /** @@ -921,6 +1098,32 @@ static void cache_rules_add_use_rule(CACHE_RULES* self, CACHE_RULE* rule) self->use_rules = cache_rule_append(self->use_rules, rule); } +/** + * Creates a rules object from a JSON object. + * + * @param root The root JSON object in the rules file. + * @param debug The debug level. + * + * @return A rules object if the json object could be parsed, NULL otherwise. + */ +static CACHE_RULES* cache_rules_create_from_json(json_t* root, uint32_t debug) +{ + ss_dassert(root); + + CACHE_RULES *rules = cache_rules_create(debug); + + if (rules) + { + if (!cache_rules_parse_json(rules, root)) + { + cache_rules_free(rules); + rules = NULL; + } + } + + return rules; +} + /** * Parses the JSON object used for configuring the rules. * @@ -1106,3 +1309,138 @@ static bool cache_rules_parse_use_element(CACHE_RULES *self, json_t *object, siz return rule != NULL; } + +/** + * Remove quote characters surrounding a string. + * 'abcd' => abcd + * "abcd" => abcd + * `abcd` => abcd + * + * @param s The string to be dequoted. + * + * @note The string is modified in place. + */ +static bool dequote_mysql(char *s) +{ + bool dequoted = true; + + char *i = s; + char *end = s + strlen(s); + + // Remove space from the beginning + while (*i && isspace(*i)) + { + ++i; + } + + if (*i) + { + // Remove space from the end + while (isspace(*(end - 1))) + { + *(end - 1) = 0; + --end; + } + + ss_dassert(end > i); + + char quote; + + switch (*i) + { + case '\'': + case '"': + case '`': + quote = *i; + ++i; + break; + + default: + quote = 0; + } + + if (quote) + { + --end; + + if (*end == quote) + { + *end = 0; + + memmove(s, i, end - i + 1); + } + else + { + dequoted = false; + } + } + else if (i != s) + { + memmove(s, i, end - i + 1); + } + } + else + { + *s = 0; + } + + return dequoted; +} + +/** + * Convert MySQL/MariaDB account string to a pcre compatible one. + * + * @param pcre The string to which the conversion should be copied. + * To be on the safe size, the buffer should be twice the + * size of 'mysql'. + * @param mysql The mysql account string. + * @param approach Whether % should be converted or not. + * + * @return Whether or not the account contains a wildcard. + */ +static mysql_account_kind_t mysql_to_pcre(char *pcre, const char *mysql, pcre_quote_approach_t approach) +{ + mysql_account_kind_t rv = MYSQL_ACCOUNT_WITHOUT_WILDCARD; + + while (*mysql) + { + switch (*mysql) + { + case '%': + if (approach == PCRE_QUOTE_QUERY) + { + *pcre = '.'; + pcre++; + *pcre = '*'; + } + rv = MYSQL_ACCOUNT_WITH_WILDCARD; + break; + + case '\'': + case '^': + case '.': + case '$': + case '|': + case '(': + case ')': + case '[': + case ']': + case '*': + case '+': + case '?': + case '{': + case '}': + *pcre++ = '\\'; + // Flowthrough + default: + *pcre = *mysql; + } + + ++pcre; + ++mysql; + } + + *pcre = 0; + + return rv; +} diff --git a/server/modules/filter/cache/rules.h b/server/modules/filter/cache/rules.h index 0b69bf18e..d805b6451 100644 --- a/server/modules/filter/cache/rules.h +++ b/server/modules/filter/cache/rules.h @@ -59,11 +59,14 @@ typedef struct cache_rules CACHE_RULE *use_rules; // The rules for when to use data from the cache. } CACHE_RULES; +const char *cache_rule_attribute_to_string(cache_rule_attribute_t attribute); +const char *cache_rule_op_to_string(cache_rule_op_t op); CACHE_RULES *cache_rules_create(uint32_t debug); void cache_rules_free(CACHE_RULES *rules); CACHE_RULES *cache_rules_load(const char *path, uint32_t debug); +CACHE_RULES *cache_rules_parse(const char *json, uint32_t debug); bool cache_rules_should_store(CACHE_RULES *rules, const char *default_db, const GWBUF* query); bool cache_rules_should_use(CACHE_RULES *rules, const SESSION *session); diff --git a/server/modules/filter/cache/test/CMakeLists.txt b/server/modules/filter/cache/test/CMakeLists.txt new file mode 100644 index 000000000..061d2323f --- /dev/null +++ b/server/modules/filter/cache/test/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(testrules testrules.c ../rules.c) +include_directories(..) +target_link_libraries(testrules maxscale-common jansson) + +add_test(TestCache_rules testrules) diff --git a/server/modules/filter/cache/test/testrules.c b/server/modules/filter/cache/test/testrules.c new file mode 100644 index 000000000..d8f075558 --- /dev/null +++ b/server/modules/filter/cache/test/testrules.c @@ -0,0 +1,102 @@ +/* + * 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/bsl. + * + * Change Date: 2019-07-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 +#include "rules.h" +#include +#if !defined(SS_DEBUG) +#define SS_DEBUG +#endif +#include + +struct test_case +{ + const char* json; + struct + { + cache_rule_op_t op; + const char *value; + } expect; +}; + +#define TEST_CASE(op_from, from, op_to, to) \ +{ "{ \"use\": [ { \"attribute\": \"user\", \"op\": \"" #op_from "\", \"value\": \"" #from "\" } ] }",\ + { op_to, #to } } + +const struct test_case test_cases[] = +{ + TEST_CASE(=, bob, CACHE_OP_LIKE, bob@.*), + TEST_CASE(=, 'bob', CACHE_OP_LIKE, bob@.*), + TEST_CASE(=, bob@%, CACHE_OP_LIKE, bob@.*), + TEST_CASE(=, 'bob'@'%.52', CACHE_OP_LIKE, bob@.*\\.52), + TEST_CASE(=, bob@127.0.0.1, CACHE_OP_EQ, bob@127.0.0.1), + TEST_CASE(=, b*b@127.0.0.1, CACHE_OP_EQ, b*b@127.0.0.1), + TEST_CASE(=, b*b@%.0.0.1, CACHE_OP_LIKE, b\\*b@.*\\.0\\.0\\.1), + TEST_CASE(=, b*b@%.0.%.1, CACHE_OP_LIKE, b\\*b@.*\\.0\\..*\\.1), +}; + +const size_t n_test_cases = sizeof(test_cases) / sizeof(test_cases[0]); + +int test() +{ + int errors = 0; + + for (int i = 0; i < n_test_cases; ++i) + { + const struct test_case *test_case = &test_cases[i]; + + CACHE_RULES *rules = cache_rules_parse(test_case->json, 0); + ss_dassert(rules); + + CACHE_RULE *rule = rules->use_rules; + ss_dassert(rule); + + if (rule->op != test_case->expect.op) + { + printf("%s\nExpected: %s,\nGot : %s\n", + test_case->json, + cache_rule_op_to_string(test_case->expect.op), + cache_rule_op_to_string(rule->op)); + ++errors; + } + + if (strcmp(rule->value, test_case->expect.value) != 0) + { + printf("%s\nExpected: %s,\nGot : %s\n", + test_case->json, + test_case->expect.value, + rule->value); + ++errors; + } + } + + return errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} + +int main() +{ + int rc = EXIT_FAILURE; + + if (mxs_log_init(NULL, ".", MXS_LOG_TARGET_DEFAULT)) + { + rc = test(); + + mxs_log_finish(); + } + else + { + printf("error: Could not initialize log."); + } + + return rc; +} diff --git a/server/modules/filter/dbfwfilter/dbfwfilter.c b/server/modules/filter/dbfwfilter/dbfwfilter.c index 16cd0a027..c4baa3a58 100644 --- a/server/modules/filter/dbfwfilter/dbfwfilter.c +++ b/server/modules/filter/dbfwfilter/dbfwfilter.c @@ -1657,7 +1657,9 @@ static char* create_parse_error(FW_INSTANCE* my_instance, { char *msg = NULL; - char format[] = "dbfwfilter: Query could not be %s and will hence be rejected"; + char format[] = + "dbfwfilter: Query could not be %s and will hence be rejected. " + "Please ensure that the SQL syntax is correct"; size_t len = sizeof(format) + strlen(reason); // sizeof includes the trailing NULL as well. char message[len]; sprintf(message, format, reason); diff --git a/server/modules/include/avrorouter.h b/server/modules/include/avrorouter.h index 91128311c..ab2f2b88e 100644 --- a/server/modules/include/avrorouter.h +++ b/server/modules/include/avrorouter.h @@ -1,3 +1,16 @@ +/* + * 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/bsl. + * + * Change Date: 2019-07-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. + */ + /** * MaxScale AVRO router * diff --git a/server/modules/include/rwsplit_internal.h b/server/modules/include/rwsplit_internal.h index af0619945..9fadc8982 100644 --- a/server/modules/include/rwsplit_internal.h +++ b/server/modules/include/rwsplit_internal.h @@ -1,7 +1,14 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * 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/bsl. + * + * Change Date: 2019-07-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. */ /* diff --git a/server/modules/include/sharding_common.h b/server/modules/include/sharding_common.h index b67efbc3b..b676b31d6 100644 --- a/server/modules/include/sharding_common.h +++ b/server/modules/include/sharding_common.h @@ -1,3 +1,16 @@ +/* + * 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/bsl. + * + * Change Date: 2019-07-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. + */ + #ifndef _SHARDING_COMMON_HG #define _SHARDING_COMMON_HG diff --git a/server/modules/monitor/mysqlmon.h b/server/modules/monitor/mysqlmon.h index 3eb551ccc..efbd140f3 100644 --- a/server/modules/monitor/mysqlmon.h +++ b/server/modules/monitor/mysqlmon.h @@ -77,6 +77,7 @@ typedef struct bool failover; /**< If simple failover is enabled */ int failcount; /**< How many monitoring cycles servers must be down before failover is initiated */ + bool warn_failover; /**< Log a warning when failover happens */ } MYSQL_MONITOR; #endif diff --git a/server/modules/monitor/mysqlmon/mysql_mon.c b/server/modules/monitor/mysqlmon/mysql_mon.c index 0b289ce74..f454ae84f 100644 --- a/server/modules/monitor/mysqlmon/mysql_mon.c +++ b/server/modules/monitor/mysqlmon/mysql_mon.c @@ -275,6 +275,7 @@ startMonitor(MONITOR *monitor, const CONFIG_PARAMETER* params) handle->mysql51_replication = false; handle->failover = false; handle->failcount = MYSQLMON_DEFAULT_FAILCOUNT; + handle->warn_failover = true; memset(handle->events, false, sizeof(handle->events)); spinlock_init(&handle->lock); } @@ -1093,18 +1094,21 @@ void do_failover(MYSQL_MONITOR *handle, MONITOR_SERVERS *db) { if (SERVER_IS_RUNNING(db->server)) { - if (!SERVER_IS_MASTER(db->server)) + if (!SERVER_IS_MASTER(db->server) && handle->warn_failover) { MXS_WARNING("Failover initiated, server '%s' is now the master. " "All other servers are set into maintenance mode.", db->server->unique_name); + handle->warn_failover = false; } + server_clear_set_status(db->server, SERVER_SLAVE, SERVER_MASTER); monitor_set_pending_status(db, SERVER_MASTER); monitor_clear_pending_status(db, SERVER_SLAVE); } else { + server_set_status(db->server, SERVER_MAINT); monitor_set_pending_status(db, SERVER_MAINT); } db = db->next; @@ -1395,6 +1399,10 @@ monitorMain(void *arg) /** Other servers have died, initiate a failover to the last remaining server */ do_failover(handle, mon->databases); } + else + { + handle->warn_failover = true; + } } ptr = mon->databases; diff --git a/server/test/maxscale_test.h.in b/server/test/maxscale_test.h.in index b5448295c..dd366d801 100644 --- a/server/test/maxscale_test.h.in +++ b/server/test/maxscale_test.h.in @@ -1,5 +1,19 @@ #ifndef MAXSCALE_TEST_H #define MAXSCALE_TEST_H + +/* + * 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/bsl. + * + * Change Date: 2019-07-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. + */ + #define TEST_DIR "@CMAKE_BINARY_DIR@" #define TEST_LOG_DIR "@CMAKE_BINARY_DIR@/log" #define TEST_BIN_DIR "@CMAKE_BINARY_DIR@/bin"