diff --git a/Documentation/Changelog.md b/Documentation/Changelog.md index 4a5d6f8a2..9ad2c4348 100644 --- a/Documentation/Changelog.md +++ b/Documentation/Changelog.md @@ -21,6 +21,7 @@ * MaxScale now supports IPv6 For more details, please refer to: +* [MariaDB MaxScale 2.1.9 Release Notes](Release-Notes/MaxScale-2.1.9-Release-Notes.md) * [MariaDB MaxScale 2.1.8 Release Notes](Release-Notes/MaxScale-2.1.8-Release-Notes.md) * [MariaDB MaxScale 2.1.7 Release Notes](Release-Notes/MaxScale-2.1.7-Release-Notes.md) * [MariaDB MaxScale 2.1.6 Release Notes](Release-Notes/MaxScale-2.1.6-Release-Notes.md) diff --git a/Documentation/Release-Notes/MaxScale-2.1.9-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.1.9-Release-Notes.md new file mode 100644 index 000000000..d3d9bdf85 --- /dev/null +++ b/Documentation/Release-Notes/MaxScale-2.1.9-Release-Notes.md @@ -0,0 +1,45 @@ +# MariaDB MaxScale 2.1.9 Release Notes + +Release 2.1.9 is a GA release. + +This document describes the changes in release 2.1.9, when compared to release +[2.1.8](MaxScale-2.1.8-Release-Notes.md). + +If you are upgrading from release 2.0, please also read the following release +notes: + +* [2.1.8](./MaxScale-2.1.8-Release-Notes.md) +* [2.1.7](./MaxScale-2.1.7-Release-Notes.md) +* [2.1.6](./MaxScale-2.1.6-Release-Notes.md) +* [2.1.5](./MaxScale-2.1.5-Release-Notes.md) +* [2.1.4](./MaxScale-2.1.4-Release-Notes.md) +* [2.1.3](./MaxScale-2.1.3-Release-Notes.md) +* [2.1.2](./MaxScale-2.1.2-Release-Notes.md) +* [2.1.1](./MaxScale-2.1.1-Release-Notes.md) +* [2.1.0](./MaxScale-2.1.0-Release-Notes.md) + +For any problems you encounter, please consider submitting a bug report at +[Jira](https://jira.mariadb.org). + +## Bug fixes + +[Here is a list of bugs fixed in MaxScale 2.1.9.](https://jira.mariadb.org/issues/?jql=project%20%3D%20MXS%20AND%20issuetype%20%3D%20Bug%20AND%20status%20%3D%20Closed%20AND%20fixVersion%20%3D%202.1.9) + +* [MXS-1435](https://jira.mariadb.org/browse/MXS-1435) Persistent connections can hang on COM_QUIT +* [MXS-1377](https://jira.mariadb.org/browse/MXS-1377) maxscale doesn't cleanup pid file on startup error +* [MXS-1295](https://jira.mariadb.org/browse/MXS-1295) MaxScale's readwritesplit router does not take into account the fact that stored procedure call may change the value of a user variable + +## 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 maxscale-X.Y.Z. + +The source code is available [here](https://github.com/mariadb-corporation/MaxScale). diff --git a/Documentation/Routers/ReadWriteSplit.md b/Documentation/Routers/ReadWriteSplit.md index f4c22b4dd..7b07e3558 100644 --- a/Documentation/Routers/ReadWriteSplit.md +++ b/Documentation/Routers/ReadWriteSplit.md @@ -237,6 +237,15 @@ multi-statement queries. router_options=strict_multi_stmt=false ``` +### `strict_sp_calls` + +Similar to `strict_multi_stmt`, this option allows all queries after a CALL +operation on a stored procedure to be routed to the master. This option is +disabled by default and was added in MaxScale 2.1.9. + +All warnings and restrictions that apply to `strict_multi_stmt` also apply to +`strict_sp_calls`. + ### `master_failure_mode` This option controls how the failure of a master server is handled. By default, diff --git a/Documentation/Upgrading/Upgrading-To-MaxScale-2.1.md b/Documentation/Upgrading/Upgrading-To-MaxScale-2.1.md index 8bdbeedb3..a8095ec1f 100644 --- a/Documentation/Upgrading/Upgrading-To-MaxScale-2.1.md +++ b/Documentation/Upgrading/Upgrading-To-MaxScale-2.1.md @@ -7,6 +7,7 @@ For more information about MariaDB MaxScale 2.1, please refer to the [ChangeLog](../Changelog.md). For a complete list of changes in MaxScale 2.1, refer to the +[MaxScale 2.1.9 Release Notes](../Release-Notes/MaxScale-2.1.9-Release-Notes.md). [MaxScale 2.1.8 Release Notes](../Release-Notes/MaxScale-2.1.8-Release-Notes.md). [MaxScale 2.1.7 Release Notes](../Release-Notes/MaxScale-2.1.7-Release-Notes.md). [MaxScale 2.1.6 Release Notes](../Release-Notes/MaxScale-2.1.6-Release-Notes.md). diff --git a/Documentation/list_fixed_bugs.sh b/Documentation/list_fixed_bugs.sh index 8be27538f..6c8ed1b73 100755 --- a/Documentation/list_fixed_bugs.sh +++ b/Documentation/list_fixed_bugs.sh @@ -7,5 +7,8 @@ then exit 1 fi +# Script location +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + version=$1 -curl -s "https://jira.mariadb.org/sr/jira.issueviews:searchrequest-csv-current-fields/temp/SearchRequest.csv?jqlQuery=project+%3D+MXS+AND+issuetype+%3D+Bug+AND+status+%3D+Closed+AND+fixVersion+%3D+$version" | process.pl +curl -s "https://jira.mariadb.org/sr/jira.issueviews:searchrequest-csv-current-fields/temp/SearchRequest.csv?jqlQuery=project+%3D+MXS+AND+issuetype+%3D+Bug+AND+status+%3D+Closed+AND+fixVersion+%3D+$version" | $DIR/process.pl diff --git a/Documentation/list_fixed_issues.sh b/Documentation/list_fixed_issues.sh index 19707a3c4..9cab8dd8d 100755 --- a/Documentation/list_fixed_issues.sh +++ b/Documentation/list_fixed_issues.sh @@ -7,5 +7,8 @@ then exit 1 fi +# Script location +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + version=$1 -curl -s "https://jira.mariadb.org/sr/jira.issueviews:searchrequest-csv-current-fields/temp/SearchRequest.csv?jqlQuery=project+%3D+MXS+AND+issuetype+%21%3D+Bug+AND+status+%3D+Closed+AND+fixVersion+%3D+$version"|process.pl +curl -s "https://jira.mariadb.org/sr/jira.issueviews:searchrequest-csv-current-fields/temp/SearchRequest.csv?jqlQuery=project+%3D+MXS+AND+issuetype+%21%3D+Bug+AND+status+%3D+Closed+AND+fixVersion+%3D+$version" | $DIR/process.pl diff --git a/Documentation/process.pl b/Documentation/process.pl index f325b96de..fd2f4b3db 100755 --- a/Documentation/process.pl +++ b/Documentation/process.pl @@ -1,4 +1,4 @@ -#!/bin/perl +#!/usr/bin/env perl # Discard the CSV headers <>; @@ -16,5 +16,5 @@ while (<>) my $issue = @parts[1]; my $desc = @parts[0]; - print "* ($issue)[https://jira.mariadb.org/browse/$issue] $desc\n"; + print "* [$issue](https://jira.mariadb.org/browse/$issue) $desc\n"; } diff --git a/maxscale-system-test/CMakeLists.txt b/maxscale-system-test/CMakeLists.txt index d2affad7c..914994d4a 100644 --- a/maxscale-system-test/CMakeLists.txt +++ b/maxscale-system-test/CMakeLists.txt @@ -484,6 +484,12 @@ add_test_executable(mxs1319.cpp mxs1319 replication LABELS MySQLAuth REPL_BACKEN # https://jira.mariadb.org/browse/MXS-1418 add_test_executable(mxs1418.cpp mxs1418 replication LABELS maxscale REPL_BACKEND) +# MXS-1295: MaxScale's readwritesplit router does not take into account the fact +# that stored procedure call may change the value of a user variable +# +# https://jira.mariadb.org/browse/MXS-1295 +add_test_executable(mxs1295_sp_call.cpp mxs1295_sp_call mxs1295 LABELS maxscale REPL_BACKEND) + # 'namedserverfilter' test add_test_executable(namedserverfilter.cpp namedserverfilter namedserverfilter LABELS namedserverfilter LIGHT REPL_BACKEND) diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mxs1295 b/maxscale-system-test/cnf/maxscale.cnf.template.mxs1295 new file mode 100644 index 000000000..b5cb77c4e --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mxs1295 @@ -0,0 +1,48 @@ +[maxscale] +threads=###threads### + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1,server2 +user=maxskysql +passwd=skysql +monitor_interval=1000 + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2 +user=maxskysql +passwd=skysql +strict_multi_stmt=false +strict_sp_calls=true + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend diff --git a/maxscale-system-test/mxs1295_sp_call.cpp b/maxscale-system-test/mxs1295_sp_call.cpp new file mode 100644 index 000000000..ae4292cf4 --- /dev/null +++ b/maxscale-system-test/mxs1295_sp_call.cpp @@ -0,0 +1,44 @@ +/** + * Test for MXS-1295: https://jira.mariadb.org/browse/MXS-1295 + */ +#include "testconnections.h" + +const char sp_sql[] = + "DROP PROCEDURE IF EXISTS multi;" + "CREATE PROCEDURE multi()" + "BEGIN" + " SELECT @@server_id;" + "END"; + +int get_server_id(MYSQL* conn) +{ + char value[200] = ""; + find_field(conn, "SELECT @@server_id", "@@server_id", value); + return atoi(value); +} + +int main(int argc, char *argv[]) +{ + TestConnections test(argc, argv); + + test.connect_maxscale(); + test.repl->connect(); + + test.tprintf("Create the stored procedure and check that it works"); + test.try_query(test.repl->nodes[0], sp_sql); + test.try_query(test.repl->nodes[0], "CALL multi()"); + + test.tprintf("Check that queries after a CALL command get routed to the master"); + + int master = get_server_id(test.repl->nodes[0]); + int slave = get_server_id(test.repl->nodes[1]); + int result = get_server_id(test.conn_rwsplit); + + test.add_result(result != slave, "The query should be routed to a slave(%d): %d", slave, result); + test.try_query(test.conn_rwsplit, "USE test"); + test.try_query(test.conn_rwsplit, "CALL multi()"); + result = get_server_id(test.conn_rwsplit); + test.add_result(result != master, "The query should be routed to the master(%d): %d", master, result); + + return test.global_result; +} diff --git a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c index 519e1cadf..09b1b50e1 100644 --- a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c +++ b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c @@ -1091,12 +1091,11 @@ static int gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue) if (MYSQL_IS_COM_QUIT((uint8_t*)GWBUF_DATA(queue))) { /** The COM_CHANGE_USER was already sent but the session is already - * closing. We ignore the COM_QUIT in the hopes that the response - * to the COM_CHANGE_USER comes before the DCB is closed. If the - * DCB is closed before the response arrives, the connection will - * not qualify the persistent connection pool. */ - MXS_INFO("COM_QUIT received while COM_CHANGE_USER is in progress, ignoring"); + * closing. */ + MXS_INFO("COM_QUIT received while COM_CHANGE_USER is in progress, closing pooled connection"); gwbuf_free(queue); + poll_fake_hangup_event(dcb); + rc = 0; } else { @@ -1108,8 +1107,9 @@ static int gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue) */ MXS_INFO("COM_CHANGE_USER in progress, appending query to queue"); backend_protocol->stored_query = gwbuf_append(backend_protocol->stored_query, queue); + rc = 1; } - return 1; + return rc; } /** diff --git a/server/modules/routing/readwritesplit/readwritesplit.cc b/server/modules/routing/readwritesplit/readwritesplit.cc index 7c80db70c..067e49572 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.cc +++ b/server/modules/routing/readwritesplit/readwritesplit.cc @@ -195,6 +195,10 @@ static bool rwsplit_process_router_options(Config& config, { config.strict_multi_stmt = config_truth_value(value); } + else if (strcmp(options[i], "strict_sp_calls") == 0) + { + config.strict_sp_calls = config_truth_value(value); + } else if (strcmp(options[i], "retry_failed_reads") == 0) { config.retry_failed_reads = config_truth_value(value); @@ -966,6 +970,8 @@ static void diagnostics(MXS_ROUTER *instance, DCB *dcb) router->config().retry_failed_reads ? "true" : "false"); dcb_printf(dcb, "\tstrict_multi_stmt: %s\n", router->config().strict_multi_stmt ? "true" : "false"); + dcb_printf(dcb, "\tstrict_sp_calls: %s\n", + router->config().strict_sp_calls ? "true" : "false"); dcb_printf(dcb, "\tdisable_sescmd_history: %s\n", router->config().disable_sescmd_history ? "true" : "false"); dcb_printf(dcb, "\tmax_sescmd_history: %lu\n", @@ -1036,6 +1042,8 @@ static json_t* diagnostics_json(const MXS_ROUTER *instance) json_boolean(router->config().retry_failed_reads)); json_object_set_new(rval, "strict_multi_stmt", json_boolean(router->config().strict_multi_stmt)); + json_object_set_new(rval, "strict_sp_calls", + json_boolean(router->config().strict_sp_calls)); json_object_set_new(rval, "disable_sescmd_history", json_boolean(router->config().disable_sescmd_history)); json_object_set_new(rval, "max_sescmd_history", @@ -1398,6 +1406,7 @@ MXS_MODULE *MXS_CREATE_MODULE() {"disable_sescmd_history", MXS_MODULE_PARAM_BOOL, "true"}, {"max_sescmd_history", MXS_MODULE_PARAM_COUNT, "0"}, {"strict_multi_stmt", MXS_MODULE_PARAM_BOOL, "true"}, + {"strict_sp_calls", MXS_MODULE_PARAM_BOOL, "false"}, {"master_accept_reads", MXS_MODULE_PARAM_BOOL, "false"}, {"connection_keepalive", MXS_MODULE_PARAM_COUNT, "0"}, {MXS_END_MODULE_PARAMS} diff --git a/server/modules/routing/readwritesplit/readwritesplit.hh b/server/modules/routing/readwritesplit/readwritesplit.hh index 22de5ab48..d8008fbde 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.hh +++ b/server/modules/routing/readwritesplit/readwritesplit.hh @@ -161,6 +161,7 @@ struct Config disable_sescmd_history(config_get_bool(params, "disable_sescmd_history")), master_accept_reads(config_get_bool(params, "master_accept_reads")), strict_multi_stmt(config_get_bool(params, "strict_multi_stmt")), + strict_sp_calls(config_get_bool(params, "strict_sp_calls")), retry_failed_reads(config_get_bool(params, "retry_failed_reads")), connection_keepalive(config_get_integer(params, "connection_keepalive")), max_slave_replication_lag(config_get_integer(params, "max_slave_replication_lag")), @@ -178,6 +179,7 @@ struct Config bool master_accept_reads; /**< Use master for reads */ bool strict_multi_stmt; /**< Force non-multistatement queries to be routed to * the master after a multistatement query. */ + bool strict_sp_calls; /**< Lock session to master after an SP call */ bool retry_failed_reads; /**< Retry failed reads on other servers */ int connection_keepalive; /**< Send pings to servers that have been idle * for too long */ diff --git a/server/modules/routing/readwritesplit/rwsplit_internal.hh b/server/modules/routing/readwritesplit/rwsplit_internal.hh index 20180ff9b..cf8ff3460 100644 --- a/server/modules/routing/readwritesplit/rwsplit_internal.hh +++ b/server/modules/routing/readwritesplit/rwsplit_internal.hh @@ -118,6 +118,7 @@ bool is_read_tmp_table(RWSplitSession *router_cli_ses, void check_create_tmp_table(RWSplitSession *router_cli_ses, GWBUF *querybuf, uint32_t type); bool check_for_multi_stmt(GWBUF *buf, void *protocol, uint8_t packet_type); +bool check_for_sp_call(GWBUF *buf, uint8_t packet_type); void close_all_connections(SRWBackendList& backends); diff --git a/server/modules/routing/readwritesplit/rwsplit_route_stmt.cc b/server/modules/routing/readwritesplit/rwsplit_route_stmt.cc index bd35eb27a..bf6035cc0 100644 --- a/server/modules/routing/readwritesplit/rwsplit_route_stmt.cc +++ b/server/modules/routing/readwritesplit/rwsplit_route_stmt.cc @@ -183,7 +183,7 @@ bool route_single_stmt(RWSplit *inst, RWSplitSession *rses, GWBUF *querybuf, con uint32_t qtype = info.type; route_target_t route_target = info.target; bool not_locked_to_master = !rses->large_query && - (!rses->target_node || rses->target_node != rses->current_master); + (!rses->target_node || rses->target_node != rses->current_master); if (not_locked_to_master && is_ps_command(command)) { @@ -236,6 +236,7 @@ bool route_single_stmt(RWSplit *inst, RWSplitSession *rses, GWBUF *querybuf, con succp = handle_master_is_target(inst, rses, &target); if (!rses->rses_config.strict_multi_stmt && + !rses->rses_config.strict_sp_calls && rses->target_node == rses->current_master) { /** Reset the forced node as we're in relaxed multi-statement mode */ @@ -774,12 +775,15 @@ handle_multi_temp_and_load(RWSplitSession *rses, GWBUF *querybuf, * situation, assigning QUERY_TYPE_WRITE for the query will trigger * the error processing. */ if ((rses->target_node == NULL || rses->target_node != rses->current_master) && - check_for_multi_stmt(querybuf, rses->client_dcb->protocol, packet_type)) + (check_for_multi_stmt(querybuf, rses->client_dcb->protocol, packet_type) || + check_for_sp_call(querybuf, packet_type))) { if (rses->current_master) { rses->target_node = rses->current_master; - MXS_INFO("Multi-statement query, routing all future queries to master."); + MXS_INFO("Multi-statement query or stored procedure call, routing " + "all future queries to master."); + } else { diff --git a/server/modules/routing/readwritesplit/rwsplit_tmp_table_multi.cc b/server/modules/routing/readwritesplit/rwsplit_tmp_table_multi.cc index 364914f78..f31dc8527 100644 --- a/server/modules/routing/readwritesplit/rwsplit_tmp_table_multi.cc +++ b/server/modules/routing/readwritesplit/rwsplit_tmp_table_multi.cc @@ -224,3 +224,8 @@ bool check_for_multi_stmt(GWBUF *buf, void *protocol, uint8_t packet_type) return rval; } + +bool check_for_sp_call(GWBUF *buf, uint8_t packet_type) +{ + return packet_type == MXS_COM_QUERY && qc_get_operation(buf) == QUERY_OP_CALL; +}