From a7a22156810433c5f161efe2121e0a4f509a9548 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 7 May 2019 15:22:37 +0300 Subject: [PATCH 01/15] Update release notes and change log --- Documentation/Changelog.md | 1 + .../MaxScale-2.2.21-Release-Notes.md | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 Documentation/Release-Notes/MaxScale-2.2.21-Release-Notes.md diff --git a/Documentation/Changelog.md b/Documentation/Changelog.md index 79912a30a..bf90c3b53 100644 --- a/Documentation/Changelog.md +++ b/Documentation/Changelog.md @@ -28,6 +28,7 @@ the master. There is also limited capability for rejoining nodes. For more details, please refer to: +* [MariaDB MaxScale 2.2.21 Release Notes](Release-Notes/MaxScale-2.2.21-Release-Notes.md) * [MariaDB MaxScale 2.2.20 Release Notes](Release-Notes/MaxScale-2.2.20-Release-Notes.md) * [MariaDB MaxScale 2.2.19 Release Notes](Release-Notes/MaxScale-2.2.19-Release-Notes.md) * [MariaDB MaxScale 2.2.18 Release Notes](Release-Notes/MaxScale-2.2.18-Release-Notes.md) diff --git a/Documentation/Release-Notes/MaxScale-2.2.21-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.2.21-Release-Notes.md new file mode 100644 index 000000000..d2f54c46d --- /dev/null +++ b/Documentation/Release-Notes/MaxScale-2.2.21-Release-Notes.md @@ -0,0 +1,34 @@ +# MariaDB MaxScale 2.2.21 Release Notes + +Release 2.2.21 is a GA release. + +This document describes the changes in release 2.2.21, when compared to the +previous release in the same series. + +For any problems you encounter, please consider submitting a bug +report on [our Jira](https://jira.mariadb.org/projects/MXS). + +## Bug fixes + +* [MXS-2410](https://jira.mariadb.org/browse/MXS-2410) Hangup delivered to wrong DCB +* [MXS-2366](https://jira.mariadb.org/browse/MXS-2366) Wrong tarball RPATH + +## 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 supported the Linux distributions. + +Packages can be downloaded [here](https://mariadb.com/downloads/mariadb-tx/maxscale). + +## 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 `maxscale-X.Y.Z`. Further, the default branch is always the latest GA version +of MaxScale. + +The source code is available [here](https://github.com/mariadb-corporation/MaxScale). From 9ae25e67a8c129806da58e80df07e0a5e338859f Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 7 May 2019 15:27:48 +0300 Subject: [PATCH 02/15] Update maintenance number in 2.2 minor branch --- VERSION22.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION22.cmake b/VERSION22.cmake index 13684f7f5..84ea47e2e 100644 --- a/VERSION22.cmake +++ b/VERSION22.cmake @@ -5,7 +5,7 @@ set(MAXSCALE_VERSION_MAJOR "2" CACHE STRING "Major version") set(MAXSCALE_VERSION_MINOR "2" CACHE STRING "Minor version") -set(MAXSCALE_VERSION_PATCH "21" CACHE STRING "Patch version") +set(MAXSCALE_VERSION_PATCH "22" CACHE STRING "Patch version") # This should only be incremented if a package is rebuilt set(MAXSCALE_BUILD_NUMBER 1 CACHE STRING "Release number") From 3d66e68e9559784960c34de3de7dae77e232d559 Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 7 May 2019 10:08:39 +0300 Subject: [PATCH 03/15] MXS-2170 Start MaxScale normally if it gets the same PID as previous MaxScale Check is made to see if the found MaxScale PID is owned by the process itself. --- server/core/gateway.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/core/gateway.cc b/server/core/gateway.cc index 9eed4ae2d..a23fea6fc 100644 --- a/server/core/gateway.cc +++ b/server/core/gateway.cc @@ -2368,7 +2368,7 @@ bool pid_is_maxscale(int pid) if (file && std::getline(file, line)) { - if (line == "maxscale") + if (line == "maxscale" && pid != getpid()) { rval = true; } From 446788f2ed0deb2689400f4864123c4477451cb3 Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 7 May 2019 13:24:18 +0300 Subject: [PATCH 04/15] MXS-1799 Add timestamps to retain_last_statements messages --- server/core/internal/session.hh | 5 +++++ server/core/session.cc | 8 ++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/server/core/internal/session.hh b/server/core/internal/session.hh index 4985cfa84..527fcdfc4 100644 --- a/server/core/internal/session.hh +++ b/server/core/internal/session.hh @@ -136,6 +136,11 @@ public: return m_sQuery; } + timespec time_completed() const + { + return m_completed; + } + void book_server_response(SERVER* pServer, bool final_response); void book_as_complete(); void reset_server_bookkeeping(); diff --git a/server/core/session.cc b/server/core/session.cc index 07dfb2e2f..e618c8ee0 100644 --- a/server/core/session.cc +++ b/server/core/session.cc @@ -1209,6 +1209,10 @@ void Session::dump_statements() const { const QueryInfo& info = *i; GWBUF* pBuffer = info.query().get(); + timespec ts = info.time_completed(); + struct tm *tm = localtime(&ts.tv_sec); + char timestamp[20]; + strftime(timestamp, 20, "%Y-%m-%d %H:%M:%S", tm); const char* pCmd; char* pStmt; @@ -1219,14 +1223,14 @@ void Session::dump_statements() const { if (id != 0) { - MXS_NOTICE("Stmt %d: %.*s", n, len, pStmt); + MXS_NOTICE("Stmt %d(%s): %.*s", n, timestamp, len, pStmt); } else { // We are in a context where we do not have a current session, so we need to // log the session id ourselves. - MXS_NOTICE("(%" PRIu64 ") Stmt %d: %.*s", ses_id, n, len, pStmt); + MXS_NOTICE("(%" PRIu64 ") Stmt %d(%s): %.*s", ses_id, n, timestamp, len, pStmt); } if (deallocate) From 0638ea736e42811ad562c9ca1ddd9fe9e27caa2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 6 May 2019 15:17:42 +0300 Subject: [PATCH 05/15] Write slave heartbeat in correct thread The writing should be done on the worker that owns the DCB. --- server/modules/routing/binlogrouter/blr_slave.cc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server/modules/routing/binlogrouter/blr_slave.cc b/server/modules/routing/binlogrouter/blr_slave.cc index f0fa49940..ed77b0e38 100644 --- a/server/modules/routing/binlogrouter/blr_slave.cc +++ b/server/modules/routing/binlogrouter/blr_slave.cc @@ -47,6 +47,7 @@ #include #include #include +#include using std::string; using std::vector; @@ -6305,7 +6306,11 @@ static int blr_slave_send_heartbeat(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave } /* Write the packet */ - return MXS_SESSION_ROUTE_REPLY(slave->dcb->session, h_event); + mxs::RoutingWorker* worker = (mxs::RoutingWorker*)slave->dcb->poll.owner; + worker->execute([slave, h_event]() { + MXS_SESSION_ROUTE_REPLY(slave->dcb->session, h_event); + }, mxs::RoutingWorker::EXECUTE_AUTO); + return 1; } /** From c818b1208a4be5702d546cad89ee3ffb534601ed Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 7 May 2019 13:06:06 +0300 Subject: [PATCH 06/15] MXS-2455 Recognize transaction rollbacks All transaction rollback errors have an sql_state like "40XXX". So, when an error reply is received we check for that and act accordingly. --- .../routing/readwritesplit/rwsplitsession.cc | 88 +++++++------------ 1 file changed, 32 insertions(+), 56 deletions(-) diff --git a/server/modules/routing/readwritesplit/rwsplitsession.cc b/server/modules/routing/readwritesplit/rwsplitsession.cc index 27f560fe0..a88379d30 100644 --- a/server/modules/routing/readwritesplit/rwsplitsession.cc +++ b/server/modules/routing/readwritesplit/rwsplitsession.cc @@ -569,80 +569,56 @@ void RWSplitSession::close_stale_connections() namespace { -// TODO: It is not OK that knowledge about Clustrix is embedded into RWS. -// TODO: The capacity for recovery should be abstracted into SERVER, of -// TODO: which there then would be backend specific concrete specializations. - -const int CLUSTRIX_ERROR_CODE = 1; - -// NOTE: Keep these alphabetically ordered! -const char CLUSTRIX_ERROR_1[] = "[16389] Group change during GTM operation"; - -const struct ClustrixError +inline bool is_transaction_rollback(uint8_t* pData) { - const void* message; - int len; + bool rv = false; - bool operator == (const ClustrixError& rhs) const + // The 'sql_state' of all transaction rollbacks is "40XXX". In an error + // packet, the 'sql_state' is found in the payload at offset 4, after the one + // byte 'header', the two byte 'error_code', and the 1 byte 'sql_state_marker'. + uint8_t* p = pData + MYSQL_HEADER_LEN + 1 + 2 + 1; + + if (*p++ == '4' && *p == '0') { - return len == rhs.len && memcmp(message, rhs.message, len) == 0; - } + rv = true; - bool operator < (const ClustrixError& rhs) const - { - int rv = memcmp(message, rhs.message, std::min(len, rhs.len)); - - if (rv == 0) + if (mxb_log_is_priority_enabled(LOG_INFO)) { - rv = len - rhs.len; + // p now points at the second byte of the 5 byte long 'sql_state'. + p += 4; // Now at the start of the human readable error message + + uint8_t* end = pData + MYSQL_HEADER_LEN + MYSQL_GET_PAYLOAD_LEN(pData); + int len = end - p; + char message[len + 1]; + memcpy(message, p, len); + message[len] = 0; + + MXS_NOTICE("A retryable Clustrix error: %s", message); } - - return rv < 0 ? true : false; } -} clustrix_errors[] = -{ - { CLUSTRIX_ERROR_1, sizeof(CLUSTRIX_ERROR_1) - 1 } -}; -const int nClustrix_errors = sizeof(clustrix_errors) / sizeof(clustrix_errors[0]); + return rv; +} -bool is_manageable_clustrix_error(GWBUF* writebuf) +bool is_transaction_rollback(GWBUF* writebuf) { bool rv = false; if (MYSQL_IS_ERROR_PACKET(GWBUF_DATA(writebuf))) { - uint8_t* pData = GWBUF_DATA(writebuf); - uint8_t data[MYSQL_HEADER_LEN + MYSQL_GET_PAYLOAD_LEN(pData)]; - - if (!GWBUF_IS_CONTIGUOUS(writebuf)) + if (GWBUF_IS_CONTIGUOUS(writebuf)) { - gwbuf_copy_data(writebuf, 0, sizeof(data), data); - pData = data; + rv = is_transaction_rollback(GWBUF_DATA(writebuf)); } - - uint16_t code = MYSQL_GET_ERRCODE(pData); - - if (code == CLUSTRIX_ERROR_CODE) + else { - // May be a recoverable error. - uint8_t* pMessage; - uint16_t nMessage; - extract_error_message(pData, &pMessage, &nMessage); + uint8_t* pData = GWBUF_DATA(writebuf); + int len = MYSQL_HEADER_LEN + MYSQL_GET_PAYLOAD_LEN(pData); + uint8_t data[len]; - if (std::binary_search(clustrix_errors, clustrix_errors + nClustrix_errors, - ClustrixError { pMessage, nMessage })) - { - if (mxb_log_is_priority_enabled(LOG_INFO)) - { - char message[nMessage + 1]; - memcpy(message, pMessage, nMessage); - message[nMessage] = 0; + gwbuf_copy_data(writebuf, 0, len, data); - MXS_INFO("A recoverable Clustrix error: %s", message); - } - rv = true; - } + rv = is_transaction_rollback(data); } } @@ -689,7 +665,7 @@ void RWSplitSession::clientReply(GWBUF* writebuf, DCB* backend_dcb) backend->process_reply(writebuf); - if (m_config.transaction_replay && is_manageable_clustrix_error(writebuf)) + if (m_config.transaction_replay && is_transaction_rollback(writebuf)) { // writebuf was an error that can be handled by replaying the transaction. m_expected_responses--; From a652b6bd5bb04e4be43dfb5c68a0dea979f63ebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Wed, 8 May 2019 07:59:24 +0300 Subject: [PATCH 07/15] Add advance(int) to mxs::Buffer iterators This makes iterating over packets in buffers faster while still maintaining the requirements for forward iterators. Not using operator+= makes it clear that this is not a random access iterator. --- include/maxscale/buffer.hh | 36 ++++++++++++++++++++++ server/modules/protocol/MySQL/rwbackend.cc | 11 ++++--- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/include/maxscale/buffer.hh b/include/maxscale/buffer.hh index 914c54c7a..cfe987e68 100644 --- a/include/maxscale/buffer.hh +++ b/include/maxscale/buffer.hh @@ -78,6 +78,42 @@ public: return &m_i; } + /** + * Advance the iterator + * + * This provides similar behavior to random access iterators with operator+= but does it in + * non-constant time. + * + * @param i Number of steps to advance the iterator + */ + void advance(int i) + { + mxb_assert(m_i != m_end); + mxb_assert(i >= 0); + + while (m_i && m_i + i >= m_end) + { + i -= m_end - m_i; + m_pBuffer = m_pBuffer->next; + + if (m_pBuffer) + { + m_i = GWBUF_DATA(m_pBuffer); + m_end = m_i + GWBUF_LENGTH(m_pBuffer); + } + else + { + m_i = NULL; + m_end = NULL; + } + } + + if (m_i) + { + m_i += i; + } + } + protected: iterator_base(buf_type pBuffer = NULL) : m_pBuffer(pBuffer) diff --git a/server/modules/protocol/MySQL/rwbackend.cc b/server/modules/protocol/MySQL/rwbackend.cc index 208192e8c..2dd8a9d22 100644 --- a/server/modules/protocol/MySQL/rwbackend.cc +++ b/server/modules/protocol/MySQL/rwbackend.cc @@ -196,19 +196,19 @@ Iter skip_encoded_int(Iter it) switch (*it) { case 0xfc: - std::advance(it, 3); + it.advance(3); break; case 0xfd: - std::advance(it, 4); + it.advance(4); break; case 0xfe: - std::advance(it, 9); + it.advance(9); break; default: - std::advance(it, 1); + ++it; break; } @@ -293,7 +293,8 @@ void RWBackend::process_packets(GWBUF* result) len |= (*it++) << 16; ++it; // Skip the sequence mxb_assert(it != buffer.end()); - auto end = std::next(it, len); + auto end = it; + end.advance(len); uint8_t cmd = *it; // Ignore the tail end of a large packet large packet. Only resultsets can generate packets this large From e3b5ba9620c2856bd46defe4db5c2cffc947ad90 Mon Sep 17 00:00:00 2001 From: Esa Korhonen Date: Mon, 6 May 2019 18:44:23 +0300 Subject: [PATCH 08/15] MXS-1973 Support reverse DNS for client hostnames in MaxCtrl May slow maxscale down when used. Only supported for "list sessions", "show sessions" and "show session ". --- Documentation/REST-API/Resources-Session.md | 4 ++ include/maxscale/session.hh | 8 ++-- maxctrl/lib/common.js | 10 +++++ maxctrl/lib/list.js | 8 +++- maxctrl/lib/show.js | 16 ++++++- maxutils/maxbase/include/maxbase/host.hh | 10 +++++ maxutils/maxbase/src/host.cc | 50 +++++++++++++++++++++ server/core/resource.cc | 12 ++++- server/core/session.cc | 37 +++++++++++---- 9 files changed, 137 insertions(+), 18 deletions(-) diff --git a/Documentation/REST-API/Resources-Session.md b/Documentation/REST-API/Resources-Session.md index fc94afc03..67bd7ce52 100644 --- a/Documentation/REST-API/Resources-Session.md +++ b/Documentation/REST-API/Resources-Session.md @@ -15,6 +15,10 @@ GET /v1/sessions/:id Get a single session. _:id_ must be a valid session ID. The session ID is the same that is exposed to the client as the connection ID. +This endpoint also supports the `rdns=true` parameter, which instructs MaxScale to +perform reverse DNS on the client IP address. As this requires communicating with +an external server, the operation may be expensive. + #### Response `Status: 200 OK` diff --git a/include/maxscale/session.hh b/include/maxscale/session.hh index 230368d18..6f40c8124 100644 --- a/include/maxscale/session.hh +++ b/include/maxscale/session.hh @@ -438,19 +438,19 @@ void session_put_ref(MXS_SESSION* session); * * @param session Session to convert * @param host Hostname of this server - * + * @param rdns Attempt reverse DNS on client ip address * @return New JSON object or NULL on error */ -json_t* session_to_json(const MXS_SESSION* session, const char* host); +json_t* session_to_json(const MXS_SESSION* session, const char* host, bool rdns); /** * @brief Convert all sessions to JSON * * @param host Hostname of this server - * + * @param rdns Attempt reverse DNS on client ip addresses * @return A JSON array with all sessions */ -json_t* session_list_to_json(const char* host); +json_t* session_list_to_json(const char* host, bool rdns); /** * Qualify the session for connection pooling diff --git a/maxctrl/lib/common.js b/maxctrl/lib/common.js index b3555c8f1..2cd2ec912 100644 --- a/maxctrl/lib/common.js +++ b/maxctrl/lib/common.js @@ -409,6 +409,16 @@ module.exports = function() { this.error = function(err) { return Promise.reject(colors.red('Error: ') + err) } + + this.rDnsOption = { + shortname: 'rdns', + optionOn: 'rdns=true', + definition : { + describe: 'Reverse DNS on client IP. May slow MaxScale down.', + type: 'bool', + default: false + } + } } diff --git a/maxctrl/lib/list.js b/maxctrl/lib/list.js index f7a18594c..e909364b5 100644 --- a/maxctrl/lib/list.js +++ b/maxctrl/lib/list.js @@ -115,9 +115,15 @@ exports.builder = function(yargs) { .command('sessions', 'List sessions', function(yargs) { return yargs.epilog('List all client sessions.') .usage('Usage: list sessions') + .group([rDnsOption.shortname], 'Options:') + .option(rDnsOption.shortname, rDnsOption.definition) }, function(argv) { maxctrl(argv, function(host) { - return getCollection(host, 'sessions',[ + var resource = 'sessions' + if (argv[this.rDnsOption.shortname]) { + resource += '?' + this.rDnsOption.optionOn + } + return getCollection(host, resource,[ {'Id': 'id'}, {'User': 'attributes.user'}, {'Host': 'attributes.remote'}, diff --git a/maxctrl/lib/show.js b/maxctrl/lib/show.js index 45598d849..359a701dc 100644 --- a/maxctrl/lib/show.js +++ b/maxctrl/lib/show.js @@ -174,18 +174,30 @@ exports.builder = function(yargs) { 'the session is connected and the `Connection IDs` ' + 'field lists the IDs for those connections.') .usage('Usage: show session ') + .group([rDnsOption.shortname], 'Options:') + .option(rDnsOption.shortname, rDnsOption.definition) }, function(argv) { maxctrl(argv, function(host) { - return getResource(host, 'sessions/' + argv.session, session_fields) + var resource = 'sessions/' + argv.session + if (argv[this.rDnsOption.shortname]) { + resource += '?' + this.rDnsOption.optionOn + } + return getResource(host, resource, session_fields) }) }) .command('sessions', 'Show all sessions', function(yargs) { return yargs.epilog('Show detailed information about all sessions. ' + 'See `help show session` for more details.') .usage('Usage: show sessions') + .group([rDnsOption.shortname], 'Options:') + .option(rDnsOption.shortname, rDnsOption.definition) }, function(argv) { maxctrl(argv, function(host) { - return getCollectionAsResource(host, 'sessions/', session_fields) + var resource = 'sessions/' + if (argv[this.rDnsOption.shortname]) { + resource += '?' + this.rDnsOption.optionOn + } + return getCollectionAsResource(host, resource, session_fields) }) }) .command('filter ', 'Show filter', function(yargs) { diff --git a/maxutils/maxbase/include/maxbase/host.hh b/maxutils/maxbase/include/maxbase/host.hh index 90bb27744..9c5d1406d 100644 --- a/maxutils/maxbase/include/maxbase/host.hh +++ b/maxutils/maxbase/include/maxbase/host.hh @@ -108,4 +108,14 @@ inline bool operator!=(const Host& l, const Host& r) { return !(l == r); } + +/** + * Perform reverse DNS on an IP address. This may involve network communication so can be slow. + * + * @param ip IP to convert to hostname + * @param output Where to write the output. If operation fails, original IP is written. + * @return True on success + */ +bool reverse_dns(const std::string& ip, std::string* output); + } diff --git a/maxutils/maxbase/src/host.cc b/maxutils/maxbase/src/host.cc index 3cebbbd9c..143947bcd 100644 --- a/maxutils/maxbase/src/host.cc +++ b/maxutils/maxbase/src/host.cc @@ -16,6 +16,8 @@ #include #include #include +#include +#include namespace { @@ -222,4 +224,52 @@ std::istream& operator>>(std::istream& is, Host& host) host = Host(input); return is; } + +bool reverse_dns(const std::string& ip, std::string* output) +{ + sockaddr_storage socket_address; + memset(&socket_address, 0, sizeof(socket_address)); + socklen_t slen = 0; + + if (is_valid_ipv4(ip)) + { + // Casts between the different sockaddr-types should work. + int family = AF_INET; + auto sa_in = reinterpret_cast(&socket_address); + if (inet_pton(family, ip.c_str(), &sa_in->sin_addr) == 1) + { + sa_in->sin_family = family; + slen = sizeof(sockaddr_in); + } + } + else if (is_valid_ipv6(ip)) + { + int family = AF_INET6; + auto sa_in6 = reinterpret_cast(&socket_address); + if (inet_pton(family, ip.c_str(), &sa_in6->sin6_addr) == 1) + { + sa_in6->sin6_family = family; + slen = sizeof(sockaddr_in6); + } + } + + bool success = false; + if (slen > 0) + { + char host[NI_MAXHOST]; + auto sa = reinterpret_cast(&socket_address); + if (getnameinfo(sa, slen, host, sizeof(host), nullptr, 0, NI_NAMEREQD) == 0) + { + *output = host; + success = true; + } + } + + if (!success) + { + *output = ip; + } + return success; +} + } diff --git a/server/core/resource.cc b/server/core/resource.cc index ca67a4d9c..988f447c1 100644 --- a/server/core/resource.cc +++ b/server/core/resource.cc @@ -178,6 +178,12 @@ bool Resource::requires_body() const namespace { +bool option_rdns_is_on(const HttpRequest& request) +{ + return request.get_option("rdns") == "true"; +} + + static bool drop_path_part(std::string& path) { size_t pos = path.find_last_of('/'); @@ -622,7 +628,8 @@ HttpResponse cb_get_monitor(const HttpRequest& request) HttpResponse cb_all_sessions(const HttpRequest& request) { - return HttpResponse(MHD_HTTP_OK, session_list_to_json(request.host())); + bool rdns = option_rdns_is_on(request); + return HttpResponse(MHD_HTTP_OK, session_list_to_json(request.host(), rdns)); } HttpResponse cb_get_session(const HttpRequest& request) @@ -632,7 +639,8 @@ HttpResponse cb_get_session(const HttpRequest& request) if (session) { - json_t* json = session_to_json(session, request.host()); + bool rdns = option_rdns_is_on(request); + json_t* json = session_to_json(session, request.host(), rdns); session_put_ref(session); return HttpResponse(MHD_HTTP_OK, json); } diff --git a/server/core/session.cc b/server/core/session.cc index e618c8ee0..5740e6bd1 100644 --- a/server/core/session.cc +++ b/server/core/session.cc @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -714,7 +715,7 @@ uint64_t session_get_next_id() return mxb::atomic::add(&this_unit.next_session_id, 1, mxb::atomic::RELAXED); } -json_t* session_json_data(const Session* session, const char* host) +json_t* session_json_data(const Session* session, const char* host, bool rdns) { json_t* data = json_object(); @@ -761,7 +762,17 @@ json_t* session_json_data(const Session* session, const char* host) if (session->client_dcb->remote) { - json_object_set_new(attr, "remote", json_string(session->client_dcb->remote)); + string result_address; + auto remote = session->client_dcb->remote; + if (rdns) + { + maxbase::reverse_dns(remote, &result_address); + } + else + { + result_address = remote; + } + json_object_set_new(attr, "remote", json_string(result_address.c_str())); } struct tm result; @@ -798,18 +809,26 @@ json_t* session_json_data(const Session* session, const char* host) return data; } -json_t* session_to_json(const MXS_SESSION* session, const char* host) +json_t* session_to_json(const MXS_SESSION* session, const char* host, bool rdns) { stringstream ss; ss << MXS_JSON_API_SESSIONS << session->ses_id; const Session* s = static_cast(session); - return mxs_json_resource(host, ss.str().c_str(), session_json_data(s, host)); + return mxs_json_resource(host, ss.str().c_str(), session_json_data(s, host, rdns)); } struct SessionListData { - json_t* json; - const char* host; + SessionListData(const char* host, bool rdns) + : json(json_array()) + , host(host) + , rdns(rdns) + { + } + + json_t* json {nullptr}; + const char* host {nullptr}; + bool rdns {false}; }; bool seslist_cb(DCB* dcb, void* data) @@ -818,15 +837,15 @@ bool seslist_cb(DCB* dcb, void* data) { SessionListData* d = (SessionListData*)data; Session* session = static_cast(dcb->session); - json_array_append_new(d->json, session_json_data(session, d->host)); + json_array_append_new(d->json, session_json_data(session, d->host, d->rdns)); } return true; } -json_t* session_list_to_json(const char* host) +json_t* session_list_to_json(const char* host, bool rdns) { - SessionListData data = {json_array(), host}; + SessionListData data(host, rdns); dcb_foreach(seslist_cb, &data); return mxs_json_resource(host, MXS_JSON_API_SESSIONS, data.json); } From 1df6495d9ab7f9bdc3fbe351fe2708ee04bb846a Mon Sep 17 00:00:00 2001 From: Esa Korhonen Date: Wed, 8 May 2019 15:29:00 +0300 Subject: [PATCH 09/15] Update release notes on MariaDB-Monitor change --- .../Release-Notes/MaxScale-2.2.21-Release-Notes.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Documentation/Release-Notes/MaxScale-2.2.21-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.2.21-Release-Notes.md index d2f54c46d..7d0c2228d 100644 --- a/Documentation/Release-Notes/MaxScale-2.2.21-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-2.2.21-Release-Notes.md @@ -13,6 +13,13 @@ report on [our Jira](https://jira.mariadb.org/projects/MXS). * [MXS-2410](https://jira.mariadb.org/browse/MXS-2410) Hangup delivered to wrong DCB * [MXS-2366](https://jira.mariadb.org/browse/MXS-2366) Wrong tarball RPATH +## Changes to MariaDB-Monitor failover + +Failover is no longer disabled permanently if it or any other cluster operation fails. +The disabling is now only temporary and lasts for 'failcount' monitor iterations. Check +[MariaDB-Monitor documentation](../Monitors/MariaDB-Monitor.md#limitations-and-requirements) +for more information. + ## Known Issues and Limitations There are some limitations and known issues within this version of MaxScale. From 73f8bf1f6f864c70641fa4c6d6c2d1a8ee3d6de5 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Wed, 8 May 2019 15:49:11 +0300 Subject: [PATCH 10/15] Update 2.2.21 release date --- Documentation/Release-Notes/MaxScale-2.2.21-Release-Notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Release-Notes/MaxScale-2.2.21-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.2.21-Release-Notes.md index 7d0c2228d..6a074ea88 100644 --- a/Documentation/Release-Notes/MaxScale-2.2.21-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-2.2.21-Release-Notes.md @@ -1,4 +1,4 @@ -# MariaDB MaxScale 2.2.21 Release Notes +# MariaDB MaxScale 2.2.21 Release Notes -- 2019-05-08 Release 2.2.21 is a GA release. From b313c6d0e750c556676147bc35b357350ea42422 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 7 May 2019 16:04:34 +0300 Subject: [PATCH 11/15] MXS-2474 Ignore attempts to re-register a housekeeper task It is an error to register the same task multiple times, but for a maintenance release it is simpler and less risky to simply ignore an attempt (that BLR does) to do that. Allowing a task to be registered anew causes behaviour akin to a leak. --- server/core/housekeeper.cc | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/server/core/housekeeper.cc b/server/core/housekeeper.cc index 665a51ceb..aeb544825 100644 --- a/server/core/housekeeper.cc +++ b/server/core/housekeeper.cc @@ -12,6 +12,7 @@ */ #include +#include #include #include #include @@ -205,7 +206,29 @@ void Housekeeper::stop() void Housekeeper::add(const Task& task) { std::lock_guard guard(m_lock); - m_tasks.push_back(task); + + auto i = std::find_if(m_tasks.begin(), m_tasks.end(), Task::NameMatch(task.name)); + if (i == m_tasks.end()) + { + m_tasks.push_back(task); + } + else + { + const Task& existing = *i; + + bool identical = false; + if (task.func == existing.func + && task.data == existing.data + && task.frequency == existing.frequency) + { + identical = true; + } + + MXS_INFO("Housekeeper task `%s` added anew, all settings %s identical. " + "Second attempt to add is ignored.", + identical ? "ARE" : "are NOT", + task.name.c_str()); + } } void Housekeeper::remove(std::string name) From 788dc429f82fd4be296e86a293cd2a9f3f98e7c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Thu, 9 May 2019 10:05:40 +0300 Subject: [PATCH 12/15] Do client callback on owning worker The callback should've been done on the worker that owns the DCB instead of the main worker. --- server/modules/routing/avrorouter/avro_client.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/modules/routing/avrorouter/avro_client.cc b/server/modules/routing/avrorouter/avro_client.cc index 566b7b571..1ff7a2515 100644 --- a/server/modules/routing/avrorouter/avro_client.cc +++ b/server/modules/routing/avrorouter/avro_client.cc @@ -244,7 +244,7 @@ bool file_in_dir(const char* dir, const char* file) */ void AvroSession::queue_client_callback() { - auto worker = mxs::RoutingWorker::get(mxs::RoutingWorker::MAIN); + auto worker = static_cast(dcb->poll.owner); worker->execute([this]() { client_callback(); }, mxs::RoutingWorker::EXECUTE_QUEUED); From 59f2145c00cdfa905e854a14d42d2fcef610a34e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Thu, 9 May 2019 10:13:43 +0300 Subject: [PATCH 13/15] Allocate blr heartbeat buffer on correct worker The buffer was allocated on one worker and written on another. --- .../modules/routing/binlogrouter/blr_slave.cc | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/server/modules/routing/binlogrouter/blr_slave.cc b/server/modules/routing/binlogrouter/blr_slave.cc index ed77b0e38..8a008184d 100644 --- a/server/modules/routing/binlogrouter/blr_slave.cc +++ b/server/modules/routing/binlogrouter/blr_slave.cc @@ -243,7 +243,7 @@ static int blr_slave_send_columndef_with_status_schema(ROUTER_INSTANCE* router, int len, uint8_t seqno); static bool blr_send_slave_heartbeat(void* inst); -static int blr_slave_send_heartbeat(ROUTER_INSTANCE* router, +static void blr_slave_send_heartbeat(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave); static int blr_set_master_ssl(ROUTER_INSTANCE* router, const ChangeMasterConfig& config, @@ -6204,13 +6204,11 @@ static bool blr_send_slave_heartbeat(void* inst) sptr->heartbeat, (unsigned long)sptr->lastReply); - if (blr_slave_send_heartbeat(router, sptr)) - { - /* Set last event */ - sptr->lastEventReceived = HEARTBEAT_EVENT; - /* Set last time */ - sptr->lastReply = t_now; - } + blr_slave_send_heartbeat(router, sptr); + /* Set last event */ + sptr->lastEventReceived = HEARTBEAT_EVENT; + /* Set last time */ + sptr->lastReply = t_now; } sptr = sptr->next; @@ -6228,7 +6226,7 @@ static bool blr_send_slave_heartbeat(void* inst) * @param slave The current slave connection * @return Number of bytes sent or 0 in case of failure */ -static int blr_slave_send_heartbeat(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave) +static void send_heartbeat(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave) { REP_HEADER hdr; GWBUF* h_event; @@ -6255,10 +6253,7 @@ static int blr_slave_send_heartbeat(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave * * Total = 5 bytes + len */ - if ((h_event = gwbuf_alloc(MYSQL_HEADER_LEN + 1 + len)) == NULL) - { - return 0; - } + h_event = gwbuf_alloc(MYSQL_HEADER_LEN + 1 + len); /* The OK/Err byte is part of payload */ hdr.payload_len = len + 1; @@ -6306,11 +6301,15 @@ static int blr_slave_send_heartbeat(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave } /* Write the packet */ + MXS_SESSION_ROUTE_REPLY(slave->dcb->session, h_event); +} + +static void blr_slave_send_heartbeat(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave) +{ mxs::RoutingWorker* worker = (mxs::RoutingWorker*)slave->dcb->poll.owner; - worker->execute([slave, h_event]() { - MXS_SESSION_ROUTE_REPLY(slave->dcb->session, h_event); + worker->execute([router, slave]() { + send_heartbeat(router, slave); }, mxs::RoutingWorker::EXECUTE_AUTO); - return 1; } /** From 567ad9b8b846743936660096c620fa3a686e0775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Thu, 9 May 2019 12:05:22 +0300 Subject: [PATCH 14/15] Fix galeramon regression The comparisons were wrong: strcasecmp returns 0 for equal strings. --- server/modules/monitor/galeramon/galeramon.cc | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/server/modules/monitor/galeramon/galeramon.cc b/server/modules/monitor/galeramon/galeramon.cc index f84ef38b6..3cff962de 100644 --- a/server/modules/monitor/galeramon/galeramon.cc +++ b/server/modules/monitor/galeramon/galeramon.cc @@ -215,37 +215,41 @@ void GaleraMonitor::update_server_status(MXS_MONITORED_SERVER* monitored_server) /* Node is in desync - lets take it offline */ if (strcmp(row[0], "wsrep_desync") == 0) { - if (strcasecmp(row[1],"YES") || strcasecmp(row[1],"ON") || strcasecmp(row[1],"1") || strcasecmp(row[1],"true")) - { - info.joined = 0; - } + if (strcasecmp(row[1], "YES") == 0 || strcasecmp(row[1], "ON") == 0 + || strcasecmp(row[1], "1") == 0 || strcasecmp(row[1], "true") == 0) + { + info.joined = 0; + } } /* Node rejects queries - lets take it offline */ if (strcmp(row[0], "wsrep_reject_queries") == 0) { - if (strcasecmp(row[1],"ALL") || strcasecmp(row[1],"ALL_KILL")) - { - info.joined = 0; - } + if (strcasecmp(row[1], "ALL") == 0 + || strcasecmp(row[1], "ALL_KILL") == 0) + { + info.joined = 0; + } } /* Node rejects queries - lets take it offline */ if (strcmp(row[0], "wsrep_sst_donor_rejects_queries") == 0) { - if (strcasecmp(row[1],"YES") || strcasecmp(row[1],"ON") || strcasecmp(row[1],"1") || strcasecmp(row[1],"true")) - { - info.joined = 0; - } + if (strcasecmp(row[1], "YES") == 0 || strcasecmp(row[1], "ON") == 0 + || strcasecmp(row[1], "1") == 0 || strcasecmp(row[1], "true") == 0) + { + info.joined = 0; + } } /* Node is not ready - lets take it offline */ if (strcmp(row[0], "wsrep_ready") == 0) { - if (strcasecmp(row[1],"NO") || strcasecmp(row[1],"OFF") || strcasecmp(row[1],"0") || strcasecmp(row[1],"false")) - { - info.joined = 0; - } + if (strcasecmp(row[1], "NO") == 0 || strcasecmp(row[1], "OFF") == 0 + || strcasecmp(row[1], "0") == 0 || strcasecmp(row[1], "false") == 0) + { + info.joined = 0; + } } if (strcmp(row[0], "wsrep_cluster_state_uuid") == 0 && row[1] && *row[1]) From 23a09a62941823d1a065f505b4d147b1815b48dd Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Wed, 8 May 2019 11:27:58 +0300 Subject: [PATCH 15/15] MXS-2455 Use mxb::Buffer::iterator Simplifies the code and as extra allocations etc. are only made when info is enabled, and can thus be ignored. --- .../routing/readwritesplit/rwsplitsession.cc | 65 +++++++------------ 1 file changed, 23 insertions(+), 42 deletions(-) diff --git a/server/modules/routing/readwritesplit/rwsplitsession.cc b/server/modules/routing/readwritesplit/rwsplitsession.cc index a88379d30..de0f03c91 100644 --- a/server/modules/routing/readwritesplit/rwsplitsession.cc +++ b/server/modules/routing/readwritesplit/rwsplitsession.cc @@ -13,7 +13,9 @@ #include "rwsplitsession.hh" +#include #include +#include #include #include @@ -569,57 +571,36 @@ void RWSplitSession::close_stale_connections() namespace { -inline bool is_transaction_rollback(uint8_t* pData) -{ - bool rv = false; - - // The 'sql_state' of all transaction rollbacks is "40XXX". In an error - // packet, the 'sql_state' is found in the payload at offset 4, after the one - // byte 'header', the two byte 'error_code', and the 1 byte 'sql_state_marker'. - uint8_t* p = pData + MYSQL_HEADER_LEN + 1 + 2 + 1; - - if (*p++ == '4' && *p == '0') - { - rv = true; - - if (mxb_log_is_priority_enabled(LOG_INFO)) - { - // p now points at the second byte of the 5 byte long 'sql_state'. - p += 4; // Now at the start of the human readable error message - - uint8_t* end = pData + MYSQL_HEADER_LEN + MYSQL_GET_PAYLOAD_LEN(pData); - int len = end - p; - char message[len + 1]; - memcpy(message, p, len); - message[len] = 0; - - MXS_NOTICE("A retryable Clustrix error: %s", message); - } - } - - return rv; -} - bool is_transaction_rollback(GWBUF* writebuf) { bool rv = false; if (MYSQL_IS_ERROR_PACKET(GWBUF_DATA(writebuf))) { - if (GWBUF_IS_CONTIGUOUS(writebuf)) - { - rv = is_transaction_rollback(GWBUF_DATA(writebuf)); - } - else - { - uint8_t* pData = GWBUF_DATA(writebuf); - int len = MYSQL_HEADER_LEN + MYSQL_GET_PAYLOAD_LEN(pData); - uint8_t data[len]; + mxs::Buffer buffer(writebuf); + auto it = buffer.begin(); + it.advance(MYSQL_HEADER_LEN + 1 + 2 + 1); - gwbuf_copy_data(writebuf, 0, len, data); + if (*it++ == '4' && *it == '0') + { + rv = true; - rv = is_transaction_rollback(data); + if (mxb_log_is_priority_enabled(LOG_INFO)) + { + // it now points at the second byte of the 5 byte long 'sql_state'. + it.advance(4); // And now at the start of the human readable error message. + + auto end = buffer.begin(); + end.advance(MYSQL_HEADER_LEN + MYSQL_GET_PAYLOAD_LEN(GWBUF_DATA(writebuf))); + mxb_assert(end == buffer.end()); + + std::string message(it, end); + + MXS_INFO("Transaction rollback, transaction can be retried: %s", message.c_str()); + } } + + buffer.release(); } return rv;