From 2c04adafd192b7e44c1b66b743e3d1449b944ef5 Mon Sep 17 00:00:00 2001 From: Timofey Turenko Date: Tue, 8 Aug 2017 19:13:37 +0300 Subject: [PATCH 1/5] fix kerberos test and mxs1516 test --- maxscale-system-test/kerberos_setup.cpp | 35 +++++++++++++++++-------- maxscale-system-test/mxs1516.cpp | 3 +++ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/maxscale-system-test/kerberos_setup.cpp b/maxscale-system-test/kerberos_setup.cpp index 7ec96d82b..cc4f19291 100644 --- a/maxscale-system-test/kerberos_setup.cpp +++ b/maxscale-system-test/kerberos_setup.cpp @@ -14,6 +14,7 @@ int main(int argc, char *argv[]) TestConnections * Test = new TestConnections(argc, argv); Test->set_timeout(1000); char str[1024]; + char str1[1024]; int i; @@ -32,23 +33,27 @@ int main(int argc, char *argv[]) sprintf(str, "%s/krb5.conf", test_dir); for (i = 0; i < Test->repl->N; i++) { + Test->repl->ssh_node(i, true, "yum clean all"); Test->repl->ssh_node(i, true, "yum install -y MariaDB-gssapi-server MariaDB-gssapi-client krb5-workstation pam_krb5"); - Test->repl->copy_to_node(str, (char *) "~/", i); - Test->repl->ssh_node(i, true, "cp ~/krb5.conf /etc/"); + Test->repl->copy_to_node(str, Test->repl->access_homedir[i], i); + sprintf(str1, "cp %s/krb5.conf /etc/", Test->repl->access_homedir[i]); + Test->repl->ssh_node(i, true, str1); Test->repl->copy_to_node((char *) "hosts", (char *) "~/", i); - Test->repl->ssh_node(i, true, "cp ~/hosts /etc/"); + sprintf(str1, "cp %s/hosts /etc/", Test->repl->access_homedir[i]); + Test->repl->ssh_node(i, true, str1); } Test->tprintf("Copying 'hosts' and krb5.conf files to Maxscale node\n"); - Test->copy_to_maxscale((char *) "hosts", (char *) "~/"); - Test->ssh_maxscale(true, (char *) "cp ~/hosts /etc/"); + Test->copy_to_maxscale((char *) "hosts", Test->maxscale_access_homedir); + Test->ssh_maxscale(true, (char *) "cp %s/hosts /etc/", Test->maxscale_access_homedir); - Test->copy_to_maxscale(str, (char *) "~/"); - Test->ssh_maxscale(true, (char *) "cp ~/krb5.conf /etc/"); + Test->copy_to_maxscale(str, Test->maxscale_access_homedir); + Test->ssh_maxscale(true, (char *) "cp %s/krb5.conf /etc/", Test->maxscale_access_homedir); Test->tprintf("Instaling Kerberos server packages to Maxscale node\n"); + Test->ssh_maxscale(true, (char *) "yum clean all"); Test->ssh_maxscale(true, (char *) "yum install rng-tools -y"); Test->ssh_maxscale(true, (char *) "rngd -r /dev/urandom -o /dev/random"); @@ -84,6 +89,7 @@ int main(int argc, char *argv[]) Test->ssh_maxscale(true, (char *) "chmod a+r /etc/krb5.keytab;"); Test->ssh_maxscale(false, (char *) "kinit mariadb/maxscale.test@MAXSCALE.TEST -k -t /etc/krb5.keytab"); + Test->ssh_maxscale(true, (char *) "mkdir -p /home/maxscale"); Test->ssh_maxscale(true, (char *) "su maxscale --login -s /bin/sh -c \"kinit mariadb/maxscale.test@MAXSCALE.TEST -k -t /etc/krb5.keytab\""); @@ -94,11 +100,13 @@ int main(int argc, char *argv[]) for (i = 0; i < Test->repl->N; i++) { sprintf(str, "%s/kerb.cnf", test_dir); - Test->repl->copy_to_node(str, (char *) "~/", i); - Test->repl->ssh_node(i, true, "cp ~/kerb.cnf /etc/my.cnf.d/"); + Test->repl->copy_to_node(str, Test->repl->access_homedir[i], i); + sprintf(str, "cp %s/kerb.cnf /etc/my.cnf.d/", Test->repl->access_homedir[i]); + Test->repl->ssh_node(i, true, str); - Test->repl->copy_to_node((char *) "krb5.keytab", (char *) "~/", i); - Test->repl->ssh_node(i, true, "cp ~/krb5.keytab /etc/"); + Test->repl->copy_to_node((char *) "krb5.keytab", Test->repl->access_homedir[i], i); + sprintf(str, "cp %s/krb5.keytab /etc/", Test->repl->access_homedir[i]); + Test->repl->ssh_node(i, true, str); Test->repl->ssh_node(i, false, "kinit mariadb/maxscale.test@MAXSCALE.TEST -k -t /etc/krb5.keytab"); } @@ -131,6 +139,11 @@ int main(int argc, char *argv[]) "echo select User,Host from mysql.user | mysql -uusr1 -h maxscale.maxscale.test -P 4009"), "Error executing query against Read Connection Slave\n"); + for (int i = 0; i < Test->repl->N; i++) + { + Test->repl->ssh_node(i, true, "sudo rm -f /etc/my.cnf.d/kerb.cnf"); + } + int rval = Test->global_result; delete Test; return rval; diff --git a/maxscale-system-test/mxs1516.cpp b/maxscale-system-test/mxs1516.cpp index af9c0e84f..887013373 100644 --- a/maxscale-system-test/mxs1516.cpp +++ b/maxscale-system-test/mxs1516.cpp @@ -17,6 +17,9 @@ int main(int argc, char** argv) test.repl->connect(); test.repl->change_master(1, 0); + // Give the monitor some time to detect it + sleep(5); + test.add_result(execute_query_silent(test.conn_master, "SELECT 1") == 0, "Query should fail"); // Change the master back to the original one From 943c82b33be16859575b6fe79295991e1e77f7dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Thu, 1 Feb 2018 12:55:30 +0200 Subject: [PATCH 2/5] Extend cdc_datatypes test The test now also creates TIME type values and checks that they are converted correctly. Also added NULL value tests for all values and made required adjustments to the code. --- .../cdc_datatypes/cdc_datatypes.cpp | 32 ++++++++++++++++--- .../cdc_datatypes/cdc_result.h | 6 ++-- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/maxscale-system-test/cdc_datatypes/cdc_datatypes.cpp b/maxscale-system-test/cdc_datatypes/cdc_datatypes.cpp index 5695d6d7a..c749fa88d 100644 --- a/maxscale-system-test/cdc_datatypes/cdc_datatypes.cpp +++ b/maxscale-system-test/cdc_datatypes/cdc_datatypes.cpp @@ -28,6 +28,7 @@ static const char* integer_values[] = "-1", "20", "-20", + "NULL", NULL }; @@ -47,6 +48,7 @@ static const char* decimal_values[] = "-1.5", "20.5", "-20.5", + "NULL", NULL }; @@ -65,7 +67,7 @@ static const char* string_values[] = { "\"Hello world!\"", "\"The quick brown fox jumps over the lazy dog\"", -// "\"The Unicode should work: äöåǢ\"", + "NULL", NULL }; @@ -85,26 +87,27 @@ static const char* binary_values[] = "\"Hello world!\"", "\"The quick brown fox jumps over the lazy dog\"", "NULL", -// "\"The Unicode should work: äöåǢ\"", -// "\"These should work for binary types: ⦿☏☃☢😤😂\"", NULL }; static const char* datetime_types[] = { + "DATETIME", "DATETIME(1)", "DATETIME(2)", "DATETIME(3)", "DATETIME(4)", "DATETIME(5)", "DATETIME(6)", - "TIMESTAMP", + // TODO: Fix test setup to use same timezone + // "TIMESTAMP", NULL }; static const char* datetime_values[] = { "'2018-01-01 11:11:11'", + "NULL", NULL }; @@ -117,6 +120,26 @@ static const char* date_types[] = static const char* date_values[] = { "'2018-01-01'", + "NULL", + NULL +}; + +static const char* time_types[] = +{ + "TIME", + "TIME(1)", + "TIME(2)", + "TIME(3)", + "TIME(4)", + "TIME(5)", + "TIME(6)", + NULL +}; + +static const char* time_values[] = +{ + "'12:00:00'", + "NULL", NULL }; @@ -132,6 +155,7 @@ struct { binary_types, binary_values }, { datetime_types, datetime_values }, { date_types, date_values }, + { time_types, time_values }, { 0 } }; diff --git a/maxscale-system-test/cdc_datatypes/cdc_result.h b/maxscale-system-test/cdc_datatypes/cdc_result.h index 2475230d6..03c209855 100644 --- a/maxscale-system-test/cdc_datatypes/cdc_result.h +++ b/maxscale-system-test/cdc_datatypes/cdc_result.h @@ -39,8 +39,10 @@ public: bool operator ==(const TestOutput& output) const { return m_value == output.getValue() || - (m_type.find("BLOB") != std::string::npos && - output.getValue().length() == 0); + (m_type.find("BLOB") != std::string::npos && output.getValue().length() == 0) || + // A NULL timestamp appears to be inserted as NOW() by default in 10.2, a NULL INT is + // inserted as 0 and a NULL string gets converted into an empty string by the CDC system + (m_value == "NULL" && (output.getValue().empty() || m_type == "TIMESTAMP" || output.getValue() == "0")); } bool operator !=(const TestOutput& output) const From 7093a5bdf82bd4ecefe6d3ebb3e7188599227658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Thu, 1 Feb 2018 13:46:28 +0200 Subject: [PATCH 3/5] Fix CREATE TABLE tokenization The token skipping function did not check for a period or an opening parenthesis when parsing the test. Also fixed a debug assertion when only NULL values were inserted. --- server/modules/routing/avrorouter/avro_rbr.c | 19 ++++++++++++++++++- .../modules/routing/avrorouter/avro_schema.c | 2 +- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/server/modules/routing/avrorouter/avro_rbr.c b/server/modules/routing/avrorouter/avro_rbr.c index dc72be06a..b34b0f83c 100644 --- a/server/modules/routing/avrorouter/avro_rbr.c +++ b/server/modules/routing/avrorouter/avro_rbr.c @@ -498,6 +498,23 @@ int get_metadata_len(uint8_t type) } \ }while(false) +// Debug function for checking whether a row event consists of only NULL values +static bool all_fields_null(uint8_t* null_bitmap, int ncolumns) +{ + bool rval = true; + + for (long i = 0; i < ncolumns; i++) + { + if (!bit_is_set(null_bitmap, ncolumns, i)) + { + rval = false; + break; + } + } + + return rval; +} + /** * @brief Extract the values from a single row in a row event * @@ -525,7 +542,7 @@ uint8_t* process_row_event_data(TABLE_MAP *map, TABLE_CREATE *create, avro_value /** Store the null value bitmap */ uint8_t *null_bitmap = ptr; ptr += (ncolumns + 7) / 8; - ss_dassert(ptr < end); + ss_dassert(ptr < end || (bit_is_set(null_bitmap, ncolumns, 0))); char trace[ncolumns][768]; memset(trace, 0, sizeof(trace)); diff --git a/server/modules/routing/avrorouter/avro_schema.c b/server/modules/routing/avrorouter/avro_schema.c index 962a394f7..21b91e038 100644 --- a/server/modules/routing/avrorouter/avro_schema.c +++ b/server/modules/routing/avrorouter/avro_schema.c @@ -1251,7 +1251,7 @@ static void skip_token(const char** saved) { const char* ptr = *saved; - while (*ptr && !isspace(*ptr)) + while (*ptr && !isspace(*ptr) && *ptr != '(' && *ptr != '.') { ptr++; } From 7ae931ce9c1b615477bd68c1947b87bf1e57de82 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Wed, 31 Jan 2018 16:14:14 +0200 Subject: [PATCH 4/5] MXS-1635 Allow using specific address when connecting In some cases you might want to use a specific address/interface when connecting to a server instead of the default one. With the global parameter 'local_address' it can now be specified which address to use. --- .../Getting-Started/Configuration-Guide.md | 11 ++++ .../MaxScale-2.1.14-Release-Notes.md | 63 +++++++++++++++++++ include/maxscale/config.h | 1 + server/core/config.c | 4 ++ server/core/utils.c | 37 ++++++++++- 5 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 Documentation/Release-Notes/MaxScale-2.1.14-Release-Notes.md diff --git a/Documentation/Getting-Started/Configuration-Guide.md b/Documentation/Getting-Started/Configuration-Guide.md index f367176a6..947741d2d 100644 --- a/Documentation/Getting-Started/Configuration-Guide.md +++ b/Documentation/Getting-Started/Configuration-Guide.md @@ -537,6 +537,17 @@ This will log all statements that cannot be parsed completely. This may be useful if you suspect that MariaDB MaxScale routes statements to the wrong server (e.g. to a slave instead of to a master). +#### `local_address` + +What specific local address/interface to use when connecting to servers. + +This can be used for ensuring that MaxScale uses a particular interface +when connecting to servers, in case the computer MaxScale is running on +has multiple interfaces. +``` +local_address=192.168.1.254 +``` + ### Service A service represents the database service that MariaDB MaxScale offers to the diff --git a/Documentation/Release-Notes/MaxScale-2.1.14-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.1.14-Release-Notes.md new file mode 100644 index 000000000..eb9f4426f --- /dev/null +++ b/Documentation/Release-Notes/MaxScale-2.1.14-Release-Notes.md @@ -0,0 +1,63 @@ +# MariaDB MaxScale 2.1.14 Release Notes + +Release 2.1.14 is a GA release. + +This document describes the changes in release 2.1.14, when compared +to release [2.1.13](MaxScale-2.1.13-Release-Notes.md). + +If you are upgrading from release 2.0, please also read the following +release notes: + +* [2.1.13](./MaxScale-2.1.13-Release-Notes.md) +* [2.1.12](./MaxScale-2.1.12-Release-Notes.md) +* [2.1.11](./MaxScale-2.1.11-Release-Notes.md) +* [2.1.10](./MaxScale-2.1.10-Release-Notes.md) +* [2.1.9](./MaxScale-2.1.9-Release-Notes.md) +* [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). + +## New Features + +### Local Address + +It is now possible to specify what local address MaxScale should +use when connecting to servers. Please refer to the documentation +for [details](../Getting-Started/Configuration-Guide.md#local_address). + +## Bug fixes + +[Here is a list of bugs fixed in MaxScale 2.1.14.](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.14) + +* [MXS-1627](https://jira.mariadb.org/browse/MXS-1627) MySQLAuth loads users that use authentication plugins +* [MXS-1620](https://jira.mariadb.org/browse/MXS-1620) CentOS package symbols are stripped +* [MXS-1602](https://jira.mariadb.org/browse/MXS-1602) cannot connect to maxinfo with python client +* [MXS-1601](https://jira.mariadb.org/browse/MXS-1601) maxinfo crash at execute query 'flush;' +* [MXS-1600](https://jira.mariadb.org/browse/MXS-1600) maxscale it seen to not coop well with lower-case-table-names=1 on cnf +* [MXS-1576](https://jira.mariadb.org/browse/MXS-1576) Maxscale crashes when starting if .avro and .avsc files are present +* [MXS-1543](https://jira.mariadb.org/browse/MXS-1543) Avrorouter doesn't detect MIXED or STATEMENT format replication +* [MXS-1416](https://jira.mariadb.org/browse/MXS-1416) maxscale should not try to do anything when started with --config-check + +## 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/include/maxscale/config.h b/include/maxscale/config.h index 98930914b..7a1265470 100644 --- a/include/maxscale/config.h +++ b/include/maxscale/config.h @@ -77,6 +77,7 @@ typedef struct char* qc_args; /**< Arguments for the query classifier */ int query_retries; /**< Number of times a interrupted query is retried */ time_t query_retry_timeout; /**< Timeout for query retries */ + char* local_address; /**< Local address to use when connecting */ } MXS_CONFIG; /** diff --git a/server/core/config.c b/server/core/config.c index 19132f2c8..bd31b3f6b 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -1401,6 +1401,10 @@ handle_global_item(const char *name, const char *value) MXS_FREE(v); } } + else if (strcmp(name, "local_address") == 0) + { + gateway.local_address = MXS_STRDUP_A(value); + } else { for (i = 0; lognames[i].name; i++) diff --git a/server/core/utils.c b/server/core/utils.c index 47570d48f..194a43e4f 100644 --- a/server/core/utils.c +++ b/server/core/utils.c @@ -41,6 +41,7 @@ #include #include +#include #include #include #include @@ -986,15 +987,47 @@ int open_network_socket(enum mxs_socket_type type, struct sockaddr_storage *addr memcpy(addr, ai->ai_addr, ai->ai_addrlen); set_port(addr, port); + freeaddrinfo(ai); + if ((type == MXS_SOCKET_NETWORK && !configure_network_socket(so)) || (type == MXS_SOCKET_LISTENER && !configure_listener_socket(so))) { close(so); so = -1; } - } + else if (type == MXS_SOCKET_NETWORK) + { + MXS_CONFIG* config = config_get_global_options(); - freeaddrinfo(ai); + if (config->local_address) + { + if ((rc = getaddrinfo(config->local_address, NULL, &hint, &ai)) == 0) + { + struct sockaddr_storage local_address = {}; + + memcpy(&local_address, ai->ai_addr, ai->ai_addrlen); + freeaddrinfo(ai); + + if (bind(so, (struct sockaddr*)&local_address, sizeof(local_address)) == 0) + { + MXS_INFO("Bound connecting socket to \"%s\".", config->local_address); + } + else + { + MXS_ERROR("Could not bind connecting socket to local address \"%s\", " + "connecting to server using default local address: %s", + config->local_address, mxs_strerror(errno)); + } + } + else + { + MXS_ERROR("Could not get address information for local address \"%s\", " + "connecting to server using default local address: %s", + config->local_address, mxs_strerror(errno)); + } + } + } + } } #else From facb8d60f7676fdac3115170926e797dd16ad24d Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Fri, 2 Feb 2018 14:54:21 +0200 Subject: [PATCH 5/5] MXS-1635 Test program for local_address Tests that local_address is taken into account. However, at the time of writing the maxscale VM does not have two usable IP addresses, so we only test that explicitly specifying an IP-address does not break things. Locally it has been confirmed that this indeed works the way it is supposed to. --- maxscale-system-test/.gitignore | 1 + maxscale-system-test/CMakeLists.txt | 3 + .../cnf/maxscale.cnf.template.local_address | 90 +++++ maxscale-system-test/local_address.cpp | 331 ++++++++++++++++++ 4 files changed, 425 insertions(+) create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.local_address create mode 100644 maxscale-system-test/local_address.cpp diff --git a/maxscale-system-test/.gitignore b/maxscale-system-test/.gitignore index 78424a324..c291ed6ba 100644 --- a/maxscale-system-test/.gitignore +++ b/maxscale-system-test/.gitignore @@ -97,6 +97,7 @@ kill_master large_insert_hang load_balancing load_balancing_galera +local_address long_sysbench longblob lots_of_rows diff --git a/maxscale-system-test/CMakeLists.txt b/maxscale-system-test/CMakeLists.txt index 9177d7daf..aaf91c21a 100644 --- a/maxscale-system-test/CMakeLists.txt +++ b/maxscale-system-test/CMakeLists.txt @@ -761,6 +761,9 @@ add_test_executable_notest(create_rds.cpp create_rds replication LABELS EXTERN_B # start sysbench ageints RWSplit for infinite execution add_test_executable_notest(long_sysbench.cpp long_sysbench replication LABELS readwritesplit REPL_BACKEND) +# test effect of local_address in configuration file +add_test_executable(local_address.cpp local_address local_address LABELS REPL_BACKEND) + configure_file(templates.h.in templates.h @ONLY) include(CTest) diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.local_address b/maxscale-system-test/cnf/maxscale.cnf.template.local_address new file mode 100644 index 000000000..44bf3ae38 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.local_address @@ -0,0 +1,90 @@ +[maxscale] +threads=###threads### +###local_address### + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +monitor_interval=1000 +detect_stale_master=false + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options=slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[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 + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/local_address.cpp b/maxscale-system-test/local_address.cpp new file mode 100644 index 000000000..9eafb184c --- /dev/null +++ b/maxscale-system-test/local_address.cpp @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2018 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl11. + * + * Change Date: 2019-07-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +#include "testconnections.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace +{ + +template +void to_collection(string s, const string& delimiter, T* pT) +{ + size_t pos; + + while ((pos = s.find(delimiter)) != std::string::npos) + { + pT->push_back(s.substr(0, pos)); + s.erase(0, pos + delimiter.length()); + } + + if (s.length() != 0) + { + pT->push_back(s); + } +} + +string& ltrim(std::string& s) +{ + s.erase(s.begin(), std::find_if(s.begin(), s.end(), + std::not1(std::ptr_fun(std::isspace)))); + return s; +} + +string& rtrim(std::string& s) +{ + s.erase(std::find_if(s.rbegin(), s.rend(), + std::not1(std::ptr_fun(std::isspace))).base(), s.end()); + return s; +} + +string& trim(std::string& s) +{ + return ltrim(rtrim(s)); +} + +string extract_ip(string s) +{ + // 's' looks something like: " inet 127.0.0.1/..."; + s = s.substr(9); // => "127.0.0.1/..."; + s = s.substr(0, s.find_first_of('/')); // => "127.0.0.1" + return s; +} + +void get_maxscale_ips(TestConnections& test, vector* pIps) +{ + string output(test.ssh_maxscale_output(false, "ip addr|fgrep inet|fgrep -v ::")); + + to_collection(output, "\n", pIps); + transform(pIps->begin(), pIps->end(), pIps->begin(), extract_ip); + + pIps->erase(find(pIps->begin(), pIps->end(), "127.0.0.1")); +} + +} + +namespace +{ + +void drop_user(TestConnections& test, const string& user, const string& host) +{ + string stmt("DROP USER IF EXISTS "); + + stmt += "'"; + stmt += user; + stmt += "'@'"; + stmt += host; + stmt += "'"; + test.try_query(test.conn_rwsplit, stmt.c_str()); +} + +void create_user(TestConnections& test, const string& user, const string& password, const string& host) +{ + string stmt("CREATE USER "); + + stmt += "'"; + stmt += user; + stmt += "'@'"; + stmt += host; + stmt += "'"; + stmt += " IDENTIFIED BY "; + stmt += "'"; + stmt += password; + stmt += "'"; + test.try_query(test.conn_rwsplit, stmt.c_str()); +} + +void grant_access(TestConnections& test, const string& user, const string& host) +{ + string stmt("GRANT SELECT, INSERT, UPDATE ON *.* TO "); + + stmt += "'"; + stmt += user; + stmt += "'@'"; + stmt += host; + stmt += "'"; + test.try_query(test.conn_rwsplit, stmt.c_str()); + + test.try_query(test.conn_rwsplit, "FLUSH PRIVILEGES"); +} + +void create_user_and_grants(TestConnections& test, + const string& user, const string& password, const string& host) +{ + test.tprintf("Creating user: %s@%s", user.c_str(), host.c_str()); + + drop_user(test, user, host); + create_user(test, user, password, host); + grant_access(test, user, host); +} + +bool select_user(MYSQL* pMysql, string* pUser) +{ + bool rv = false; + + if (mysql_query(pMysql, "SELECT USER()") == 0) + { + MYSQL_RES* pRes = mysql_store_result(pMysql); + + if (mysql_num_rows(pRes) == 1) + { + MYSQL_ROW row = mysql_fetch_row(pRes); + *pUser = row[0]; + rv = true; + } + + mysql_free_result(pRes); + + while (mysql_next_result(pMysql) == 0) + { + MYSQL_RES* pRes = mysql_store_result(pMysql); + mysql_free_result(pRes); + } + } + + return rv; +} + +bool can_connect_to_maxscale(const char* zHost, int port, const char* zUser, const char* zPassword) +{ + bool could_connect = false; + + MYSQL* pMysql = mysql_init(NULL); + + if (pMysql) + { + unsigned int timeout = 5; + mysql_options(pMysql, MYSQL_OPT_CONNECT_TIMEOUT, &timeout); + mysql_options(pMysql, MYSQL_OPT_READ_TIMEOUT, &timeout); + mysql_options(pMysql, MYSQL_OPT_WRITE_TIMEOUT, &timeout); + + if (mysql_real_connect(pMysql, zHost, zUser, zPassword, NULL, port, NULL, 0)) + { + string user; + if (select_user(pMysql, &user)) + { + could_connect = true; + } + else + { + cout << "Could not 'SELECT USER()' as '" << zUser << "': " << mysql_error(pMysql) << endl; + } + } + else + { + cout << "Could not connect as '" << zUser << "': " << mysql_error(pMysql) << endl; + } + + mysql_close(pMysql); + } + + return could_connect; +} + +string get_local_ip(TestConnections& test) +{ + string output(test.ssh_maxscale_output(false, "nslookup maxscale|fgrep Server:|sed s/Server://")); + return trim(output); +} + +void start_maxscale_with_local_address(TestConnections& test, + const string& replace, + const string& with) +{ + string command("sed -i s/"); + command += replace; + command += "/"; + command += with; + command += "/ "; + command += "/etc/maxscale.cnf"; + + test.ssh_maxscale_output(true, command.c_str()); + + test.start_maxscale(); +} + +void test_connecting(TestConnections& test, + const char* zUser, const char* zPassword, const char* zHost, + bool should_be_able_to) +{ + bool could_connect = can_connect_to_maxscale(test.maxscale_IP, test.rwsplit_port, zUser, zPassword); + + if (!could_connect && should_be_able_to) + { + test.assert(false, "%s@%s should have been able to connect, but wasn't.", zUser, zHost); + } + else if (could_connect && !should_be_able_to) + { + test.assert(false, "%s@%s should NOT have been able to connect, but was.", zUser, zHost); + } + else + { + if (could_connect) + { + test.tprintf("%s@%s could connect, as expected.", zUser, zHost); + } + else + { + test.tprintf("%s@%s could NOT connect, as expected.", zUser, zHost); + } + } +} + +void run_test(TestConnections& test, const string& ip1, const string& ip2) +{ + test.connect_maxscale(); + + string local_ip = get_local_ip(test); + + const char* zUser1 = "alice"; + const char* zUser2 = "bob"; + const char* zPassword1 = "alicepwd"; + const char* zPassword2 = "bobpwd"; + + create_user_and_grants(test, zUser1, zPassword1, ip1); + create_user_and_grants(test, zUser1, zPassword1, local_ip); + create_user_and_grants(test, zUser2, zPassword2, ip2); + create_user_and_grants(test, zUser2, zPassword2, local_ip); + + test.tprintf("\n"); + test.tprintf("Testing default; alice should be able to access, bob not."); + + test_connecting(test, zUser1, zPassword1, ip1.c_str(), true); + test_connecting(test, zUser2, zPassword2, ip2.c_str(), false); + + test.close_maxscale_connections(); + test.stop_maxscale(); + + test.tprintf("\n"); + test.tprintf("Testing with local_address=%s; alice should be able to access, bob not.", + ip1.c_str()); + + string local_address_ip1 = "local_address=" + ip1; + start_maxscale_with_local_address(test, "###local_address###", local_address_ip1); + test.connect_maxscale(); + + test_connecting(test, zUser1, zPassword1, ip1.c_str(), true); + test_connecting(test, zUser2, zPassword2, ip2.c_str(), false); + + test.close_maxscale_connections(); + test.stop_maxscale(); + + test.tprintf("\n"); + test.tprintf("WARNING: Other IP-address not tested, as usable IP-address not available."); + +#ifdef USABLE_SECOND_IP_ADDRESS_ON_MAXSCALE_NODE_IS_AVAILABLE + test.tprintf("\n"); + test.tprintf("\nTesting with local_address=%s, bob should be able to access, alice not.", + ip2.c_str()); + + string local_address_ip2 = "local_address=" + ip2; + start_maxscale_with_local_address(test, local_address_ip1, local_address_ip2); + test.connect_maxscale(); + + test_connecting(test, zUser1, zPassword1, ip1.c_str(), false); + test_connecting(test, zUser2, zPassword2, ip2.c_str(), true); + + test.close_maxscale_connections(); + test.stop_maxscale(); +#endif +} + +} + +int main(int argc, char** argv) +{ + TestConnections test(argc, argv); + + vector ips; + get_maxscale_ips(test, &ips); + + if (ips.size() >= 2) + { + run_test(test, ips[0], ips[1]); + } + else + { + test.assert(false, "MaxScale node does not have at least two IP-addresses."); + } + + return test.global_result; +}