From 526e48e459995b3da58b150083da72f9dd7892c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Thu, 9 Feb 2017 15:00:19 +0200 Subject: [PATCH 01/15] Fix floats being read as doubles The avro floating point numbers were processed as 8 byte values when they are 4 bytes values. --- avro/maxavro_record.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/avro/maxavro_record.c b/avro/maxavro_record.c index 82e05f873..9327bcf69 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; From 47a1cdad5db9b5c14bdef94fa414cc9a461ea29a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Thu, 9 Feb 2017 15:33:17 +0200 Subject: [PATCH 02/15] Fix binlog integer conversion The binlog integers were stored as unsigned values instead of signed ones. --- server/modules/routing/avro/avro_rbr.c | 52 ++++++++++++++++++-------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/server/modules/routing/avro/avro_rbr.c b/server/modules/routing/avro/avro_rbr.c index b06d601ff..85f85605e 100644 --- a/server/modules/routing/avro/avro_rbr.c +++ b/server/modules/routing/avro/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); + { + 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); + { + 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); + { + 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); + { + 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); + { + long l = gw_mysql_get_byte8(value); + avro_value_set_int(field, l); break; + } case TABLE_COL_TYPE_FLOAT: - memcpy(&i, value, 4); - avro_value_set_float(field, (float)i); + { + 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); + { + double d = 0; + memcpy(&d, value, 8); + avro_value_set_double(field, d); break; + } default: break; From fb21b999830a4869b5bc1beb3391128a5835a623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Thu, 9 Feb 2017 16:50:14 +0200 Subject: [PATCH 03/15] Fix DATE and DATETIME months The month values for DATE and DATETIME were off by one. --- server/core/mysql_binlog.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/core/mysql_binlog.c b/server/core/mysql_binlog.c index c2c67cd0d..597c7abed 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; } From 10e74225c04fadaa576460abf5401a7d9775d9d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Thu, 9 Feb 2017 16:53:55 +0200 Subject: [PATCH 04/15] Add missing error detection If the file fails to seek to the correct position, the indexing should stop. --- server/modules/routing/avro/avro_index.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/server/modules/routing/avro/avro_index.c b/server/modules/routing/avro/avro_index.c index 905c712c4..d5ede4944 100644 --- a/server/modules/routing/avro/avro_index.c +++ b/server/modules/routing/avro/avro_index.c @@ -87,18 +87,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}; From 0b892c9714bf9f06e8dfa915796e916b5fbe92a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Sun, 12 Feb 2017 07:38:52 +0200 Subject: [PATCH 05/15] Add missing newline to avrorouter output The schema was not terminated with a newline. --- server/modules/routing/avro/avro_client.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/modules/routing/avro/avro_client.c b/server/modules/routing/avro/avro_client.c index 90cd9e268..5b994c0e6 100644 --- a/server/modules/routing/avro/avro_client.c +++ b/server/modules/routing/avro/avro_client.c @@ -848,13 +848,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) From 65caaab77bb1fd4bcc7de119a40583d9c1cc067c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 13 Feb 2017 08:22:24 +0200 Subject: [PATCH 06/15] Fix maxavro block verification errors An error was logged when the end of file was reached. The error should only be logged when a partial sync marker is read and the end of file has not been reached. --- avro/maxavro_file.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/avro/maxavro_file.c b/avro/maxavro_file.c index 7c5083489..07225a32b 100644 --- a/avro/maxavro_file.c +++ b/avro/maxavro_file.c @@ -12,6 +12,7 @@ */ #include "maxavro.h" +#include "skygw_utils.h" #include #include #include @@ -49,11 +50,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[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); From 4bd743d3cea562b8c98eb28a709c4dadc081a633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 13 Feb 2017 08:25:26 +0200 Subject: [PATCH 07/15] Use correct Avro function The avrorouter used the 32-bit function to store 64-bit integers. This caused incorrect values to be stored. --- server/modules/routing/avro/avro_rbr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/modules/routing/avro/avro_rbr.c b/server/modules/routing/avro/avro_rbr.c index 85f85605e..730c3803b 100644 --- a/server/modules/routing/avro/avro_rbr.c +++ b/server/modules/routing/avro/avro_rbr.c @@ -391,7 +391,7 @@ void set_numeric_field_value(avro_value_t *field, uint8_t type, uint8_t *metadat case TABLE_COL_TYPE_LONGLONG: { long l = gw_mysql_get_byte8(value); - avro_value_set_int(field, l); + avro_value_set_long(field, l); break; } From f9732d7041bc3008cea52c5e118017509d078178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Sat, 17 Dec 2016 12:15:16 +0200 Subject: [PATCH 08/15] Fix DECIMAL handling in Avrorouter The DECIMAL value type is now properly handled in Avrorouter. It is processed into an Avro double value when before it was ignored and replaced with a zero integer. Backported to the 2.0 branch. --- server/core/mysql_binlog.c | 88 ++++++++++++++++++++++- server/include/mysql_binlog.h | 9 +-- server/modules/routing/avro/avro_rbr.c | 23 +----- server/modules/routing/avro/avro_schema.c | 2 +- 4 files changed, 94 insertions(+), 28 deletions(-) diff --git a/server/core/mysql_binlog.c b/server/core/mysql_binlog.c index 597c7abed..0fa01d866 100644 --- a/server/core/mysql_binlog.c +++ b/server/core/mysql_binlog.c @@ -24,6 +24,7 @@ #include #include #include +#include /** * @brief Convert a table column type to a string @@ -357,7 +358,7 @@ static void unpack_date(uint8_t *ptr, struct tm *dest) * @param metadata Pointer to field metadata * @return Length of the processed field in bytes */ -uint64_t unpack_enum(uint8_t *ptr, uint8_t *metadata, uint8_t *dest) +size_t unpack_enum(uint8_t *ptr, uint8_t *metadata, uint8_t *dest) { memcpy(dest, ptr, metadata[1]); return metadata[1]; @@ -380,7 +381,7 @@ uint64_t unpack_enum(uint8_t *ptr, uint8_t *metadata, uint8_t *dest) * @param dest Destination where the value is stored * @return Length of the processed field in bytes */ -uint64_t unpack_bit(uint8_t *ptr, uint8_t *null_mask, uint32_t col_count, +size_t unpack_bit(uint8_t *ptr, uint8_t *null_mask, uint32_t col_count, uint32_t curr_col_index, uint8_t *metadata, uint64_t *dest) { if (metadata[1]) @@ -438,7 +439,7 @@ static size_t temporal_field_size(uint8_t type, uint8_t decimals) * @param val Extracted packed value * @param tm Pointer where the unpacked temporal value is stored */ -uint64_t unpack_temporal_value(uint8_t type, uint8_t *ptr, uint8_t *metadata, struct tm *tm) +size_t unpack_temporal_value(uint8_t type, uint8_t *ptr, uint8_t *metadata, struct tm *tm) { switch (type) { @@ -553,3 +554,84 @@ size_t unpack_numeric_field(uint8_t *src, uint8_t type, uint8_t *metadata, uint8 memcpy(dest, src, size); return size; } + +static uint64_t unpack_bytes(uint8_t *ptr, size_t bytes) +{ + uint64_t val = 0; + + 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; + } + + return val; +} + +size_t unpack_decimal_field(uint8_t *ptr, uint8_t *metadata, double *val_float) +{ + const int dec_dig = 9; + int precision = metadata[0]; + int decimals = metadata[1]; + int dig_bytes[] = {0, 1, 1, 2, 2, 3, 3, 4, 4, 4}; + int ipart = precision - decimals; + int ipart1 = ipart / dec_dig; + int fpart1 = decimals / dec_dig; + int ipart2 = ipart - ipart1 * dec_dig; + int fpart2 = decimals - fpart1 * dec_dig; + int ibytes = ipart1 * 4 + dig_bytes[ipart2]; + int fbytes = fpart1 * 4 + dig_bytes[fpart2]; + + /** Remove the sign bit and store it locally */ + bool signed_int = (ptr[0] & 0x80); + + if (!signed_int) + { + ptr[0] |= 0x80; + + for (int i = 0; i < ibytes; i++) + { + ptr[i] = ~ptr[i]; + } + + for (int i = 0; i < fbytes; i++) + { + ptr[i + ibytes] = ~ptr[i + ibytes]; + } + } + + int64_t val_i = unpack_bytes(ptr, ibytes); + int64_t val_f = fbytes ? unpack_bytes(ptr + ibytes, fbytes) : 0; + + if (!signed_int) + { + val_i = -val_i; + val_f = -val_f; + } + + *val_float = (double)val_i + ((double)val_f / (pow(10.0, decimals))); + + return ibytes + fbytes; +} diff --git a/server/include/mysql_binlog.h b/server/include/mysql_binlog.h index 8c3c90cf3..b1a81eaa2 100644 --- a/server/include/mysql_binlog.h +++ b/server/include/mysql_binlog.h @@ -83,11 +83,12 @@ bool column_is_decimal(uint8_t type); bool fixed_string_is_enum(uint8_t type); /** Value unpacking */ -uint64_t unpack_temporal_value(uint8_t type, uint8_t *ptr, uint8_t* metadata, struct tm *tm); -uint64_t unpack_enum(uint8_t *ptr, uint8_t *metadata, uint8_t *dest); -uint64_t unpack_numeric_field(uint8_t *ptr, uint8_t type, uint8_t* metadata, uint8_t* val); -uint64_t unpack_bit(uint8_t *ptr, uint8_t *null_mask, uint32_t col_count, +size_t unpack_temporal_value(uint8_t type, uint8_t *ptr, uint8_t* metadata, struct tm *tm); +size_t unpack_enum(uint8_t *ptr, uint8_t *metadata, uint8_t *dest); +size_t unpack_numeric_field(uint8_t *ptr, uint8_t type, uint8_t* metadata, uint8_t* val); +size_t unpack_bit(uint8_t *ptr, uint8_t *null_mask, uint32_t col_count, uint32_t curr_col_index, uint8_t *metadata, uint64_t *dest); +size_t unpack_decimal_field(uint8_t *ptr, uint8_t *metadata, double *val_float); void format_temporal_value(char *str, size_t size, uint8_t type, struct tm *tm); diff --git a/server/modules/routing/avro/avro_rbr.c b/server/modules/routing/avro/avro_rbr.c index 730c3803b..148c6932a 100644 --- a/server/modules/routing/avro/avro_rbr.c +++ b/server/modules/routing/avro/avro_rbr.c @@ -558,26 +558,9 @@ uint8_t* process_row_event_data(TABLE_MAP *map, TABLE_CREATE *create, avro_value } else if (column_is_decimal(map->column_types[i])) { - const int dec_dig = 9; - int precision = metadata[metadata_offset]; - int decimals = metadata[metadata_offset + 1]; - int dig_bytes[] = {0, 1, 1, 2, 2, 3, 3, 4, 4, 4}; - int ipart = precision - decimals; - int ipart1 = ipart / dec_dig; - int fpart1 = decimals / dec_dig; - int ipart2 = ipart - ipart1 * dec_dig; - int fpart2 = decimals - fpart1 * dec_dig; - int ibytes = ipart1 * 4 + dig_bytes[ipart2]; - int fbytes = fpart1 * 4 + dig_bytes[fpart2]; - ptr += ibytes + fbytes; - - // TODO: Add support for DECIMAL - if (!warn_decimal) - { - warn_decimal = true; - MXS_WARNING("DECIMAL is not currently supported, values are stored as 0."); - } - avro_value_set_int(&field, 0); + double f_value = 0.0; + ptr += unpack_decimal_field(ptr, metadata + metadata_offset, &f_value); + avro_value_set_double(&field, f_value); } else if (column_is_variable_string(map->column_types[i])) { diff --git a/server/modules/routing/avro/avro_schema.c b/server/modules/routing/avro/avro_schema.c index 9595e83b7..dd75e9d7d 100644 --- a/server/modules/routing/avro/avro_schema.c +++ b/server/modules/routing/avro/avro_schema.c @@ -44,7 +44,6 @@ static const char* column_type_to_avro_type(uint8_t type) { switch (type) { - case TABLE_COL_TYPE_NEWDECIMAL: case TABLE_COL_TYPE_TINY: case TABLE_COL_TYPE_SHORT: case TABLE_COL_TYPE_LONG: @@ -56,6 +55,7 @@ static const char* column_type_to_avro_type(uint8_t type) return "float"; case TABLE_COL_TYPE_DOUBLE: + case TABLE_COL_TYPE_NEWDECIMAL: return "double"; case TABLE_COL_TYPE_NULL: From 16ddefc686a0af5dd126bc9926ea29a99fa44c04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Sun, 18 Dec 2016 08:37:22 +0200 Subject: [PATCH 09/15] Remove DECIMAL from avrorouter limitations The router can now handle DECIMAL types. Backported to the 2.0 branch. --- Documentation/About/Limitations.md | 1 - 1 file changed, 1 deletion(-) diff --git a/Documentation/About/Limitations.md b/Documentation/About/Limitations.md index 860b3d81d..320d2eb9f 100644 --- a/Documentation/About/Limitations.md +++ b/Documentation/About/Limitations.md @@ -238,7 +238,6 @@ and routed. Here is a list of the current limitations. The avrorouter does not support the following data types and conversions. -* DECIMAL * BIT * Fields CAST from integer types to string types From 7a35ec71da4a05b30c8178500dbd3c8f57ba7434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 13 Feb 2017 09:30:30 +0200 Subject: [PATCH 10/15] Fix DECIMAL conversion The DECIMAL type was not correctly converted to positive integers. The 0x80 bit was set only for negative numbers when it needed to be XOR-ed for all values. --- server/core/mysql_binlog.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/server/core/mysql_binlog.c b/server/core/mysql_binlog.c index 0fa01d866..14e69f242 100644 --- a/server/core/mysql_binlog.c +++ b/server/core/mysql_binlog.c @@ -605,12 +605,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]; @@ -625,7 +624,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; From f7cc548093b037a573c3340f10db2284349b1c69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 13 Feb 2017 13:17:34 +0200 Subject: [PATCH 11/15] Format mysql_binlog.c The lines were too long. --- server/core/mysql_binlog.c | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/server/core/mysql_binlog.c b/server/core/mysql_binlog.c index 14e69f242..5e0539679 100644 --- a/server/core/mysql_binlog.c +++ b/server/core/mysql_binlog.c @@ -382,7 +382,7 @@ size_t unpack_enum(uint8_t *ptr, uint8_t *metadata, uint8_t *dest) * @return Length of the processed field in bytes */ size_t unpack_bit(uint8_t *ptr, uint8_t *null_mask, uint32_t col_count, - uint32_t curr_col_index, uint8_t *metadata, uint64_t *dest) + uint32_t curr_col_index, uint8_t *metadata, uint64_t *dest) { if (metadata[1]) { @@ -568,22 +568,34 @@ static uint64_t unpack_bytes(uint8_t *ptr, size_t bytes) 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); + 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); + 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); + 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); + 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); + 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); + 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; } From f54c0a5b81f1abfe314791920062bc861f32eaa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Sat, 18 Feb 2017 09:16:37 +0200 Subject: [PATCH 12/15] Initialize RNG in maxkeys/maxpasswd The initialization needs to be done in each of the executables before the random number generator is used. --- server/core/maxkeys.c | 2 ++ server/core/maxpasswd.c | 3 +++ 2 files changed, 5 insertions(+) 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) From e7c7caebad841a57ab469d4f8cc728e662ec7664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Sun, 19 Feb 2017 10:24:52 +0200 Subject: [PATCH 13/15] Add option for failover recovery in mysqlmon The `failover_recovery` option allows failed servers to rejoin the cluster. This should make using MaxScale with two node clusters easier. One use case for this is when the replication-manager promotes the last node in the cluster as the master. When this is done, the slave configuration is cleared and the read-only mode is disabled. Since the failover requires that the server is not configured as a slave and that it is not in read-only mode, it is safe to use `failover_recovery` with replication-manager. --- Documentation/Monitors/MySQL-Monitor.md | 15 +++++++++++++++ server/modules/monitor/mysqlmon.h | 1 + server/modules/monitor/mysqlmon/mysql_mon.c | 11 +++++++---- 3 files changed, 23 insertions(+), 4 deletions(-) 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/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); From aa412b4fe3cced32aaf392cb366f61ec06f11888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 20 Feb 2017 12:41:52 +0200 Subject: [PATCH 14/15] Fix merge conflict 2.0 used old define names. --- avro/maxavro_file.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/avro/maxavro_file.c b/avro/maxavro_file.c index 8e94da357..fc45d2046 100644 --- a/avro/maxavro_file.c +++ b/avro/maxavro_file.c @@ -12,7 +12,6 @@ */ #include "maxavro.h" -#include "skygw_utils.h" #include #include #include @@ -52,7 +51,7 @@ bool maxavro_verify_block(MAXAVRO_FILE *file) { if (ferror(file->file)) { - char err[STRERROR_BUFLEN]; + char err[MXS_STRERROR_BUFLEN]; MXS_ERROR("Failed to read file: %d %s", errno, strerror_r(errno, err, sizeof(err))); } else if (rc > 0 || !feof(file->file)) From 44661036d81612b4dea3461ba6d599c95d6187c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 20 Feb 2017 11:29:10 +0200 Subject: [PATCH 15/15] Add 2.1.1 release notes Added MaxScale 2.1.1 release notes. --- .../MaxScale-2.1.1-Release-Notes.md | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 Documentation/Release-Notes/MaxScale-2.1.1-Release-Notes.md 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).