diff --git a/Documentation/Monitors/MySQL-Monitor.md b/Documentation/Monitors/MySQL-Monitor.md index 050109c2b..f0be5738c 100644 --- a/Documentation/Monitors/MySQL-Monitor.md +++ b/Documentation/Monitors/MySQL-Monitor.md @@ -164,6 +164,21 @@ can start is `monitor_interval * failcount`. This means that to trigger a failover after 10 seconds of master failure with a _monitor_interval_ of 1000 milliseconds, the value of _failcount_ must be 10. +### `failover_recovery` + +Allow recovery after failover. This feature takes a boolean parameter is +disabled by default. + +Normally if a failover has been triggered and the last remaining server is +chosen as the master, the monitor will set all of the failed servers into +maintenance mode. When this option is enabled, the failed servers are allowed to +rejoin the cluster. + +This option should be enabled when failover in MaxScale is used in conjunction +with an external agent that resets the slave status for new master servers. One +of these agents is the _replication-manager_ which clears the slave +configuration for each new master and removes the read-only mode. + ## Example 1 - Monitor script Here is an example shell script which sends an email to an admin when a server goes down. diff --git a/Documentation/Release-Notes/MaxScale-2.1.1-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.1.1-Release-Notes.md new file mode 100644 index 000000000..ed375a32c --- /dev/null +++ b/Documentation/Release-Notes/MaxScale-2.1.1-Release-Notes.md @@ -0,0 +1,47 @@ +# MariaDB MaxScale 2.1.1 Release Notes + +Release 2.1.1 is a Beta release. + +This document describes the changes in release 2.1.1, when compared to +release 2.1.0. + +For any problems you encounter, please consider submitting a bug +report at [Jira](https://jira.mariadb.org). + +## New Features + +### Failover Recovery for MySQL Monitor + +The `failover_recovery` option allows the failed nodes to rejoin the cluster +after a failover has been triggered. This makes it possible for external actors +to recover the failed nodes without having to manually clear the maintenance +mode. + +For more information about the failover mode and how it works, read the +[MySQL Monitor](../Monitors/MySQL-Monitor.md) documentation. + +## Bug fixes + +[Here is a list of bugs fixed since the release of MaxScale 2.1.0.](https://jira.mariadb.org/issues/?jql=project%20%3D%20MXS%20AND%20issuetype%20%3D%20Bug%20AND%20resolution%20in%20(Fixed%2C%20Done)%20AND%20fixVersion%20%3D%202.1.1%20AND%20fixVersion%20NOT%20IN%20(2.1.0)) + +* + +## Known Issues and Limitations + +There are some limitations and known issues within this version of MaxScale. +For more information, please refer to the [Limitations](../About/Limitations.md) document. + +## Packaging + +RPM and Debian packages are provided for the Linux distributions supported +by MariaDB Enterprise. + +Packages can be downloaded [here](https://mariadb.com/resources/downloads). + +## Source Code + +The source code of MaxScale is tagged at GitHub with a tag, which is identical +with the version of MaxScale. For instance, the tag of version X.Y.Z of MaxScale +is X.Y.Z. Further, *master* always refers to the latest released non-beta version. + +The source code is available [here](https://github.com/mariadb-corporation/MaxScale). diff --git a/avro/maxavro_file.c b/avro/maxavro_file.c index c1d9387a4..fc45d2046 100644 --- a/avro/maxavro_file.c +++ b/avro/maxavro_file.c @@ -49,11 +49,12 @@ bool maxavro_verify_block(MAXAVRO_FILE *file) int rc = fread(sync, 1, SYNC_MARKER_SIZE, file->file); if (rc != SYNC_MARKER_SIZE) { - if (rc == -1) + if (ferror(file->file)) { - MXS_ERROR("Failed to read file: %d %s", errno, strerror(errno)); + char err[MXS_STRERROR_BUFLEN]; + MXS_ERROR("Failed to read file: %d %s", errno, strerror_r(errno, err, sizeof(err))); } - else + else if (rc > 0 || !feof(file->file)) { MXS_ERROR("Short read when reading sync marker. Read %d bytes instead of %d", rc, SYNC_MARKER_SIZE); diff --git a/avro/maxavro_record.c b/avro/maxavro_record.c index 982913cea..fb5b6b5f3 100644 --- a/avro/maxavro_record.c +++ b/avro/maxavro_record.c @@ -49,9 +49,11 @@ static json_t* read_and_pack_value(MAXAVRO_FILE *file, MAXAVRO_SCHEMA_FIELD *fie case MAXAVRO_TYPE_LONG: { uint64_t val = 0; - maxavro_read_integer(file, &val); - json_int_t jsonint = val; - value = json_pack("I", jsonint); + if (maxavro_read_integer(file, &val)) + { + json_int_t jsonint = val; + value = json_pack("I", jsonint); + } } break; @@ -74,11 +76,23 @@ static json_t* read_and_pack_value(MAXAVRO_FILE *file, MAXAVRO_SCHEMA_FIELD *fie break; case MAXAVRO_TYPE_FLOAT: + { + float f = 0; + if (maxavro_read_float(file, &f)) + { + double d = f; + value = json_pack("f", d); + } + } + + break; case MAXAVRO_TYPE_DOUBLE: { double d = 0; - maxavro_read_double(file, &d); - value = json_pack("f", d); + if (maxavro_read_double(file, &d)) + { + value = json_pack("f", d); + } } break; diff --git a/server/core/maxkeys.c b/server/core/maxkeys.c index 7f9945591..3446f7bfd 100644 --- a/server/core/maxkeys.c +++ b/server/core/maxkeys.c @@ -29,6 +29,7 @@ #include #include +#include struct option options[] = { @@ -94,6 +95,7 @@ int main(int argc, char **argv) } mxs_log_init(NULL, NULL, MXS_LOG_TARGET_DEFAULT); + random_jkiss_init(); if (secrets_write_keys(directory) != 0) { diff --git a/server/core/maxpasswd.c b/server/core/maxpasswd.c index da5266ed3..fcb1bf4e4 100644 --- a/server/core/maxpasswd.c +++ b/server/core/maxpasswd.c @@ -31,6 +31,7 @@ #include #include +#include #include "maxscale/secrets.h" @@ -154,6 +155,8 @@ int main(int argc, char **argv) mxs_log_set_priority_enabled(LOG_INFO, false); mxs_log_set_priority_enabled(LOG_DEBUG, false); + random_jkiss_init(); + size_t len = strlen(password); if (len > MXS_PASSWORD_MAXLEN) diff --git a/server/core/mysql_binlog.c b/server/core/mysql_binlog.c index e67934885..8eeaaa02e 100644 --- a/server/core/mysql_binlog.c +++ b/server/core/mysql_binlog.c @@ -290,7 +290,7 @@ static void unpack_datetime2(uint8_t *ptr, uint8_t decimals, struct tm *dest) dest->tm_min = (time >> 6) % (1 << 6); dest->tm_hour = time >> 12; dest->tm_mday = date % (1 << 5); - dest->tm_mon = yearmonth % 13; + dest->tm_mon = (yearmonth % 13) - 1; /** struct tm stores the year as: Year - 1900 */ dest->tm_year = (yearmonth / 13) - 1900; @@ -347,7 +347,7 @@ static void unpack_date(uint8_t *ptr, struct tm *dest) uint64_t val = ptr[0] + (ptr[1] << 8) + (ptr[2] << 16); memset(dest, 0, sizeof(struct tm)); dest->tm_mday = val & 31; - dest->tm_mon = (val >> 5) & 15; + dest->tm_mon = ((val >> 5) & 15) - 1; dest->tm_year = (val >> 9) - 1900; } @@ -560,34 +560,42 @@ static uint64_t unpack_bytes(uint8_t *ptr, size_t bytes) switch (bytes) { - case 1: - val = ptr[0]; - break; - case 2: - val = ptr[1] | ((uint64_t)(ptr[0]) << 8); - break; - case 3: - val = (uint64_t)ptr[2] | ((uint64_t)ptr[1] << 8) | ((uint64_t)ptr[0] << 16); - break; - case 4: - val = (uint64_t)ptr[3] | ((uint64_t)ptr[2] << 8) | ((uint64_t)ptr[1] << 16) | ((uint64_t)ptr[0] << 24); - break; - case 5: - val = (uint64_t)ptr[4] | ((uint64_t)ptr[3] << 8) | ((uint64_t)ptr[2] << 16) | ((uint64_t)ptr[1] << 24) | (( - uint64_t)ptr[0] << 32); - break; - case 6: - val = (uint64_t)ptr[5] | ((uint64_t)ptr[4] << 8) | ((uint64_t)ptr[3] << 16) | ((uint64_t)ptr[2] << 24) | (( - uint64_t)ptr[1] << 32) | ((uint64_t)ptr[0] << 40); - break; - case 7: - val = (uint64_t)ptr[6] | ((uint64_t)ptr[5] << 8) | ((uint64_t)ptr[4] << 16) | ((uint64_t)ptr[3] << 24) | (( - uint64_t)ptr[2] << 32) | ((uint64_t)ptr[1] << 40) | ((uint64_t)ptr[0] << 48); - break; - case 8: - val = (uint64_t)ptr[7] | ((uint64_t)ptr[6] << 8) | ((uint64_t)ptr[5] << 16) | ((uint64_t)ptr[4] << 24) | (( - uint64_t)ptr[3] << 32) | ((uint64_t)ptr[2] << 40) | ((uint64_t)ptr[1] << 48) | ((uint64_t)ptr[0] << 56); - break; + case 1: + val = ptr[0]; + break; + case 2: + val = ptr[1] | ((uint64_t)(ptr[0]) << 8); + break; + case 3: + val = (uint64_t)ptr[2] | ((uint64_t)ptr[1] << 8) | + ((uint64_t)ptr[0] << 16); + break; + case 4: + val = (uint64_t)ptr[3] | ((uint64_t)ptr[2] << 8) | + ((uint64_t)ptr[1] << 16) | ((uint64_t)ptr[0] << 24); + break; + case 5: + val = (uint64_t)ptr[4] | ((uint64_t)ptr[3] << 8) | + ((uint64_t)ptr[2] << 16) | ((uint64_t)ptr[1] << 24) | + ((uint64_t)ptr[0] << 32); + break; + case 6: + val = (uint64_t)ptr[5] | ((uint64_t)ptr[4] << 8) | + ((uint64_t)ptr[3] << 16) | ((uint64_t)ptr[2] << 24) | + ((uint64_t)ptr[1] << 32) | ((uint64_t)ptr[0] << 40); + break; + case 7: + val = (uint64_t)ptr[6] | ((uint64_t)ptr[5] << 8) | + ((uint64_t)ptr[4] << 16) | ((uint64_t)ptr[3] << 24) | + ((uint64_t)ptr[2] << 32) | ((uint64_t)ptr[1] << 40) | + ((uint64_t)ptr[0] << 48); + break; + case 8: + val = (uint64_t)ptr[7] | ((uint64_t)ptr[6] << 8) | + ((uint64_t)ptr[5] << 16) | ((uint64_t)ptr[4] << 24) | + ((uint64_t)ptr[3] << 32) | ((uint64_t)ptr[2] << 40) | + ((uint64_t)ptr[1] << 48) | ((uint64_t)ptr[0] << 56); + break; } return val; @@ -608,12 +616,11 @@ size_t unpack_decimal_field(uint8_t *ptr, uint8_t *metadata, double *val_float) int fbytes = fpart1 * 4 + dig_bytes[fpart2]; /** Remove the sign bit and store it locally */ - bool signed_int = (ptr[0] & 0x80); + bool negative = (ptr[0] & 0x80) == 0; + ptr[0] ^= 0x80; - if (!signed_int) + if (negative) { - ptr[0] |= 0x80; - for (int i = 0; i < ibytes; i++) { ptr[i] = ~ptr[i]; @@ -628,7 +635,7 @@ size_t unpack_decimal_field(uint8_t *ptr, uint8_t *metadata, double *val_float) int64_t val_i = unpack_bytes(ptr, ibytes); int64_t val_f = fbytes ? unpack_bytes(ptr + ibytes, fbytes) : 0; - if (!signed_int) + if (negative) { val_i = -val_i; val_f = -val_f; diff --git a/server/modules/monitor/mysqlmon.h b/server/modules/monitor/mysqlmon.h index d2cfd5f02..eca4b6b83 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 failover_recovery; /**< Allow servers to rejoin the cluster in failover mode */ bool warn_failover; /**< Log a warning when failover happens */ } MYSQL_MONITOR; diff --git a/server/modules/monitor/mysqlmon/mysql_mon.c b/server/modules/monitor/mysqlmon/mysql_mon.c index 4961ddd27..9caa297f8 100644 --- a/server/modules/monitor/mysqlmon/mysql_mon.c +++ b/server/modules/monitor/mysqlmon/mysql_mon.c @@ -127,6 +127,7 @@ MXS_MODULE* MXS_CREATE_MODULE() {"multimaster", MXS_MODULE_PARAM_BOOL, "false"}, {"failover", MXS_MODULE_PARAM_BOOL, "false"}, {"failcount", MXS_MODULE_PARAM_COUNT, "5"}, + {"failover_recovery", MXS_MODULE_PARAM_BOOL, "false"}, { "script", MXS_MODULE_PARAM_PATH, @@ -280,6 +281,7 @@ startMonitor(MXS_MONITOR *monitor, const MXS_CONFIG_PARAMETER* params) handle->multimaster = config_get_bool(params, "multimaster"); handle->failover = config_get_bool(params, "failover"); handle->failcount = config_get_integer(params, "failcount"); + handle->failover_recovery = config_get_bool(params, "failover_recovery"); handle->mysql51_replication = config_get_bool(params, "mysql51_replication"); handle->script = config_copy_string(params, "script"); handle->events = config_get_enum(params, "events", mxs_monitor_event_enum_values); @@ -1006,9 +1008,10 @@ void do_failover(MYSQL_MONITOR *handle, MXS_MONITOR_SERVERS *db) { 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); + MXS_WARNING("Failover initiated, server '%s' is now the master.%s", + db->server->unique_name, + handle->failover_recovery ? + "" : " All other servers are set into maintenance mode."); handle->warn_failover = false; } @@ -1016,7 +1019,7 @@ void do_failover(MYSQL_MONITOR *handle, MXS_MONITOR_SERVERS *db) monitor_set_pending_status(db, SERVER_MASTER); monitor_clear_pending_status(db, SERVER_SLAVE); } - else + else if (!handle->failover_recovery) { server_set_status_nolock(db->server, SERVER_MAINT); monitor_set_pending_status(db, SERVER_MAINT); diff --git a/server/modules/routing/avrorouter/avro.c b/server/modules/routing/avrorouter/avro.c index 435cc8fda..49eda9f41 100644 --- a/server/modules/routing/avrorouter/avro.c +++ b/server/modules/routing/avrorouter/avro.c @@ -991,7 +991,8 @@ extract_message(GWBUF *errpkt) * */ static void -errorReply(MXS_ROUTER *instance, void *router_session, GWBUF *message, DCB *backend_dcb, mxs_error_action_t action, +errorReply(MXS_ROUTER *instance, void *router_session, GWBUF *message, DCB *backend_dcb, + mxs_error_action_t action, bool *succp) { /** We should never end up here */ diff --git a/server/modules/routing/avrorouter/avro_client.c b/server/modules/routing/avrorouter/avro_client.c index 811739d40..9957524db 100644 --- a/server/modules/routing/avrorouter/avro_client.c +++ b/server/modules/routing/avrorouter/avro_client.c @@ -788,25 +788,25 @@ static bool avro_client_stream_data(AVRO_CLIENT *client) { switch (client->format) { - case AVRO_FORMAT_JSON: - /** Currently only JSON format supports seeking to a GTID */ - if (client->requested_gtid && - seek_to_index_pos(client, client->file_handle) && - seek_to_gtid(client, client->file_handle)) - { - client->requested_gtid = false; - } + case AVRO_FORMAT_JSON: + /** Currently only JSON format supports seeking to a GTID */ + if (client->requested_gtid && + seek_to_index_pos(client, client->file_handle) && + seek_to_gtid(client, client->file_handle)) + { + client->requested_gtid = false; + } - read_more = stream_json(client); - break; + read_more = stream_json(client); + break; - case AVRO_FORMAT_AVRO: - read_more = stream_binary(client); - break; + case AVRO_FORMAT_AVRO: + read_more = stream_binary(client); + break; - default: - MXS_ERROR("Unexpected format: %d", client->format); - break; + default: + MXS_ERROR("Unexpected format: %d", client->format); + break; } @@ -847,13 +847,15 @@ GWBUF* read_avro_json_schema(const char *avrofile, const char* dir) if (file) { int nread; - while ((nread = fread(buffer, 1, sizeof(buffer), file)) > 0) + while ((nread = fread(buffer, 1, sizeof(buffer) - 1, file)) > 0) { while (isspace(buffer[nread - 1])) { nread--; } + buffer[nread++] = '\n'; + GWBUF * newbuf = gwbuf_alloc_and_load(nread, buffer); if (newbuf) diff --git a/server/modules/routing/avrorouter/avro_index.c b/server/modules/routing/avrorouter/avro_index.c index b7139ae69..44e0e1b43 100644 --- a/server/modules/routing/avrorouter/avro_index.c +++ b/server/modules/routing/avrorouter/avro_index.c @@ -88,18 +88,22 @@ void avro_index_file(AVRO_INSTANCE *router, const char* filename) snprintf(sql, sizeof(sql), "SELECT position FROM "INDEX_TABLE_NAME " WHERE filename=\"%s\";", name); + if (sqlite3_exec(router->sqlite_handle, sql, index_query_cb, &pos, &errmsg) != SQLITE_OK) { MXS_ERROR("Failed to read last indexed position of file '%s': %s", name, errmsg); + sqlite3_free(errmsg); + maxavro_file_close(file); + return; } - else if (pos > 0) + + /** Continue from last position */ + if (pos > 0 && !maxavro_record_set_pos(file, pos)) { - /** Continue from last position */ - maxavro_record_set_pos(file, pos); + maxavro_file_close(file); + return; } - sqlite3_free(errmsg); - errmsg = NULL; gtid_pos_t prev_gtid = {0, 0, 0, 0, 0}; diff --git a/server/modules/routing/avrorouter/avro_rbr.c b/server/modules/routing/avrorouter/avro_rbr.c index 19b9b4356..ca6534a3b 100644 --- a/server/modules/routing/avrorouter/avro_rbr.c +++ b/server/modules/routing/avrorouter/avro_rbr.c @@ -352,44 +352,64 @@ bool handle_row_event(AVRO_INSTANCE *router, REP_HEADER *hdr, uint8_t *ptr) */ void set_numeric_field_value(avro_value_t *field, uint8_t type, uint8_t *metadata, uint8_t *value) { - int64_t i = 0; - switch (type) { case TABLE_COL_TYPE_TINY: - i = *value; - avro_value_set_int(field, i); - break; + { + char c = *value; + avro_value_set_int(field, c); + break; + } case TABLE_COL_TYPE_SHORT: - memcpy(&i, value, 2); - avro_value_set_int(field, i); - break; + { + short s = gw_mysql_get_byte2(value); + avro_value_set_int(field, s); + break; + } case TABLE_COL_TYPE_INT24: - memcpy(&i, value, 3); - avro_value_set_int(field, i); - break; + { + int x = gw_mysql_get_byte3(value); + + if (x & 0x800000) + { + x = -((0xffffff & (~x)) + 1); + } + + avro_value_set_int(field, x); + break; + } case TABLE_COL_TYPE_LONG: - memcpy(&i, value, 4); - avro_value_set_int(field, i); - break; + { + int x = gw_mysql_get_byte4(value); + avro_value_set_int(field, x); + break; + } case TABLE_COL_TYPE_LONGLONG: - memcpy(&i, value, 8); - avro_value_set_int(field, i); - break; + { + long l = gw_mysql_get_byte8(value); + avro_value_set_long(field, l); + break; + } case TABLE_COL_TYPE_FLOAT: - memcpy(&i, value, 4); - avro_value_set_float(field, (float)i); - break; + { + float f = 0; + memcpy(&f, value, 4); + avro_value_set_float(field, f); + break; + } case TABLE_COL_TYPE_DOUBLE: - memcpy(&i, value, 8); - avro_value_set_float(field, (double)i); - break; + { + double d = 0; + memcpy(&d, value, 8); + avro_value_set_double(field, d); + break; + } default: break;