From 1689fe7f4850be48476a3df367869dc5dbd5bb3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Thu, 20 Sep 2018 13:04:54 +0300 Subject: [PATCH 1/4] Update `user` documentation The documentation mentions a bug in MariaDB 10.2.9 that causes problems with the new users query. --- .../Getting-Started/Configuration-Guide.md | 28 ++++--------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/Documentation/Getting-Started/Configuration-Guide.md b/Documentation/Getting-Started/Configuration-Guide.md index 7d452e067..4399c719e 100644 --- a/Documentation/Getting-Started/Configuration-Guide.md +++ b/Documentation/Getting-Started/Configuration-Guide.md @@ -909,30 +909,12 @@ GRANT SELECT ON mysql.roles_mapping TO 'maxscale'@'maxscalehost'; GRANT SHOW DATABASES ON *.* TO 'maxscale'@'maxscalehost'; ``` -MariaDB MaxScale will execute the following query to retrieve the users. If you -suspect that you might have problems with grants, it is recommended to run this -query and see the results it returns. +See [MaxScale Troubleshooting](https://mariadb.com/kb/en/mariadb-enterprise/maxscale-troubleshooting/) +for more information on how to troubleshoot authentication related problems. -``` -SELECT DISTINCT - user.user AS user, - user.host AS host, - user.password AS password, - concat(user.user,user.host,user.password, - IF((user.Select_priv+0)||find_in_set('Select',Coalesce(tp.Table_priv,0)),'Y','N') , - COALESCE( db.db,tp.db, '')) AS userdata, - user.Select_priv AS anydb, - COALESCE( db.db,tp.db, NULL) AS db - FROM - mysql.user LEFT JOIN - mysql.db ON user.user=db.user AND user.host=db.host LEFT JOIN - mysql.tables_priv tp ON user.user=tp.user AND user.host=tp.host - WHERE user.user IS NOT NULL AND user.user <> '' -``` - -In versions of MySQL 5.7.6 and later, the `Password` column was replaced by -`authentication_string`. Change `user.password` above with -`user.authentication_string`. +**Note:** Due to a bug in MariaDB 10.2.9, if you see a + `SELECT command denied to user ... for table 'users'` + error, grant `SELECT ON mysql.*` to this user. #### `password` From e888bcac3b6b3f459771e1df6d37f3400c77038b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Thu, 20 Sep 2018 10:49:10 +0300 Subject: [PATCH 2/4] MXS-2045: Update avrorouter tutorial Removed unnecessary sections and updated some of the text to be more specific. Expanded the explanations on where the replication starts and how the avrorouter needs to be configured. Added example output from the cdc.py client and removed the relatively useless maxavrocheck section. --- .../Tutorials/Avrorouter-Tutorial.md | 146 ++++++++---------- 1 file changed, 68 insertions(+), 78 deletions(-) diff --git a/Documentation/Tutorials/Avrorouter-Tutorial.md b/Documentation/Tutorials/Avrorouter-Tutorial.md index 876a4c4dc..7b3b7c54a 100644 --- a/Documentation/Tutorials/Avrorouter-Tutorial.md +++ b/Documentation/Tutorials/Avrorouter-Tutorial.md @@ -4,12 +4,6 @@ This tutorial is a short introduction to the [Avrorouter](../Routers/Avrorouter.md), how to set it up and how it interacts with the binlogrouter. -The avrorouter can also be deployed directly on the master server which removes -the need to use the binlogrouter. This does require a lot more disk space on the -master server as both the binlogs and the Avro format files are stored there. It -is recommended to deploy the avrorouter and the binlogrouter on a remove server -so that the data streaming process has a minimal effect on performance. - The first part configures the services and sets them up for the binary log to Avro file conversion. The second part of this tutorial uses the client listener interface for the avrorouter and shows how to communicate with the the service @@ -22,9 +16,9 @@ over the network. ## Preparing the master server The master server where we will be replicating from needs to have binary logging -enabled, the binary log format set to row based replication and the binary log -row image needs to contain all the changed. These can be enabled by adding the -two following lines to the _my.cnf_ file of the master. +enabled, `binlog_format` set to `row` and `binlog_row_image` set to +`full`. These can be enabled by adding the two following lines to the _my.cnf_ +file of the master. ``` binlog_format=row @@ -57,6 +51,8 @@ passwd=maxpwd type=service router=avrorouter source=replication-service +filestem=binlog +start_index=15 # The listener for the replication-service [replication-listener] @@ -84,16 +80,19 @@ protocol=maxscaled socket=default ``` -You can see that the `source` parameter in the _avro-service_ points to the -_replication-service_ we defined before. This service will be the data source -for the avrorouter. The _filestem_ is the prefix in the binlog files. For more -information on the avrorouter options, read the -[Avrorouter Documentation](../Routers/Avrorouter.md). +The `source` parameter in the _avro-service_ points to the _replication-service_ +we defined before. This service will be the data source for the avrorouter. The +_filestem_ is the prefix in the binlog files and _start_index_ is the binlog +number to start from. With these parameters, the avrorouter will start reading +events from binlog `binlog.000015`. -After the services were defined, we added the listeners for the -_replication-service_ and the _avro-service_. The _CDC_ protocol is a new -protocol added with the avrorouter and it is the only supported protocol for the -avrorouter. +Note that the _filestem_ and _start_index_ must point to the file that is the +first binlog that the binlogrouter will replicate. For example, if the first +file you are replicating is `my-binlog-file.001234`, set the parameters to +`filestem=my-binlog-file` and `start_index=1234`. + +For more information on the avrorouter options, read the [Avrorouter +Documentation](../Routers/Avrorouter.md). # Preparing the data in the master server @@ -104,53 +103,23 @@ binary logs before the conversion process is started. If the binary logs contain data modification events for tables that aren't created in the binary logs, the Avro schema of the table needs to be manually -created. There are two ways to do this: +created. There are multiple ways to do this: + +- Dump the database to a slave, configure it to replicate from the master and + point MaxScale to this slave (this is the recommended method as it requires no + extra steps) + +- Use the [_cdc_schema_ Go utility](../Routers/Avrorouter.md#avro-schema-generator) + and copy the generated .avsc files to the _avrodir_ -- Manually create the schema -- Use the [_cdc_schema_ Go utilty](../Routers/Avrorouter.md#avro-schema-generator) - Use the [Python version of the schema generator](../../server/modules/protocol/examples/cdc_schema.py) + and copy the generated .avsc files to the _avrodir_ -All Avro file schemas follow the same general idea. They are in JSON and follow -the following format: - -``` -{ - "namespace": "MaxScaleChangeDataSchema.avro", - "type": "record", - "name": "ChangeRecord", - "fields": - [ - { - "name": "name", - "type": "string", - "real_type": "varchar", - "length": 200 - }, - { - "name":"address", - "type":"string", - "real_type": "varchar", - "length": 200 - }, - { - "name":"age", - "type":"int", - "real_type": "int", - "length": -1 - } - ] -} -``` - -The avrorouter uses the schema file to identify the columns, their names and -what type they are. The _name_ field contains the name of the column and the -_type_ contains the Avro type. Read the [Avro specification](https://avro.apache.org/docs/1.8.1/spec.html) -for details on the layout of the schema files. - -All Avro schema files for tables that are not created in the binary logs need to -be in the location pointed by the _avrodir_ router_option and must use the -following naming: `...avsc`. For example, the -schema file name of the _test.t1_ table would be `test.t1.0000001.avsc`. +If you used the schema generator scripts, all Avro schema files for tables that +are not created in the binary logs need to be in the location pointed to by the +_avrodir_ parameter. The files use the following naming: +`.
..avsc`. For example, the schema file name of +the _test.t1_ table would be `test.t1.0000001.avsc`. # Starting MariaDB MaxScale @@ -161,7 +130,7 @@ executing a few commands. ``` CHANGE MASTER TO MASTER_HOST='172.18.0.1', MASTER_PORT=3000, - MASTER_LOG_FILE='binlog.000001', + MASTER_LOG_FILE='binlog.000015', MASTER_LOG_POS=4, MASTER_USER='maxuser', MASTER_PASSWORD='maxpwd'; @@ -169,30 +138,30 @@ CHANGE MASTER TO MASTER_HOST='172.18.0.1', START SLAVE; ``` +**NOTE:** GTID replication is not currently supported and file-and-position + replication must be used. + This will start the replication of binary logs from the master server at -172.18.0.1:3000. For more details about the details of the commands, refer -to the [Binlogrouter](../Routers/Binlogrouter.md) documentation. +172.18.0.1 listening on port 3000. The first file that the binlogrouter +replicates is `binlog.000015`. This is the same file that was configured as the +starting file in the avrorouter. + +For more details about the SQL commands, refer to the +[Binlogrouter](../Routers/Binlogrouter.md) documentation. After the binary log streaming has started, the avrorouter will automatically -start converting the binlogs into Avro files. +start processing the binlogs. -For the purpose of this tutorial, create a simple test table using the following -statement and populated it with some data. +# Creating and Processing Data + +Next, create a simple test table and populated it with some data by executing +the following statements. ``` CREATE TABLE test.t1 (id INT); INSERT INTO test.t1 VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9), (10); ``` -This table will be replicated through MaxScale and it will be converted into an -Avro file, which you can inspect by using the _maxavrocheck_ utility program. - -``` -[markusjm@localhost avrodata]$ ../bin/maxavrocheck test.t1.000001.avro -File sync marker: caaed7778bbe58e701eec1f96d7719a -/home/markusjm/build/avrodata/test.t1.000001.avro: 1 blocks, 1 records and 12 bytes -``` - To use the _cdc.py_ command line client to connect to the CDC service, we must first create a user. This can be done via maxadmin by executing the following command. @@ -201,8 +170,29 @@ maxadmin call command cdc add_user avro-service maxuser maxpwd ``` This will create the _maxuser:maxpwd_ credentials which can then be used to -request a data stream of the `test.t1` table that was created earlier. +request a JSON data stream of the `test.t1` table that was created earlier. ``` cdc.py -u maxuser -p maxpwd -h 127.0.0.1 -P 4001 test.t1 ``` + +The output is a stream of JSON events describing the changes done to the +database. + +``` +{"namespace": "MaxScaleChangeDataSchema.avro", "type": "record", "name": "ChangeRecord", "fields": [{"name": "domain", "type": "int"}, {"name": "server_id", "type": "int"}, {"name": "sequence", "type": "int"}, {"name": "event_number", "type": "int"}, {"name": "timestamp", "type": "int"}, {"name": "event_type", "type": {"type": "enum", "name": "EVENT_TYPES", "symbols": ["insert", "update_before", "update_after", "delete"]}}, {"name": "id", "type": "int", "real_type": "int", "length": -1}]} +{"domain": 0, "server_id": 3000, "sequence": 11, "event_number": 1, "timestamp": 1537429419, "event_type": "insert", "id": 1} +{"domain": 0, "server_id": 3000, "sequence": 11, "event_number": 2, "timestamp": 1537429419, "event_type": "insert", "id": 2} +{"domain": 0, "server_id": 3000, "sequence": 11, "event_number": 3, "timestamp": 1537429419, "event_type": "insert", "id": 3} +{"domain": 0, "server_id": 3000, "sequence": 11, "event_number": 4, "timestamp": 1537429419, "event_type": "insert", "id": 4} +{"domain": 0, "server_id": 3000, "sequence": 11, "event_number": 5, "timestamp": 1537429419, "event_type": "insert", "id": 5} +{"domain": 0, "server_id": 3000, "sequence": 11, "event_number": 6, "timestamp": 1537429419, "event_type": "insert", "id": 6} +{"domain": 0, "server_id": 3000, "sequence": 11, "event_number": 7, "timestamp": 1537429419, "event_type": "insert", "id": 7} +{"domain": 0, "server_id": 3000, "sequence": 11, "event_number": 8, "timestamp": 1537429419, "event_type": "insert", "id": 8} +{"domain": 0, "server_id": 3000, "sequence": 11, "event_number": 9, "timestamp": 1537429419, "event_type": "insert", "id": 9} +{"domain": 0, "server_id": 3000, "sequence": 11, "event_number": 10, "timestamp": 1537429419, "event_type": "insert", "id": 10} +``` + +The first record is always the JSON format schema for the table describing the +types and names of the fields. All records that follow it represent the changes +that have happened on the database. From 60915f847f29d60fe7b43c99871b89a4fc47505a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Fri, 21 Sep 2018 09:24:47 +0300 Subject: [PATCH 3/4] MXS-2064: Log workaround for grant problems with MariaDB 10.2.10 When the 10.2 users query is executed with a MariaDB server older than 10.2.11, the query will fail due to inadequate grants on the 'users' table generated as a part of the CTE. To work around the issue, a SELECT grant on the whole mysql database is required. Logging the server where the query fails also helps resolve the problem by pointing out where the grant needs to be added. --- .../modules/authenticator/MySQLAuth/dbusers.c | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index 06e62ad05..f02f34da4 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -860,6 +861,35 @@ static bool roles_are_available(MYSQL* conn, SERVICE* service, SERVER* server) return rval; } +static void report_mdev13453_problem(MYSQL *con, SERVER *server) +{ + if (server->version >= 100200 && server->version < 100211 && + mxs_pcre2_simple_match("SELECT command denied to user .* for table 'users'", + mysql_error(con), 0, NULL) == MXS_PCRE2_MATCH) + { + char user[256] = ""; // Enough for all user-hostname combinations + const char* quoted_user = "select concat(\"'\", user, \"'@'\", host, \"'\") as user " + "from mysql.user " + "where concat(user, \"@\", host) = current_user()"; + MYSQL_RES *res; + + if (mxs_mysql_query(con, quoted_user) == 0 && (res = mysql_store_result(con))) + { + MYSQL_ROW row = mysql_fetch_row(res); + + if (row && row[0]) + { + snprintf(user, sizeof(user), "%s", row[0]); + } + + mysql_free_result(res); + } + + MXS_ERROR("Due to MDEV-13453, the service user requires extra grants on the `mysql` database. " + "To fix the problem, add the following grant: GRANT SELECT ON `mysql`.* TO %s", user); + } +} + int get_users_from_server(MYSQL *con, SERVER_REF *server_ref, SERVICE *service, SERV_LISTENER *listener) { if (server_ref->server->version_string[0] == 0) @@ -917,7 +947,8 @@ int get_users_from_server(MYSQL *con, SERVER_REF *server_ref, SERVICE *service, } else { - MXS_ERROR("Failed to load users: %s", mysql_error(con)); + MXS_ERROR("Failed to load users from server '%s': %s", server_ref->server->name, mysql_error(con)); + report_mdev13453_problem(con, server_ref->server); } MXS_FREE(query); From d55c07dc2ef1364413a40f9a53eb2876612e21ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Fri, 21 Sep 2018 10:26:30 +0300 Subject: [PATCH 4/4] MXS-2066: Reset resultset collection by default The collection of resultsets needs to be disabled by default when a response is received to cover the cases where an error is returned. The collection of results should also not be set for queries that do not generate any responses. --- .../protocol/MySQL/mariadbbackend/mysql_backend.c | 14 +++++++------- .../routing/readwritesplit/rwsplit_route_stmt.cc | 6 +++++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/server/modules/protocol/MySQL/mariadbbackend/mysql_backend.c b/server/modules/protocol/MySQL/mariadbbackend/mysql_backend.c index 16df96206..448d1da71 100644 --- a/server/modules/protocol/MySQL/mariadbbackend/mysql_backend.c +++ b/server/modules/protocol/MySQL/mariadbbackend/mysql_backend.c @@ -836,14 +836,14 @@ gw_read_and_write(DCB *dcb) result_collected = true; } else if (expecting_ps_response(proto) && - mxs_mysql_is_prep_stmt_ok(read_buffer)) + mxs_mysql_is_prep_stmt_ok(read_buffer) && + !complete_ps_response(read_buffer)) + { + dcb_readq_prepend(dcb, read_buffer); + return 0; + } + else { - if (!complete_ps_response(read_buffer)) - { - dcb_readq_prepend(dcb, read_buffer); - return 0; - } - // Collected the complete result proto->collect_result = false; result_collected = true; diff --git a/server/modules/routing/readwritesplit/rwsplit_route_stmt.cc b/server/modules/routing/readwritesplit/rwsplit_route_stmt.cc index b8296790f..7eba4e858 100644 --- a/server/modules/routing/readwritesplit/rwsplit_route_stmt.cc +++ b/server/modules/routing/readwritesplit/rwsplit_route_stmt.cc @@ -344,7 +344,11 @@ bool route_session_write(RWSplitSession *rses, GWBUF *querybuf, bool expecting_response = mxs_mysql_command_will_respond(command); int nsucc = 0; uint64_t lowest_pos = id; - gwbuf_set_type(querybuf, GWBUF_TYPE_COLLECT_RESULT); + + if (expecting_response) + { + gwbuf_set_type(querybuf, GWBUF_TYPE_COLLECT_RESULT); + } if (qc_query_is_type(type, QUERY_TYPE_PREPARE_NAMED_STMT) || qc_query_is_type(type, QUERY_TYPE_PREPARE_STMT))