Files
MaxScale/server/modules/routing/binlog/blr_slave.c
Johan Wikman dc7f7b6fb4 All changes 2.0.0...develop
973b983 Merge branch 'release-2.0.0' into develop
255dd23 Make spinlock functions take const argument
6e23bab Fix bitmask reallocation
338c189 Rename and clean up slavelag filter
3ea8f28 Fix possible NULL pointer dereference
bfe6738 MXS-830: Add module information to logged messages
1fad962 Fix strncat usage
d38997a Adjust log throttling policy
0be4e4b Add hashtable_item_strcasecmp
726100e Take hashtable convenience functions into use
5e7744a Fix typo in maxadmin.md
c5778c8 Merge branch 'release-2.0.0' into develop
b5762af Move from tmpnam to mkstemp
d6f2c71 Add convenience functions to hashtable
359058a MXS-825: Add support for --execdir
636347c Enable runtime reconfiguration of log throttling
ef9fba9 Improve log throttling documentation
aef917a Implement log throttling
e3a5349 Remove shardrouter.c
8051e80 Remove custom qc_sqlite allocation functions
fd34d60 Initial implementation of the learning firewall
a8752a8 Removed "filestem option" from example
1ef2519 Removed "filestem option" from example
0815cc8 Cleanup spinlock.h
ab4dc99 Clean up hashtable.h
ef2c078 Add prototypes for hashtable copy and free functions
fb5cfaf Add 'log_throttling' configuration entry
300d823 Add proper prototypes for hashtable hash and cmp functions
1c649aa qc_mysqlembedded: Include skygw_...h without path.
d276160 Add missing RPM scripts
e70e644 Fix HTTPAuth installation
1b2b389 Combine utils into the server directory
3ff9913 Add missing utils headers to devel package
407efb2 Fix minor packaging problems
99aa6ad Split MaxScale into core, experimental and devel packages
1290386 Merge branch 'develop' of ssh://github.com/mariadb-corporation/maxscale-new into develop
e59f148 Make scripts POSIX sh compatible
7319266 Fixed SHOW SLAVE STATUS in bonlog router
f8d760a Update Binlogrouter.md
0a904ed Update Replication-Proxy-Binlog-Router-Tutorial.md
75d4202 Update Replication-Proxy-Binlog-Router-Tutorial.md
b8651fc Add missing newline in listmanager.h
c7ad047 Add note about user data caches to release notes
70ccc2b Merge branch 'release-2.0.0' into develop
575d1b6 Mistake - dummy session needs list markers set.
8364508 Merge branch 'develop' into binlog_server_semisync
868b902 Update MaxScale limitations
2c8b327 Store listener caches in separate directories
6e183ec Create unique user data caches for each listeners
f643685 Don't free orphaned tee filter sessions
4179afa Allow binlogrouter to be used without a listener
7ad79af Add function for freeing a listener
677a0a2 Move authentication data from services to listeners
4f12af7 Merge remote-tracking branch 'origin/MXS-677' into develop
1419b81 Semi-Sync support to binlog server: code review updtate
0ea0f01 Semi-Sync support to binlog server: added missing routine
4aad909 Semi-Sync support to binlog server
b824e1e Add authenticator support to httpd.c
705a688 Change tabs to spaces
d0c419e Change method of adding list fields to e.g. DCB
25504fc Document the changed routing priority of hints
41666d1 Remove use_ssl_if_enabled global option
a3584e9 Make routing hints have highest priority
34a1d24 Updated document with new binlog router option
01eedc5 Updated documentation with SSL usage
8a4c0f6 Update Replication-Proxy-Binlog-Router-Tutorial.md
4e374aa Update Replication-Proxy-Binlog-Router-Tutorial.md
f3f3c57 Update Replication-Proxy-Binlog-Router-Tutorial.md
617b79f Binlog Server: error messages typo fix
fa8dfae Binlog Server: error messages review
1b8819c Fix freeing of schemarouter session memory
07f49e1 MXS-788: new code review fix
1fd3b09 MXS-788: show services now displays SSL info
6ca2584 MXS-788 code review fix
ae6a7d0 MXS-788 code review
43d3474 Master server SSL connection
90b2377 Use correct variable in listmanager pre-allocation
9a5b238 Fix listmanager pre-allocation
9c78625 Fix a memory leak when backend authentication fails
e59a966 Fix hang in list_find_free
ff30223 Fix freeing of shared data in schemarouter
fc8f9d3 Add missing include in luafilter
ecf7f53 Add missing NULL value to filter parameter array
636d849 Update memory allocation approach
f0d1d38 Add new allocation functions
97d00a0 Fix writing of uninitialized data to logs
e72c9b2 Merge branch 'release-2.0.0' into develop
cf2b712 Merge branch 'release-2.0.0' into develop
8917c5c Change the logic behind valid list entry checks
c10deff Improve documentation about version_string
f59f1f7 Merge branch 'develop' of ssh://github.com/mariadb-corporation/maxscale-new into develop
c88edb3 Backend authentication failure improvement
abd5bee Revert "Backend authentication failure improvement"
5bb3107 Backend authentication failure improvement
b7f434a Add new allocation functions
3f022fa Fix stupid mistake
99c4317 Merge remote-tracking branch 'origin/MXS-677' into develop
3c1ded6 Added connection/authentication failure error reporting in SHOW SLAVE STATUS
0a60f7b Tidy up and deal with review points.
ba103ff blr_slave.c: Update strncpy usage
467331e blr_master.c: Strncpy usage updates
d2b7c0c Merge remote-tracking branch 'origin/develop-nullauth-merge' into develop
5a8c1d0 qc: Measure execution time at the right place.
bccdb93 Merge branch 'NullAuthDeny' into develop
2e6511c Add 5.5.5 prefix to all version strings that lack it
314655a Improve DCB and session initialization and list handling
e1c43f0 MXS-655: Make MaxScale logging logrotate(8) compatible
ce36afd MXS-626: Don't log a header unless maxlog enabled
dcd47a7 blr_file.c: Replace uses of strncpy
6b8f576 bls_slave.c: Replace strncpy with memcpy
68a0039 Add list preallocation, tidy up, simplify init.
cb37d1b Fix copyright etc headers.
11a400d Tidy; comment; fix bad copies and mistakes.
7e36ec4 Add list manager files.
c4794e3 Initial code for list manager.
1b42e25 Merge remote-tracking branch 'origin/MXS-765' into develop
d50f617 Fix problems, extend tests, respond to review.
dcb4a91 Filter test folder removed
0b60dbe Add a couple of comments.
83cdba0 Fix overwriting problem.
ba5d353 Fix overwriting problem.
53671cb Small fixes in response to review.
173d049 blr.c: Review strncpy usage
4ff6ef2 binlog_common.c: Replace strncpy with memcpy
f238e03 maxbinlogcheck.s: Replace strncpy
9807f8d harness: Replace unnecessary use of strncpy
8c7fe6a avro: Modify strncpy usage
9b8008e Small improvements.
b7f784f Fix mistakes in testqueuemanager.c
cc26962 Restore missing poll.c code; add testqueuemanager.c.
2e91806 Format the filter harness
22059e6 Initial implementation connection queueing.
c604dc2 readwritesplit.c: Improve COM_INIT_DB handling
454d920 schemarouter.c: Replace strncpy with strcpy
8e85d66 sharding_common.c: Too long a database name handled explicitly
77f4446 Astyle schemarouter
491f7c2 maxinfo.c: Replace strncpy with memcpy
6b98105 maxinfo: Reformat with astyle
c1dbf08 Handle oversize user and database names
5fa4a0f Merge branch 'develop' of ssh://github.com/mariadb-corporation/maxscale-new into develop
706963b BLR_DBUSERS_TAIL new var in blr.h
d75b9af Tweak comments, remove trailing blanks.
ab2400a Optimise statistics gathering by inline & simpler fns.
fb59ddc Remove unnecessary strncpy/strncat usage in Binlog Server
bdcd551 resultset.c: Change strncpy to memcpy
c6b1c5e Reject rather than cut too long a path
6d8f112 Remove unnecessary strncpy/strncat usage
18bf5ed Remove unnecessary strncpy usage
dc0e2db Make maxpasswd more userfriendly
c9c8695 Fix calculation of padded_len in encryptPassword
2cfd2c6 dbusers.c: Check strncpy usage
7ab9342 Make more thorough checks in secrets_readKeys
be7d593 Format cli.c debugcli.c testroute.c webserver.c
1ee5efb config.c: Check usage of strncpy
3043b12 gq_utils.c: Unnecessary use of strncpy removed
77874ac Add help to maxkeys
38392a3 Update secrets_writeKeys documentation
2d1325c Make SSL optional in MaxScale's own communication
bda00da Fix avro build failures
b2cb31a Add more OOM macros
41ccf17 Fix strdup usage
a48f732 Fix realloc calls
20771f6 Add forgotten extern "C" block
8faf35a Add maxscale allocation functions
bb47890 Add macros for OOM logging
afea388 Fix silly mistakes.
6dafd22 Make deny default for null auth; move code from common to auth.
2016-08-17 10:06:35 +03:00

5612 lines
176 KiB
C

/*
* Copyright (c) 2016 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/bsl.
*
* 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.
*/
/**
* @file blr_slave.c - contains code for the router to slave communication
*
* The binlog router is designed to be used in replication environments to
* increase the replication fanout of a master server. It provides a transparant
* mechanism to read the binlog entries for multiple slaves while requiring
* only a single connection to the actual master to support the slaves.
*
* The current prototype implement is designed to support MySQL 5.6 and has
* a number of limitations. This prototype is merely a proof of concept and
* should not be considered production ready.
*
* @verbatim
* Revision History
*
* Date Who Description
* 14/04/2014 Mark Riddoch Initial implementation
* 18/02/2015 Massimiliano Pinto Addition of DISCONNECT ALL and DISCONNECT SERVER server_id
* 18/03/2015 Markus Makela Better detection of CRC32 | NONE checksum
* 19/03/2015 Massimiliano Pinto Addition of basic MariaDB 10 compatibility support
* 07/05/2015 Massimiliano Pinto Added MariaDB 10 Compatibility
* 11/05/2015 Massimiliano Pinto Only MariaDB 10 Slaves can register to binlog router
* with a MariaDB 10 Master
* 25/05/2015 Massimiliano Pinto Addition of BLRM_SLAVE_STOPPED state and blr_start/stop_slave.
* New commands STOP SLAVE, START SLAVE added.
* 29/05/2015 Massimiliano Pinto Addition of CHANGE MASTER TO ...
* 05/06/2015 Massimiliano Pinto router->service->dbref->sever->name instead of master->remote
* in blr_slave_send_slave_status()
* 08/06/2015 Massimiliano Pinto blr_slave_send_slave_status() shows mysql_errno and error_msg
* 15/06/2015 Massimiliano Pinto Added constraints to CHANGE MASTER TO MASTER_LOG_FILE/POS
* 23/06/2015 Massimiliano Pinto Added utility routines for blr_handle_change_master
* Call create/use binlog in blr_start_slave() (START SLAVE)
* 29/06/2015 Massimiliano Pinto Successfully CHANGE MASTER results in updating master.ini
* in blr_handle_change_master()
* 20/08/2015 Massimiliano Pinto Added parsing and validation for CHANGE MASTER TO
* 21/08/2015 Massimiliano Pinto Added support for new config options:
* master_uuid, master_hostname, master_version
* If set those values are sent to slaves instead of
* saved master responses
* 03/09/2015 Massimiliano Pinto Added support for SHOW [GLOBAL] VARIABLES LIKE
* 04/09/2015 Massimiliano Pinto Added support for SHOW WARNINGS
* 15/09/2015 Massimiliano Pinto Added support for SHOW [GLOBAL] STATUS LIKE 'Uptime'
* 25/09/2015 Massimiliano Pinto Addition of slave heartbeat:
* the period set during registration is checked
* and heartbeat event might be sent to the affected slave.
* 25/09/2015 Martin Brampton Block callback processing when no router session in the DCB
* 23/10/2015 Markus Makela Added current_safe_event
* 09/05/2016 Massimiliano Pinto Added SELECT USER()
* 11/07/2016 Massimiliano Pinto Added SSL backend support
*
* @endverbatim
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <maxscale.h>
#include <service.h>
#include <server.h>
#include <router.h>
#include <atomic.h>
#include <spinlock.h>
#include <blr.h>
#include <dcb.h>
#include <spinlock.h>
#include <housekeeper.h>
#include <sys/stat.h>
#include <skygw_types.h>
#include <skygw_utils.h>
#include <log_manager.h>
#include <version.h>
#include <zlib.h>
#include <maxscale/alloc.h>
#include <gw.h>
extern int load_mysql_users(SERV_LISTENER *listener);
extern void blr_master_close(ROUTER_INSTANCE* router);
extern int blr_file_new_binlog(ROUTER_INSTANCE *router, char *file);
extern int blr_file_write_master_config(ROUTER_INSTANCE *router, char *error);
extern char *blr_extract_column(GWBUF *buf, int col);
extern uint32_t extract_field(uint8_t *src, int bits);
int blr_file_get_next_binlogname(ROUTER_INSTANCE *router);
static void encode_value(unsigned char *data, unsigned int value, int len);
static int blr_slave_query(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue);
static int blr_slave_replay(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *master);
static void blr_slave_send_error(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, char *msg);
static int blr_slave_send_timestamp(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave);
static int blr_slave_register(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue);
static int blr_slave_binlog_dump(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue);
int blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large);
uint8_t *blr_build_header(GWBUF *pkt, REP_HEADER *hdr);
int blr_slave_callback(DCB *dcb, DCB_REASON reason, void *data);
static int blr_slave_fake_rotate(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, BLFILE** filep);
static void blr_slave_send_fde(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave);
static int blr_slave_send_maxscale_version(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave);
static int blr_slave_send_server_id(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave);
static int blr_slave_send_maxscale_variables(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave);
static int blr_slave_send_master_status(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave);
static int blr_slave_send_slave_status(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave);
static int blr_slave_send_slave_hosts(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave);
static int blr_slave_send_fieldcount(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, int count);
static int blr_slave_send_columndef(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, char *name, int type,
int len, uint8_t seqno);
static int blr_slave_send_eof(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, int seqno);
static int blr_slave_send_disconnected_server(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, int server_id,
int found);
static int blr_slave_disconnect_all(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave);
static int blr_slave_disconnect_server(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, int server_id);
static int blr_slave_send_ok(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave);
static int blr_stop_slave(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave);
static int blr_start_slave(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave);
static void blr_slave_send_error_packet(ROUTER_SLAVE *slave, char *msg, unsigned int err_num, char *status);
static int blr_handle_change_master(ROUTER_INSTANCE* router, char *command, char *error);
static int blr_set_master_hostname(ROUTER_INSTANCE *router, char *hostname);
static int blr_set_master_port(ROUTER_INSTANCE *router, char *command);
static char *blr_set_master_logfile(ROUTER_INSTANCE *router, char *filename, char *error);
static void blr_master_get_config(ROUTER_INSTANCE *router, MASTER_SERVER_CFG *current_master);
static void blr_master_free_config(MASTER_SERVER_CFG *current_master);
static void blr_master_restore_config(ROUTER_INSTANCE *router, MASTER_SERVER_CFG *current_master);
static void blr_master_set_empty_config(ROUTER_INSTANCE *router);
static void blr_master_apply_config(ROUTER_INSTANCE *router, MASTER_SERVER_CFG *prev_master);
static int blr_slave_send_ok_message(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave, char *message);
static char *blr_get_parsed_command_value(char *input);
static char **blr_validate_change_master_option(char *option, CHANGE_MASTER_OPTIONS *config);
static int blr_set_master_user(ROUTER_INSTANCE *router, char *user);
static int blr_set_master_password(ROUTER_INSTANCE *router, char *password);
static int blr_parse_change_master_command(char *input, char *error_string, CHANGE_MASTER_OPTIONS *config);
static int blr_handle_change_master_token(char *input, char *error, CHANGE_MASTER_OPTIONS *config);
static void blr_master_free_parsed_options(CHANGE_MASTER_OPTIONS *options);
static int blr_slave_send_var_value(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, char *variable, char *value,
int column_type);
static int blr_slave_send_variable(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, char *variable, char *value,
int column_type);
static int blr_slave_send_columndef_with_info_schema(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, char *name,
int type, int len, uint8_t seqno);
int blr_test_parse_change_master_command(char *input, char *error_string, CHANGE_MASTER_OPTIONS *config);
char *blr_test_set_master_logfile(ROUTER_INSTANCE *router, char *filename, char *error);
static int blr_slave_handle_variables(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, char *stmt);
static int blr_slave_send_warning_message(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave, char *message);
static int blr_slave_show_warnings(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave);
static int blr_slave_send_status_variable(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, char *variable,
char *value, int column_type);
static int blr_slave_handle_status_variables(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, char *stmt);
static int blr_slave_send_columndef_with_status_schema(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave,
char *name, int type, int len, uint8_t seqno);
static void blr_send_slave_heartbeat(void *inst);
static int blr_slave_send_heartbeat(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave);
static int blr_set_master_ssl(ROUTER_INSTANCE *router, CHANGE_MASTER_OPTIONS config, char *error_message);
void poll_fake_write_event(DCB *dcb);
/**
* Process a request packet from the slave server.
*
* The router can handle a limited subset of requests from the slave, these
* include a subset of general SQL queries, a slave registeration command and
* the binlog dump command.
*
* The strategy for responding to these commands is to use caches responses
* for the the same commands that have previously been made to the real master
* if this is possible, if it is not then the router itself will synthesize a
* response.
*
* @param router The router instance this defines the master for this replication chain
* @param slave The slave specific data
* @param queue The incoming request packet
*/
int
blr_slave_request(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
{
if (slave->state < 0 || slave->state > BLRS_MAXSTATE)
{
MXS_ERROR("Invalid slave state machine state (%d) for binlog router.",
slave->state);
gwbuf_consume(queue, gwbuf_length(queue));
return 0;
}
slave->stats.n_requests++;
switch (MYSQL_COMMAND(queue))
{
case COM_QUERY:
slave->stats.n_queries++;
return blr_slave_query(router, slave, queue);
case COM_REGISTER_SLAVE:
if (router->master_state == BLRM_UNCONFIGURED)
{
slave->state = BLRS_ERRORED;
blr_slave_send_error_packet(slave,
"Binlog router is not yet configured for replication",
(unsigned int) 1597, NULL);
MXS_ERROR("%s: Slave %s: Binlog router is not yet configured for replication",
router->service->name,
slave->dcb->remote);
dcb_close(slave->dcb);
return 1;
}
/*
* If Master is MariaDB10 don't allow registration from
* MariaDB/Mysql 5 Slaves
*/
if (router->mariadb10_compat && !slave->mariadb10_compat)
{
slave->state = BLRS_ERRORED;
blr_send_custom_error(slave->dcb, 1, 0,
"MariaDB 10 Slave is required for Slave registration", "42000", 1064);
MXS_ERROR("%s: Slave %s: a MariaDB 10 Slave is required for Slave registration",
router->service->name,
slave->dcb->remote);
dcb_close(slave->dcb);
return 1;
}
else
{
/* Master and Slave version OK: continue with slave registration */
return blr_slave_register(router, slave, queue);
}
case COM_BINLOG_DUMP:
{
char task_name[BLRM_TASK_NAME_LEN + 1] = "";
int rc = 0;
rc = blr_slave_binlog_dump(router, slave, queue);
if (router->send_slave_heartbeat && rc && slave->heartbeat > 0)
{
snprintf(task_name, BLRM_TASK_NAME_LEN, "%s slaves heartbeat send", router->service->name);
/* Add slave heartbeat check task: it runs with 1 second frequency */
hktask_add(task_name, blr_send_slave_heartbeat, router, 1);
}
return rc;
}
case COM_STATISTICS:
return blr_statistics(router, slave, queue);
case COM_PING:
return blr_ping(router, slave, queue);
case COM_QUIT:
MXS_DEBUG("COM_QUIT received from slave with server_id %d",
slave->serverid);
break;
default:
blr_send_custom_error(slave->dcb, 1, 0,
"You have an error in your SQL syntax; Check the "
"syntax the MaxScale binlog router accepts.",
"42000", 1064);
MXS_ERROR("Unexpected MySQL Command (%d) received from slave",
MYSQL_COMMAND(queue));
break;
}
return 0;
}
/**
* Handle a query from the slave. This is expected to be one of the "standard"
* queries we expect as part of the registraton process. Most of these can
* be dealt with by replying the stored responses we got from the master
* when MaxScale registered as a slave. The exception to the rule is the
* request to obtain the current timestamp value of the server.
*
* The original set added for the registration process has been enhanced in
* order to support some commands that are useful for monitoring the binlog
* router.
*
* Thirteen select statements are currently supported:
* SELECT UNIX_TIMESTAMP();
* SELECT @master_binlog_checksum
* SELECT @@GLOBAL.GTID_MODE
* SELECT VERSION()
* SELECT 1
* SELECT @@version_comment limit 1
* SELECT @@hostname
* SELECT @@max_allowed_packet
* SELECT @@maxscale_version
* SELECT @@[GLOBAL.]server_id
* SELECT @@version
* SELECT @@[GLOBAL.]server_uuid
* SELECT USER()
*
* Eight show commands are supported:
* SHOW [GLOBAL] VARIABLES LIKE 'SERVER_ID'
* SHOW [GLOBAL] VARIABLES LIKE 'SERVER_UUID'
* SHOW [GLOBAL] VARIABLES LIKE 'MAXSCALE%'
* SHOW SLAVE STATUS
* SHOW MASTER STATUS
* SHOW SLAVE HOSTS
* SHOW WARNINGS
* SHOW [GLOBAL] STATUS LIKE 'Uptime'
*
* Nine set commands are supported:
* SET @master_binlog_checksum = @@global.binlog_checksum
* SET @master_heartbeat_period=...
* SET @slave_slave_uuid=...
* SET NAMES latin1
* SET NAMES utf8
* SET NAMES XXX
* SET mariadb_slave_capability=...
* SET autocommit=
* SET @@session.autocommit=
*
* Four administrative commands are supported:
* STOP SLAVE
* START SLAVE
* CHANGE MASTER TO
* RESET SLAVE
*
* @param router The router instance this defines the master for this replication chain
* @param slave The slave specific data
* @param queue The incoming request packet
* @return Non-zero if data has been sent
*/
static int
blr_slave_query(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
{
char *qtext, *query_text;
char *sep = " ,=";
char *word, *brkb;
int query_len;
char *ptr;
extern char *strcasestr();
qtext = GWBUF_DATA(queue);
query_len = extract_field((uint8_t *)qtext, 24) - 1;
qtext += 5; // Skip header and first byte of the payload
query_text = strndup(qtext, query_len);
/* Don't log the full statement containg 'password', just trucate it */
ptr = strcasestr(query_text, "password");
if (ptr != NULL)
{
char *new_text = MXS_STRDUP_A(query_text);
int trucate_at = (ptr - query_text);
if (trucate_at > 0)
{
if ( (trucate_at + 3) <= (int)strlen(new_text))
{
int i;
for (i = 0; i < 3; i++)
{
new_text[trucate_at + i] = '.';
}
new_text[trucate_at + 3] = '\0';
}
else
{
new_text[trucate_at] = '\0';
}
}
MXS_INFO("Execute statement (truncated, it contains password)"
" from the slave '%s'", new_text);
MXS_FREE(new_text);
}
else
{
MXS_INFO("Execute statement from the slave '%s'", query_text);
}
/*
* Implement a very rudimental "parsing" of the query text by extarcting the
* words from the statement and matchng them against the subset of queries we
* are expecting from the slave. We already have responses to these commands,
* except for the select of UNIX_TIMESTAMP(), that we have saved from MaxScale's
* own interaction with the real master. We simply replay these saved responses
* to the slave.
*/
if ((word = strtok_r(query_text, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Incomplete query.", router->service->name);
}
else if (strcasecmp(word, "SELECT") == 0)
{
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Incomplete select query.", router->service->name);
}
else if (strcasecmp(word, "UNIX_TIMESTAMP()") == 0)
{
MXS_FREE(query_text);
return blr_slave_send_timestamp(router, slave);
}
else if (strcasecmp(word, "@master_binlog_checksum") == 0)
{
MXS_FREE(query_text);
return blr_slave_replay(router, slave, router->saved_master.chksum2);
}
else if (strcasecmp(word, "@@GLOBAL.GTID_MODE") == 0)
{
MXS_FREE(query_text);
return blr_slave_replay(router, slave, router->saved_master.gtid_mode);
}
else if (strcasecmp(word, "1") == 0)
{
MXS_FREE(query_text);
return blr_slave_replay(router, slave, router->saved_master.select1);
}
else if (strcasecmp(word, "VERSION()") == 0)
{
MXS_FREE(query_text);
if (router->set_master_version)
{
return blr_slave_send_var_value(router, slave, "VERSION()",
router->set_master_version, BLR_TYPE_STRING);
}
else
{
return blr_slave_replay(router, slave, router->saved_master.selectver);
}
}
else if (strcasecmp(word, "USER()") == 0)
{
/* Return user@host */
char user_host[MYSQL_USER_MAXLEN + 1 + MYSQL_HOSTNAME_MAXLEN + 1] = "";
MXS_FREE(query_text);
snprintf(user_host, sizeof(user_host),
"%s@%s", slave->dcb->user, slave->dcb->remote);
return blr_slave_send_var_value(router, slave, "USER()",
user_host, BLR_TYPE_STRING);
}
else if (strcasecmp(word, "@@version") == 0)
{
MXS_FREE(query_text);
if (router->set_master_version)
{
return blr_slave_send_var_value(router, slave, "@@version",
router->set_master_version, BLR_TYPE_STRING);
}
else
{
char *version = blr_extract_column(router->saved_master.selectver, 1);
blr_slave_send_var_value(router, slave, "@@version",
version == NULL ? "" : version, BLR_TYPE_STRING);
MXS_FREE(version);
return 1;
}
}
else if (strcasecmp(word, "@@version_comment") == 0)
{
MXS_FREE(query_text);
if (!router->saved_master.selectvercom)
/* This will allow mysql client to get in when @@version_comment is not available */
{
return blr_slave_send_ok(router, slave);
}
else
{
return blr_slave_replay(router, slave, router->saved_master.selectvercom);
}
}
else if (strcasecmp(word, "@@hostname") == 0)
{
MXS_FREE(query_text);
if (router->set_master_hostname)
{
return blr_slave_send_var_value(router, slave, "@@hostname",
router->set_master_hostname, BLR_TYPE_STRING);
}
else
{
return blr_slave_replay(router, slave, router->saved_master.selecthostname);
}
}
else if ((strcasecmp(word, "@@server_uuid") == 0) || (strcasecmp(word, "@@global.server_uuid") == 0))
{
char heading[40]; /* to ensure we match the case in query and response */
strcpy(heading, word);
MXS_FREE(query_text);
if (router->set_master_uuid)
{
return blr_slave_send_var_value(router, slave, heading, router->master_uuid, BLR_TYPE_STRING);
}
else
{
char *master_uuid = blr_extract_column(router->saved_master.uuid, 2);
blr_slave_send_var_value(router, slave, heading,
master_uuid == NULL ? "" : master_uuid, BLR_TYPE_STRING);
MXS_FREE(master_uuid);
return 1;
}
}
else if (strcasecmp(word, "@@max_allowed_packet") == 0)
{
MXS_FREE(query_text);
return blr_slave_replay(router, slave, router->saved_master.map);
}
else if (strcasecmp(word, "@@maxscale_version") == 0)
{
MXS_FREE(query_text);
return blr_slave_send_maxscale_version(router, slave);
}
else if ((strcasecmp(word, "@@server_id") == 0) || (strcasecmp(word, "@@global.server_id") == 0))
{
char server_id[40];
char heading[40]; /* to ensure we match the case in query and response */
sprintf(server_id, "%d", router->masterid);
strcpy(heading, word);
MXS_FREE(query_text);
return blr_slave_send_var_value(router, slave, heading, server_id, BLR_TYPE_INT);
}
}
else if (strcasecmp(word, "SHOW") == 0)
{
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Incomplete show query.",
router->service->name);
}
else if (strcasecmp(word, "WARNINGS") == 0)
{
MXS_FREE(query_text);
return blr_slave_show_warnings(router, slave);
}
else if (strcasecmp(word, "GLOBAL") == 0)
{
if (router->master_state == BLRM_UNCONFIGURED)
{
MXS_FREE(query_text);
return blr_slave_send_ok(router, slave);
}
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Expected VARIABLES in SHOW GLOBAL",
router->service->name);
}
else if (strcasecmp(word, "VARIABLES") == 0)
{
int rc = blr_slave_handle_variables(router, slave, brkb);
/* if no var found, send empty result set */
if (rc == 0)
{
blr_slave_send_ok(router, slave);
}
if (rc >= 0)
{
MXS_FREE(query_text);
return 1;
}
else
MXS_ERROR("%s: Expected LIKE clause in SHOW GLOBAL VARIABLES.",
router->service->name);
}
else if (strcasecmp(word, "STATUS") == 0)
{
int rc = blr_slave_handle_status_variables(router, slave, brkb);
/* if no var found, send empty result set */
if (rc == 0)
{
blr_slave_send_ok(router, slave);
}
if (rc >= 0)
{
MXS_FREE(query_text);
return 1;
}
else
{
MXS_ERROR("%s: Expected LIKE clause in SHOW GLOBAL STATUS.",
router->service->name);
}
}
}
else if (strcasecmp(word, "VARIABLES") == 0)
{
int rc;
if (router->master_state == BLRM_UNCONFIGURED)
{
MXS_FREE(query_text);
return blr_slave_send_ok(router, slave);
}
rc = blr_slave_handle_variables(router, slave, brkb);
/* if no var found, send empty result set */
if (rc == 0)
{
blr_slave_send_ok(router, slave);
}
if (rc >= 0)
{
MXS_FREE(query_text);
return 1;
}
else
MXS_ERROR("%s: Expected LIKE clause in SHOW VARIABLES.",
router->service->name);
}
else if (strcasecmp(word, "MASTER") == 0)
{
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Expected SHOW MASTER STATUS command",
router->service->name);
}
else if (strcasecmp(word, "STATUS") == 0)
{
MXS_FREE(query_text);
/* if state is BLRM_UNCONFIGURED return empty result */
if (router->master_state > BLRM_UNCONFIGURED)
{
return blr_slave_send_master_status(router, slave);
}
else
{
return blr_slave_send_ok(router, slave);
}
}
}
else if (strcasecmp(word, "SLAVE") == 0)
{
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Expected SHOW SLAVE STATUS command",
router->service->name);
}
else if (strcasecmp(word, "STATUS") == 0)
{
MXS_FREE(query_text);
/* if state is BLRM_UNCONFIGURED return empty result */
if (router->master_state > BLRM_UNCONFIGURED)
{
return blr_slave_send_slave_status(router, slave);
}
else
{
return blr_slave_send_ok(router, slave);
}
}
else if (strcasecmp(word, "HOSTS") == 0)
{
MXS_FREE(query_text);
/* if state is BLRM_UNCONFIGURED return empty result */
if (router->master_state > BLRM_UNCONFIGURED)
{
return blr_slave_send_slave_hosts(router, slave);
}
else
{
return blr_slave_send_ok(router, slave);
}
}
}
else if (strcasecmp(word, "STATUS") == 0)
{
int rc = blr_slave_handle_status_variables(router, slave, brkb);
/* if no var found, send empty result set */
if (rc == 0)
{
blr_slave_send_ok(router, slave);
}
if (rc >= 0)
{
MXS_FREE(query_text);
return 1;
}
else
{
MXS_ERROR("%s: Expected LIKE clause in SHOW STATUS.", router->service->name);
}
}
}
else if (strcasecmp(query_text, "SET") == 0)
{
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Incomplete set command.", router->service->name);
}
else if ((strcasecmp(word, "autocommit") == 0) || (strcasecmp(word, "@@session.autocommit") == 0))
{
/* return OK */
MXS_FREE(query_text);
return blr_slave_send_ok(router, slave);
}
else if (strcasecmp(word, "@master_heartbeat_period") == 0)
{
int v_len = 0;
word = strtok_r(NULL, sep, &brkb);
if (word)
{
char *new_val;
v_len = strlen(word);
if (v_len > 6)
{
new_val = strndup(word, v_len - 6);
slave->heartbeat = atoi(new_val) / 1000;
}
else
{
new_val = strndup(word, v_len);
slave->heartbeat = atoi(new_val) / 1000000;
}
MXS_FREE(new_val);
}
MXS_FREE(query_text);
return blr_slave_replay(router, slave, router->saved_master.heartbeat);
}
else if (strcasecmp(word, "@mariadb_slave_capability") == 0)
{
/* mariadb10 compatibility is set for the slave */
slave->mariadb10_compat = true;
MXS_FREE(query_text);
if (router->mariadb10_compat)
{
return blr_slave_replay(router, slave, router->saved_master.mariadb10);
}
else
{
return blr_slave_send_ok(router, slave);
}
}
else if (strcasecmp(word, "@master_binlog_checksum") == 0)
{
word = strtok_r(NULL, sep, &brkb);
if (word && (strcasecmp(word, "'none'") == 0))
{
slave->nocrc = 1;
}
else if (word && (strcasecmp(word, "@@global.binlog_checksum") == 0))
{
slave->nocrc = !router->master_chksum;
}
else
{
slave->nocrc = 0;
}
MXS_FREE(query_text);
return blr_slave_replay(router, slave, router->saved_master.chksum1);
}
else if (strcasecmp(word, "@slave_uuid") == 0)
{
if ((word = strtok_r(NULL, sep, &brkb)) != NULL)
{
int len = strlen(word);
char *word_ptr = word;
if (len)
{
if (word[len - 1] == '\'')
{
word[len - 1] = '\0';
}
if (word[0] == '\'')
{
word[0] = '\0';
word_ptr++;
}
}
slave->uuid = MXS_STRDUP_A(word_ptr);
}
MXS_FREE(query_text);
return blr_slave_replay(router, slave, router->saved_master.setslaveuuid);
}
else if (strcasecmp(word, "NAMES") == 0)
{
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Truncated SET NAMES command.", router->service->name);
}
else if (strcasecmp(word, "latin1") == 0)
{
MXS_FREE(query_text);
return blr_slave_replay(router, slave, router->saved_master.setnames);
}
else if (strcasecmp(word, "utf8") == 0)
{
MXS_FREE(query_text);
return blr_slave_replay(router, slave, router->saved_master.utf8);
}
else
{
MXS_FREE(query_text);
return blr_slave_send_ok(router, slave);
}
}
} /* RESET current configured master */
else if (strcasecmp(query_text, "RESET") == 0)
{
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Incomplete RESET command.", router->service->name);
}
else if (strcasecmp(word, "SLAVE") == 0)
{
MXS_FREE(query_text);
if (router->master_state == BLRM_SLAVE_STOPPED)
{
char error_string[BINLOG_ERROR_MSG_LEN + 1] = "";
MASTER_SERVER_CFG *current_master = NULL;
int removed_cfg = 0;
/* save current replication parameters */
current_master = (MASTER_SERVER_CFG *)MXS_CALLOC(1, sizeof(MASTER_SERVER_CFG));
MXS_ABORT_IF_NULL(current_master);
if (!current_master)
{
snprintf(error_string,
BINLOG_ERROR_MSG_LEN, "error allocating memory for blr_master_get_config");
MXS_ERROR("%s: %s", router->service->name, error_string);
blr_slave_send_error_packet(slave, error_string, (unsigned int)1201, NULL);
return 1;
}
/* get current data */
blr_master_get_config(router, current_master);
MXS_NOTICE("%s: 'RESET SLAVE executed'. Previous state MASTER_HOST='%s', "
"MASTER_PORT=%i, MASTER_LOG_FILE='%s', MASTER_LOG_POS=%lu, "
"MASTER_USER='%s'",
router->service->name,
current_master->host,
current_master->port,
current_master->logfile,
current_master->pos,
current_master->user);
/* remove master.ini */
static const char MASTER_INI[] = "/master.ini";
char path[strlen(router->binlogdir) + sizeof(MASTER_INI)];
strcpy(path, router->binlogdir);
strcat(path, MASTER_INI);
/* remove master.ini */
removed_cfg = unlink(path);
if (removed_cfg == -1)
{
char err_msg[STRERROR_BUFLEN];
snprintf(error_string, BINLOG_ERROR_MSG_LEN,
"Error removing %s, %s, errno %u", path,
strerror_r(errno, err_msg, sizeof(err_msg)), errno);
MXS_ERROR("%s: %s", router->service->name, error_string);
}
spinlock_acquire(&router->lock);
router->master_state = BLRM_UNCONFIGURED;
blr_master_set_empty_config(router);
blr_master_free_config(current_master);
spinlock_release(&router->lock);
if (removed_cfg == -1)
{
blr_slave_send_error_packet(slave, error_string, (unsigned int)1201, NULL);
return 1;
}
else
{
return blr_slave_send_ok(router, slave);
}
}
else
{
if (router->master_state == BLRM_UNCONFIGURED)
{
blr_slave_send_ok(router, slave);
}
else
{
blr_slave_send_error_packet(slave,
"This operation cannot be performed "
"with a running slave; run STOP SLAVE first",
(unsigned int)1198, NULL);
}
return 1;
}
}
}
/* start replication from the current configured master */
else if (strcasecmp(query_text, "START") == 0)
{
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Incomplete START command.",
router->service->name);
}
else if (strcasecmp(word, "SLAVE") == 0)
{
MXS_FREE(query_text);
return blr_start_slave(router, slave);
}
}
/* stop replication from the current master*/
else if (strcasecmp(query_text, "STOP") == 0)
{
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Incomplete STOP command.", router->service->name);
}
else if (strcasecmp(word, "SLAVE") == 0)
{
MXS_FREE(query_text);
return blr_stop_slave(router, slave);
}
}
/* Change the server to replicate from */
else if (strcasecmp(query_text, "CHANGE") == 0)
{
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Incomplete CHANGE command.", router->service->name);
}
else if (strcasecmp(word, "MASTER") == 0)
{
if (router->master_state != BLRM_SLAVE_STOPPED && router->master_state != BLRM_UNCONFIGURED)
{
MXS_FREE(query_text);
blr_slave_send_error_packet(slave, "Cannot change master with a running slave; "
"run STOP SLAVE first",
(unsigned int)1198, NULL);
return 1;
}
else
{
int rc;
char error_string[BINLOG_ERROR_MSG_LEN + 1] = "";
MASTER_SERVER_CFG *current_master = NULL;
current_master = (MASTER_SERVER_CFG *)MXS_CALLOC(1, sizeof(MASTER_SERVER_CFG));
if (!current_master)
{
MXS_FREE(query_text);
blr_slave_send_error_packet(slave, error_string, (unsigned int)1201, NULL);
return 1;
}
blr_master_get_config(router, current_master);
rc = blr_handle_change_master(router, brkb, error_string);
MXS_FREE(query_text);
if (rc < 0)
{
/* CHANGE MASTER TO has failed */
blr_slave_send_error_packet(slave, error_string, (unsigned int)1234, "42000");
blr_master_free_config(current_master);
return 1;
}
else
{
int ret;
char error[BINLOG_ERROR_MSG_LEN + 1];
/* Write/Update master config into master.ini file */
ret = blr_file_write_master_config(router, error);
if (ret)
{
/* file operation failure: restore config */
spinlock_acquire(&router->lock);
blr_master_apply_config(router, current_master);
blr_master_free_config(current_master);
spinlock_release(&router->lock);
snprintf(error_string, BINLOG_ERROR_MSG_LEN,
"Error writing into %s/master.ini: %s", router->binlogdir,
error);
MXS_ERROR("%s: %s",
router->service->name, error_string);
blr_slave_send_error_packet(slave, error_string, (unsigned int)1201, NULL);
return 1;
}
/**
* check if router is BLRM_UNCONFIGURED
* and change state to BLRM_SLAVE_STOPPED
*/
if (rc == 1 || router->master_state == BLRM_UNCONFIGURED)
{
spinlock_acquire(&router->lock);
router->master_state = BLRM_SLAVE_STOPPED;
spinlock_release(&router->lock);
}
if (!router->trx_safe)
{
blr_master_free_config(current_master);
}
if (router->trx_safe && router->pending_transaction)
{
if (strcmp(router->binlog_name, router->prevbinlog) != 0)
{
char message[BINLOG_ERROR_MSG_LEN + 1] = "";
snprintf(message, BINLOG_ERROR_MSG_LEN,
"1105:Partial transaction in file %s starting at pos %lu, "
"ending at pos %lu will be lost with next START SLAVE command",
current_master->logfile, current_master->safe_pos, current_master->pos);
blr_master_free_config(current_master);
return blr_slave_send_warning_message(router, slave, message);
}
else
{
blr_master_free_config(current_master);
return blr_slave_send_ok(router, slave);
}
}
else
{
return blr_slave_send_ok(router, slave);
}
}
}
}
}
else if (strcasecmp(query_text, "DISCONNECT") == 0)
{
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Incomplete DISCONNECT command.", router->service->name);
}
else if (strcasecmp(word, "ALL") == 0)
{
MXS_FREE(query_text);
return blr_slave_disconnect_all(router, slave);
}
else if (strcasecmp(word, "SERVER") == 0)
{
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Expected DISCONNECT SERVER $server_id",
router->service->name);
}
else
{
int serverid = atoi(word);
MXS_FREE(query_text);
return blr_slave_disconnect_server(router, slave, serverid);
}
}
}
MXS_FREE(query_text);
query_text = strndup(qtext, query_len);
MXS_ERROR("Unexpected query from '%s'@'%s': %s", slave->dcb->user, slave->dcb->remote, query_text);
MXS_FREE(query_text);
blr_slave_send_error(router, slave,
"You have an error in your SQL syntax; Check the syntax "
"the MaxScale binlog router accepts.");
return 1;
}
/**
* Send a reply to a command we have received from the slave. The reply itself
* is merely a copy of a previous message we received from the master when we
* registered as a slave. Hence we just replay this saved reply.
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @param master The saved master response
* @return Non-zero if data was sent
*/
static int
blr_slave_replay(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *master)
{
GWBUF *clone;
if (router->master_state == BLRM_UNCONFIGURED)
{
return blr_slave_send_ok(router, slave);
}
if (!master)
{
return 0;
}
if ((clone = gwbuf_clone(master)) != NULL)
{
return slave->dcb->func.write(slave->dcb, clone);
}
else
{
MXS_ERROR("Failed to clone server response to send to slave.");
return 0;
}
}
/**
* Construct an error response
*
* @param router The router instance
* @param slave The slave server instance
* @param msg The error message to send
*/
static void
blr_slave_send_error(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, char *msg)
{
GWBUF *pkt;
unsigned char *data;
int len;
if ((pkt = gwbuf_alloc(strlen(msg) + 13)) == NULL)
{
return;
}
data = GWBUF_DATA(pkt);
len = strlen(msg) + 9;
encode_value(&data[0], len, 24); // Payload length
data[3] = 1; // Sequence id
// Payload
data[4] = 0xff; // Error indicator
encode_value(&data[5], 1064, 16);// Error Code
memcpy((char *)&data[7], "#42000", 6);
memcpy(&data[13], msg, strlen(msg)); // Error Message
slave->dcb->func.write(slave->dcb, pkt);
}
/*
* Some standard packets that have been captured from a network trace of server
* interactions. These packets are the schema definition sent in response to
* a SELECT UNIX_TIMESTAMP() statement and the EOF packet that marks the end
* of transmission of the result set.
*/
static uint8_t timestamp_def[] =
{
0x01, 0x00, 0x00, 0x01, 0x01, 0x26, 0x00, 0x00, 0x02, 0x03, 0x64, 0x65, 0x66, 0x00, 0x00, 0x00,
0x10, 0x55, 0x4e, 0x49, 0x58, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x53, 0x54, 0x41, 0x4d, 0x50, 0x28,
0x29, 0x00, 0x0c, 0x3f, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x08, 0x81, 0x00, 0x00, 0x00, 0x00, 0x05,
0x00, 0x00, 0x03, 0xfe, 0x00, 0x00, 0x02, 0x00
};
static uint8_t timestamp_eof[] = { 0x05, 0x00, 0x00, 0x05, 0xfe, 0x00, 0x00, 0x02, 0x00 };
/**
* Send a response to a "SELECT UNIX_TIMESTAMP()" request. This differs from the other
* requests since we do not save a copy of the original interaction with the master
* and simply replay it. We want to always send the current time. We have stored a typcial
* response, which gives us the schema information normally returned. This is sent to the
* client and then we add a dynamic part that will insert the current timestamp data.
* Finally we send a preprepaed EOF packet to end the response stream.
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @return Non-zero if data was sent
*/
static int
blr_slave_send_timestamp(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
{
GWBUF *pkt;
char timestamp[20];
uint8_t *ptr;
int len, ts_len;
sprintf(timestamp, "%ld", time(0));
ts_len = strlen(timestamp);
len = sizeof(timestamp_def) + sizeof(timestamp_eof) + 5 + ts_len;
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
memcpy(ptr, timestamp_def, sizeof(timestamp_def)); // Fixed preamble
ptr += sizeof(timestamp_def);
encode_value(ptr, ts_len + 1, 24); // Add length of data packet
ptr += 3;
*ptr++ = 0x04; // Sequence number in response
*ptr++ = ts_len; // Length of result string
memcpy((char *)ptr, timestamp, ts_len); // Result string
ptr += ts_len;
memcpy(ptr, timestamp_eof, sizeof(timestamp_eof)); // EOF packet to terminate result
return slave->dcb->func.write(slave->dcb, pkt);
}
/**
* Send a response the the SQL command SELECT @@MAXSCALE_VERSION
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @return Non-zero if data was sent
*/
static int
blr_slave_send_maxscale_version(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
{
GWBUF *pkt;
char version[80] = "";
uint8_t *ptr;
int len, vers_len;
sprintf(version, "%s", MAXSCALE_VERSION);
vers_len = strlen(version);
blr_slave_send_fieldcount(router, slave, 1);
blr_slave_send_columndef(router, slave, "MAXSCALE_VERSION", BLR_TYPE_STRING, vers_len, 2);
blr_slave_send_eof(router, slave, 3);
len = 5 + vers_len;
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, vers_len + 1, 24); // Add length of data packet
ptr += 3;
*ptr++ = 0x04; // Sequence number in response
*ptr++ = vers_len; // Length of result string
memcpy((char *)ptr, version, vers_len); // Result string
/* ptr += vers_len; Not required unless more data is to be added */
slave->dcb->func.write(slave->dcb, pkt);
return blr_slave_send_eof(router, slave, 5);
}
/**
* Send a response the the SQL command SELECT @@server_id
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @return Non-zero if data was sent
*/
static int
blr_slave_send_server_id(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
{
GWBUF *pkt;
char server_id[40];
uint8_t *ptr;
int len, id_len;
sprintf(server_id, "%d", router->masterid);
id_len = strlen(server_id);
blr_slave_send_fieldcount(router, slave, 1);
blr_slave_send_columndef(router, slave, "SERVER_ID", BLR_TYPE_INT, id_len, 2);
blr_slave_send_eof(router, slave, 3);
len = 5 + id_len;
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, id_len + 1, 24); // Add length of data packet
ptr += 3;
*ptr++ = 0x04; // Sequence number in response
*ptr++ = id_len; // Length of result string
memcpy((char *)ptr, server_id, id_len); // Result string
/* ptr += id_len; Not required unless more data is to be added */
slave->dcb->func.write(slave->dcb, pkt);
return blr_slave_send_eof(router, slave, 5);
}
/**
* Send the response to the SQL command "SHOW VARIABLES LIKE 'MAXSCALE%'
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @return Non-zero if data was sent
*/
static int
blr_slave_send_maxscale_variables(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
{
GWBUF *pkt;
char name[40];
char version[80];
uint8_t *ptr;
int len, vers_len, seqno = 2;
blr_slave_send_fieldcount(router, slave, 2);
blr_slave_send_columndef(router, slave, "Variable_name", BLR_TYPE_STRING, 40, seqno++);
blr_slave_send_columndef(router, slave, "Value", BLR_TYPE_STRING, 40, seqno++);
blr_slave_send_eof(router, slave, seqno++);
sprintf(version, "%s", MAXSCALE_VERSION);
vers_len = strlen(version);
strcpy(name, "MAXSCALE_VERSION");
len = 5 + vers_len + strlen(name) + 1;
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, vers_len + 2 + strlen(name), 24); // Add length of data packet
ptr += 3;
*ptr++ = seqno++; // Sequence number in response
*ptr++ = strlen(name); // Length of result string
memcpy((char *)ptr, name, strlen(name)); // Result string
ptr += strlen(name);
*ptr++ = vers_len; // Length of result string
memcpy((char *)ptr, version, vers_len); // Result string
/* ptr += vers_len; Not required unless more data is to be added */
slave->dcb->func.write(slave->dcb, pkt);
return blr_slave_send_eof(router, slave, seqno++);
}
/**
* Send the response to the SQL command "SHOW MASTER STATUS"
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @return Non-zero if data was sent
*/
static int
blr_slave_send_master_status(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
{
GWBUF *pkt;
char file[40];
char position[40];
uint8_t *ptr;
int len, file_len;
blr_slave_send_fieldcount(router, slave, 5);
blr_slave_send_columndef(router, slave, "File", BLR_TYPE_STRING, 40, 2);
blr_slave_send_columndef(router, slave, "Position", BLR_TYPE_STRING, 40, 3);
blr_slave_send_columndef(router, slave, "Binlog_Do_DB", BLR_TYPE_STRING, 40, 4);
blr_slave_send_columndef(router, slave, "Binlog_Ignore_DB", BLR_TYPE_STRING, 40, 5);
blr_slave_send_columndef(router, slave, "Execute_Gtid_Set", BLR_TYPE_STRING, 40, 6);
blr_slave_send_eof(router, slave, 7);
sprintf(file, "%s", router->binlog_name);
file_len = strlen(file);
sprintf(position, "%lu", router->binlog_position);
len = 5 + file_len + strlen(position) + 1 + 3;
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, len - 4, 24); // Add length of data packet
ptr += 3;
*ptr++ = 0x08; // Sequence number in response
*ptr++ = strlen(file); // Length of result string
memcpy((char *)ptr, file, strlen(file)); // Result string
ptr += strlen(file);
*ptr++ = strlen(position); // Length of result string
memcpy((char *)ptr, position, strlen(position)); // Result string
ptr += strlen(position);
*ptr++ = 0; // Send 3 empty values
*ptr++ = 0;
*ptr++ = 0;
slave->dcb->func.write(slave->dcb, pkt);
return blr_slave_send_eof(router, slave, 9);
}
/*
* Columns to send for a "SHOW SLAVE STATUS" command
*/
static char *slave_status_columns[] =
{
"Slave_IO_State", "Master_Host", "Master_User", "Master_Port", "Connect_Retry",
"Master_Log_File", "Read_Master_Log_Pos", "Relay_Log_File", "Relay_Log_Pos",
"Relay_Master_Log_File", "Slave_IO_Running", "Slave_SQL_Running", "Replicate_Do_DB",
"Replicate_Ignore_DB", "Replicate_Do_Table",
"Replicate_Ignore_Table", "Replicate_Wild_Do_Table", "Replicate_Wild_Ignore_Table",
"Last_Errno", "Last_Error", "Skip_Counter", "Exec_Master_Log_Pos", "Relay_Log_Space",
"Until_Condition", "Until_Log_File", "Until_Log_Pos", "Master_SSL_Allowed",
"Master_SSL_CA_File", "Master_SSL_CA_Path", "Master_SSL_Cert", "Master_SSL_Cipher",
"Master_SSL_Key", "Seconds_Behind_Master",
"Master_SSL_Verify_Server_Cert", "Last_IO_Errno", "Last_IO_Error", "Last_SQL_Errno",
"Last_SQL_Error", "Replicate_Ignore_Server_Ids", "Master_Server_Id", "Master_UUID",
"Master_Info_File", "SQL_Delay", "SQL_Remaining_Delay", "Slave_SQL_Running_State",
"Master_Retry_Count", "Master_Bind", "Last_IO_Error_TimeStamp",
"Last_SQL_Error_Timestamp", "Master_SSL_Crl", "Master_SSL_Crlpath",
"Retrieved_Gtid_Set", "Executed_Gtid_Set", "Auto_Position", NULL
};
/**
* Send the response to the SQL command "SHOW SLAVE STATUS"
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @return Non-zero if data was sent
*/
static int
blr_slave_send_slave_status(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
{
GWBUF *pkt;
char column[251] = "";
uint8_t *ptr;
int len, actual_len, col_len, seqno, ncols, i;
char *dyn_column = NULL;
int max_column_size = sizeof(column);
/* Count the columns */
for (ncols = 0; slave_status_columns[ncols]; ncols++);
blr_slave_send_fieldcount(router, slave, ncols);
seqno = 2;
for (i = 0; slave_status_columns[i]; i++)
{
blr_slave_send_columndef(router, slave, slave_status_columns[i], BLR_TYPE_STRING, 40, seqno++);
}
blr_slave_send_eof(router, slave, seqno++);
len = 5 + ncols * max_column_size + 250; // Max length + 250 bytes error message
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, len - 4, 24); // Add length of data packet
ptr += 3;
*ptr++ = seqno++; // Sequence number in response
snprintf(column, max_column_size, "%s", blrm_states[router->master_state]);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
snprintf(column, max_column_size, "%s", router->service->dbref->server->name ? router->service->dbref->server->name : "");
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
snprintf(column, max_column_size, "%s", router->user ? router->user : "");
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
sprintf(column, "%d", router->service->dbref->server->port);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
sprintf(column, "%d", 60); // Connect retry
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
snprintf(column, max_column_size, "%s", router->binlog_name);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* if router->trx_safe report current_pos*/
if (router->trx_safe)
{
sprintf(column, "%lu", router->current_pos);
}
else
{
sprintf(column, "%lu", router->binlog_position);
}
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* We have no relay log, we relay the binlog, so we will send the same data */
snprintf(column, max_column_size, "%s", router->binlog_name);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
sprintf(column, "%ld", router->binlog_position);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* We have no relay log, we relay the binlog, so we will send the same data */
snprintf(column, max_column_size, "%s", router->binlog_name);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
if (router->master_state != BLRM_SLAVE_STOPPED)
{
if (router->master_state < BLRM_BINLOGDUMP)
{
strcpy(column, "Connecting");
}
else
{
strcpy(column, "Yes");
}
}
else
{
strcpy(column, "No");
}
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
if (router->master_state != BLRM_SLAVE_STOPPED)
{
strcpy(column, "Yes");
}
else
{
strcpy(column, "No");
}
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
*ptr++ = 0; // Send 6 empty values
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
/* Last error information */
sprintf(column, "%lu", router->m_errno);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* Last error message */
if (router->m_errmsg == NULL)
{
*ptr++ = 0;
}
else
{
dyn_column = (char*)router->m_errmsg;
col_len = strlen(dyn_column);
if (col_len > 250)
{
col_len = 250;
}
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, dyn_column, col_len); // Result string
ptr += col_len;
}
/* Skip_Counter */
sprintf(column, "%d", 0);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
sprintf(column, "%ld", router->binlog_position);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
sprintf(column, "%ld", router->binlog_position);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
strcpy(column, "None");
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
*ptr++ = 0;
/* Until_Log_Pos */
sprintf(column, "%d", 0);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* Master_SSL_Allowed */
if (router->ssl_enabled)
{
strcpy(column, "Yes");
}
else
{
strcpy(column, "No");
}
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* Check whether to report SSL master connection details */
if (router->ssl_ca && router->ssl_cert && router->ssl_key)
{
char big_column[250 + 1] = "";
// set Master_SSL_Cert
strncpy(big_column, router->ssl_ca, 250);
col_len = strlen(big_column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, big_column, col_len); // Result string
ptr += col_len;
*ptr++ = 0; // Empty Master_SSL_CA_Path column
// set Master_SSL_Cert
strncpy(big_column, router->ssl_cert, 250);
col_len = strlen(big_column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, big_column, col_len); // Result string
ptr += col_len;
*ptr++ = 0; // Empty Master_SSL_Cipher column
// set Master_SSL_Key
strncpy(big_column, router->ssl_key, 250);
col_len = strlen(big_column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, big_column, col_len); // Result string
ptr += col_len;
}
else
{
*ptr++ = 0; // Empty SSL columns
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
}
/* Seconds_Behind_Master */
sprintf(column, "%d", 0);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* Master_SSL_Verify_Server_Cert */
strcpy(column, "No");
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* Last_IO_Error */
sprintf(column, "%d", 0);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
*ptr++ = 0;
/* Last_SQL_Error */
sprintf(column, "%d", 0);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
*ptr++ = 0;
*ptr++ = 0;
/* Master_Server_Id */
sprintf(column, "%d", router->masterid);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* Master_server_UUID */
snprintf(column, max_column_size, "%s", router->master_uuid ?
router->master_uuid : router->uuid);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* Master_info_file */
snprintf(column, max_column_size, "%s/master.ini", router->binlogdir);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* SQL_Delay*/
sprintf(column, "%d", 0);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
*ptr++ = 0xfb; // NULL value
/* Slave_Running_State */
if (router->master_state == BLRM_SLAVE_STOPPED)
{
strcpy(column, "Slave stopped");
}
else if (!router->m_errno)
{
strcpy(column, "Slave running");
}
else
{
if (router->master_state < BLRM_BINLOGDUMP)
{
strcpy(column, "Registering");
}
else
{
strcpy(column, "Error");
}
}
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
/* Master_Retry_Count */
sprintf(column, "%d", 1000);
col_len = strlen(column);
*ptr++ = col_len; // Length of result string
memcpy((char *)ptr, column, col_len); // Result string
ptr += col_len;
*ptr++ = 0; // Send 5 empty values
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
// No GTID support send empty values
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
actual_len = ptr - (uint8_t *)GWBUF_DATA(pkt);
ptr = GWBUF_DATA(pkt);
encode_value(ptr, actual_len - 4, 24); // Add length of data packet
pkt = gwbuf_rtrim(pkt, len - actual_len); // Trim the buffer to the actual size
slave->dcb->func.write(slave->dcb, pkt);
return blr_slave_send_eof(router, slave, seqno++);
}
/**
* Send the response to the SQL command "SHOW SLAVE HOSTS"
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @return Non-zero if data was sent
*/
static int
blr_slave_send_slave_hosts(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
{
GWBUF *pkt;
char server_id[40];
char host[40];
char port[40];
char master_id[40];
char slave_uuid[40];
uint8_t *ptr;
int len, seqno;
ROUTER_SLAVE *sptr;
blr_slave_send_fieldcount(router, slave, 5);
blr_slave_send_columndef(router, slave, "Server_id", BLR_TYPE_STRING, 40, 2);
blr_slave_send_columndef(router, slave, "Host", BLR_TYPE_STRING, 40, 3);
blr_slave_send_columndef(router, slave, "Port", BLR_TYPE_STRING, 40, 4);
blr_slave_send_columndef(router, slave, "Master_id", BLR_TYPE_STRING, 40, 5);
blr_slave_send_columndef(router, slave, "Slave_UUID", BLR_TYPE_STRING, 40, 6);
blr_slave_send_eof(router, slave, 7);
seqno = 8;
spinlock_acquire(&router->lock);
sptr = router->slaves;
while (sptr)
{
if (sptr->state == BLRS_DUMPING || sptr->state == BLRS_REGISTERED)
{
sprintf(server_id, "%d", sptr->serverid);
sprintf(host, "%s", sptr->hostname ? sptr->hostname : "");
sprintf(port, "%d", sptr->port);
sprintf(master_id, "%d", router->serverid);
sprintf(slave_uuid, "%s", sptr->uuid ? sptr->uuid : "");
len = 4 + strlen(server_id) + strlen(host) + strlen(port)
+ strlen(master_id) + strlen(slave_uuid) + 5;
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, len - 4, 24); // Add length of data packet
ptr += 3;
*ptr++ = seqno++; // Sequence number in response
*ptr++ = strlen(server_id); // Length of result string
memcpy((char *)ptr, server_id, strlen(server_id)); // Result string
ptr += strlen(server_id);
*ptr++ = strlen(host); // Length of result string
memcpy((char *)ptr, host, strlen(host)); // Result string
ptr += strlen(host);
*ptr++ = strlen(port); // Length of result string
memcpy((char *)ptr, port, strlen(port)); // Result string
ptr += strlen(port);
*ptr++ = strlen(master_id); // Length of result string
memcpy((char *)ptr, master_id, strlen(master_id)); // Result string
ptr += strlen(master_id);
*ptr++ = strlen(slave_uuid); // Length of result string
memcpy((char *)ptr, slave_uuid, strlen(slave_uuid)); // Result string
ptr += strlen(slave_uuid);
slave->dcb->func.write(slave->dcb, pkt);
}
sptr = sptr->next;
}
spinlock_release(&router->lock);
return blr_slave_send_eof(router, slave, seqno);
}
/**
* Process a slave replication registration message.
*
* We store the various bits of information the slave gives us and generate
* a reply message: OK packet.
*
* @param router The router instance
* @param slave The slave server
* @param queue The BINLOG_DUMP packet
* @return Non-zero if data was sent
*/
static int
blr_slave_register(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
{
uint8_t *ptr;
int slen;
ptr = GWBUF_DATA(queue);
ptr += 4; // Skip length and sequence number
if (*ptr++ != COM_REGISTER_SLAVE)
{
return 0;
}
slave->serverid = extract_field(ptr, 32);
ptr += 4;
slen = *ptr++;
if (slen != 0)
{
slave->hostname = strndup((char *)ptr, slen);
ptr += slen;
}
else
{
slave->hostname = NULL;
}
slen = *ptr++;
if (slen != 0)
{
ptr += slen;
slave->user = strndup((char *)ptr, slen);
}
else
{
slave->user = NULL;
}
slen = *ptr++;
if (slen != 0)
{
slave->passwd = strndup((char *)ptr, slen);
ptr += slen;
}
else
{
slave->passwd = NULL;
}
slave->port = extract_field(ptr, 16);
ptr += 2;
slave->rank = extract_field(ptr, 32);
slave->state = BLRS_REGISTERED;
/*
* Send OK response
*/
return blr_slave_send_ok(router, slave);
}
/**
* Process a COM_BINLOG_DUMP message from the slave. This is the
* final step in the process of registration. The new master, MaxScale
* must send a response packet and generate a fake BINLOG_ROTATE event
* with the binlog file requested by the slave. And then send a
* FORMAT_DESCRIPTION_EVENT that has been saved from the real master.
*
* Once send MaxScale must continue to send binlog events to the slave.
*
* @param router The router instance
* @param slave The slave server
* @param queue The BINLOG_DUMP packet
* @return The number of bytes written to the slave
*/
static int
blr_slave_binlog_dump(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue)
{
GWBUF *resp;
uint8_t *ptr;
int len, rval, binlognamelen;
REP_HEADER hdr;
uint32_t chksum;
ptr = GWBUF_DATA(queue);
len = extract_field(ptr, 24);
binlognamelen = len - 11;
if (binlognamelen > BINLOG_FNAMELEN)
{
MXS_ERROR("blr_slave_binlog_dump truncating binlog filename "
"from %d to %d",
binlognamelen, BINLOG_FNAMELEN);
binlognamelen = BINLOG_FNAMELEN;
}
ptr += 4; // Skip length and sequence number
if (*ptr++ != COM_BINLOG_DUMP)
{
MXS_ERROR("blr_slave_binlog_dump expected a COM_BINLOG_DUMP but received %d",
*(ptr - 1));
return 0;
}
slave->binlog_pos = extract_field(ptr, 32);
ptr += 4;
ptr += 2;
ptr += 4;
memcpy(slave->binlogfile, (char *)ptr, binlognamelen);
slave->binlogfile[binlognamelen] = 0;
if (router->trx_safe)
{
/**
* Check for a pending transaction and possible unsafe position.
* Force slave disconnection if requested position is unsafe.
*/
bool force_disconnect = false;
spinlock_acquire(&router->binlog_lock);
if (router->pending_transaction && strcmp(router->binlog_name, slave->binlogfile) == 0 &&
(slave->binlog_pos > router->binlog_position))
{
force_disconnect = true;
}
spinlock_release(&router->binlog_lock);
if (force_disconnect)
{
MXS_ERROR("%s: Slave %s:%i, server-id %d, binlog '%s', blr_slave_binlog_dump failure: "
"Requested binlog position %lu. Position is unsafe so disconnecting. "
"Latest safe position %lu, end of binlog file %lu",
router->service->name,
slave->dcb->remote,
ntohs((slave->dcb->ipv4).sin_port),
slave->serverid,
slave->binlogfile,
(unsigned long)slave->binlog_pos,
router->binlog_position,
router->current_pos);
/*
* Close the slave session and socket
* The slave will try to reconnect
*/
dcb_close(slave->dcb);
return 1;
}
}
MXS_DEBUG("%s: COM_BINLOG_DUMP: binlog name '%s', length %d, "
"from position %lu.", router->service->name,
slave->binlogfile, binlognamelen,
(unsigned long)slave->binlog_pos);
slave->seqno = 1;
if (slave->nocrc)
{
len = BINLOG_EVENT_HDR_LEN + 8 + binlognamelen;
}
else
{
len = BINLOG_EVENT_HDR_LEN + 8 + 4 + binlognamelen;
}
// Build a fake rotate event
resp = gwbuf_alloc(len + 5);
hdr.payload_len = len + 1;
hdr.seqno = slave->seqno++;
hdr.ok = 0;
hdr.timestamp = 0L;
hdr.event_type = ROTATE_EVENT;
hdr.serverid = router->masterid;
hdr.event_size = len;
hdr.next_pos = 0;
hdr.flags = 0x20;
ptr = blr_build_header(resp, &hdr);
encode_value(ptr, slave->binlog_pos, 64);
ptr += 8;
memcpy(ptr, slave->binlogfile, binlognamelen);
ptr += binlognamelen;
if (!slave->nocrc)
{
/*
* Now add the CRC to the fake binlog rotate event.
*
* The algorithm is first to compute the checksum of an empty buffer
* and then the checksum of the event portion of the message, ie we do not
* include the length, sequence number and ok byte that makes up the first
* 5 bytes of the message. We also do not include the 4 byte checksum itself.
*/
chksum = crc32(0L, NULL, 0);
chksum = crc32(chksum, GWBUF_DATA(resp) + 5, hdr.event_size - 4);
encode_value(ptr, chksum, 32);
}
/* Send Fake Rotate Event */
rval = slave->dcb->func.write(slave->dcb, resp);
/* set lastEventReceived */
slave->lastEventReceived = ROTATE_EVENT;
/* set lastReply for slave heartbeat check */
if (router->send_slave_heartbeat)
{
slave->lastReply = time(0);
}
/* Send the FORMAT_DESCRIPTION_EVENT */
if (slave->binlog_pos != 4)
{
blr_slave_send_fde(router, slave);
}
/* set lastEventReceived */
slave->lastEventReceived = FORMAT_DESCRIPTION_EVENT;
slave->dcb->low_water = router->low_water;
slave->dcb->high_water = router->high_water;
dcb_add_callback(slave->dcb, DCB_REASON_DRAINED, blr_slave_callback, slave);
slave->state = BLRS_DUMPING;
MXS_NOTICE("%s: Slave %s:%d, server id %d requested binlog file %s from position %lu",
router->service->name, slave->dcb->remote,
ntohs((slave->dcb->ipv4).sin_port),
slave->serverid,
slave->binlogfile, (unsigned long)slave->binlog_pos);
if (slave->binlog_pos != router->binlog_position ||
strcmp(slave->binlogfile, router->binlog_name) != 0)
{
spinlock_acquire(&slave->catch_lock);
slave->cstate &= ~CS_UPTODATE;
slave->cstate |= CS_EXPECTCB;
spinlock_release(&slave->catch_lock);
poll_fake_write_event(slave->dcb);
}
return rval;
}
/**
* Encode a value into a number of bits in a MySQL packet
*
* @param data Pointer to location in target packet
* @param value The value to encode into the buffer
* @param len Number of bits to encode value into
*/
static void
encode_value(unsigned char *data, unsigned int value, int len)
{
while (len > 0)
{
*data++ = value & 0xff;
value >>= 8;
len -= 8;
}
}
/**
* Populate a header structure for a replication message from a GWBUF structure.
*
* @param pkt The incoming packet in a GWBUF chain
* @param hdr The packet header to populate
* @return A pointer to the first byte following the event header
*/
uint8_t *
blr_build_header(GWBUF *pkt, REP_HEADER *hdr)
{
uint8_t *ptr;
ptr = GWBUF_DATA(pkt);
encode_value(ptr, hdr->payload_len, 24);
ptr += 3;
*ptr++ = hdr->seqno;
*ptr++ = hdr->ok;
encode_value(ptr, hdr->timestamp, 32);
ptr += 4;
*ptr++ = hdr->event_type;
encode_value(ptr, hdr->serverid, 32);
ptr += 4;
encode_value(ptr, hdr->event_size, 32);
ptr += 4;
encode_value(ptr, hdr->next_pos, 32);
ptr += 4;
encode_value(ptr, hdr->flags, 16);
ptr += 2;
return ptr;
}
/**
* We have a registered slave that is behind the current leading edge of the
* binlog. We must replay the log entries to bring this node up to speed.
*
* There may be a large number of records to send to the slave, the process
* is triggered by the slave COM_BINLOG_DUMP message and all the events must
* be sent without receiving any new event. This measn there is no trigger into
* MaxScale other than this initial message. However, if we simply send all the
* events we end up with an extremely long write queue on the DCB and risk
* running the server out of resources.
*
* The slave catchup routine will send a burst of replication events per single
* call. The paramter "long" control the number of events in the burst. The
* short burst is intended to be used when the master receive an event and
* needs to put the slave into catchup mode. This prevents the slave taking
* too much time away from the thread that is processing the master events.
*
* At the end of the burst a fake EPOLLOUT event is added to the poll event
* queue. This ensures that the slave callback for processing DCB write drain
* will be called and future catchup requests will be handled on another thread.
*
* @param router The binlog router
* @param slave The slave that is behind
* @param large Send a long or short burst of events
* @return The number of bytes written
*/
int
blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large)
{
GWBUF *record;
REP_HEADER hdr;
int rval = 1, burst;
int rotating = 0;
long burst_size;
char read_errmsg[BINLOG_ERROR_MSG_LEN + 1];
read_errmsg[BINLOG_ERROR_MSG_LEN] = '\0';
if (large)
{
burst = router->long_burst;
}
else
{
burst = router->short_burst;
}
burst_size = router->burst_size;
int do_return;
spinlock_acquire(&router->binlog_lock);
do_return = 0;
/* check for a pending transaction and safe position */
if (router->pending_transaction && strcmp(router->binlog_name, slave->binlogfile) == 0 &&
(slave->binlog_pos > router->binlog_position))
{
do_return = 1;
}
spinlock_release(&router->binlog_lock);
if (do_return)
{
spinlock_acquire(&slave->catch_lock);
slave->cstate &= ~CS_BUSY;
slave->cstate |= CS_EXPECTCB;
spinlock_release(&slave->catch_lock);
poll_fake_write_event(slave->dcb);
return 0;
}
BLFILE *file;
#ifdef BLFILE_IN_SLAVE
file = slave->file;
slave->file = NULL;
#else
file = NULL;
#endif
if (file == NULL)
{
rotating = router->rotating;
if ((file = blr_open_binlog(router, slave->binlogfile)) == NULL)
{
char err_msg[BINLOG_ERROR_MSG_LEN + 1];
err_msg[BINLOG_ERROR_MSG_LEN] = '\0';
if (rotating)
{
spinlock_acquire(&slave->catch_lock);
slave->cstate |= CS_EXPECTCB;
slave->cstate &= ~CS_BUSY;
spinlock_release(&slave->catch_lock);
poll_fake_write_event(slave->dcb);
return rval;
}
MXS_ERROR("Slave %s:%i, server-id %d, binlog '%s': blr_slave_catchup "
"failed to open binlog file",
slave->dcb->remote, ntohs((slave->dcb->ipv4).sin_port), slave->serverid,
slave->binlogfile);
slave->cstate &= ~CS_BUSY;
slave->state = BLRS_ERRORED;
snprintf(err_msg, BINLOG_ERROR_MSG_LEN, "Failed to open binlog '%s'", slave->binlogfile);
/* Send error that stops slave replication */
blr_send_custom_error(slave->dcb, slave->seqno++, 0, err_msg, "HY000", 1236);
dcb_close(slave->dcb);
return 0;
}
}
slave->stats.n_bursts++;
#ifdef BLSLAVE_IN_FILE
slave->file = file;
#endif
int events_before = slave->stats.n_events;
while (burst-- && burst_size > 0 &&
(record = blr_read_binlog(router, file, slave->binlog_pos, &hdr, read_errmsg)) != NULL)
{
char binlog_name[BINLOG_FNAMELEN + 1];
uint32_t binlog_pos;
strcpy(binlog_name, slave->binlogfile);
binlog_pos = slave->binlog_pos;
if (hdr.event_type == ROTATE_EVENT)
{
unsigned long beat1 = hkheartbeat;
blr_close_binlog(router, file);
if (hkheartbeat - beat1 > 1)
{
MXS_ERROR("blr_close_binlog took %lu maxscale beats", hkheartbeat - beat1);
}
blr_slave_rotate(router, slave, GWBUF_DATA(record));
beat1 = hkheartbeat;
#ifdef BLFILE_IN_SLAVE
if ((slave->file = blr_open_binlog(router, slave->binlogfile)) == NULL)
#else
if ((file = blr_open_binlog(router, slave->binlogfile)) == NULL)
#endif
{
char err_msg[BINLOG_ERROR_MSG_LEN + 1];
err_msg[BINLOG_ERROR_MSG_LEN] = '\0';
if (rotating)
{
spinlock_acquire(&slave->catch_lock);
slave->cstate |= CS_EXPECTCB;
slave->cstate &= ~CS_BUSY;
spinlock_release(&slave->catch_lock);
poll_fake_write_event(slave->dcb);
return rval;
}
MXS_ERROR("Slave %s:%i, server-id %d, binlog '%s': blr_slave_catchup "
"failed to open binlog file in rotate event",
slave->dcb->remote,
ntohs((slave->dcb->ipv4).sin_port),
slave->serverid,
slave->binlogfile);
slave->state = BLRS_ERRORED;
snprintf(err_msg, BINLOG_ERROR_MSG_LEN,
"Failed to open binlog '%s' in rotate event", slave->binlogfile);
/* Send error that stops slave replication */
blr_send_custom_error(slave->dcb, (slave->seqno - 1), 0, err_msg, "HY000", 1236);
dcb_close(slave->dcb);
break;
}
#ifdef BLFILE_IN_SLAVE
file = slave->file;
#endif
if (hkheartbeat - beat1 > 1)
{
MXS_ERROR("blr_open_binlog took %lu beats", hkheartbeat - beat1);
}
}
if (blr_send_event(BLR_THREAD_ROLE_SLAVE, binlog_name, binlog_pos,
slave, &hdr, (uint8_t*) record->start))
{
if (hdr.event_type != ROTATE_EVENT)
{
slave->binlog_pos = hdr.next_pos;
}
slave->stats.n_events++;
burst_size -= hdr.event_size;
}
else
{
MXS_WARNING("Slave %s:%i, server-id %d, binlog '%s, position %u: "
"Slave-thread could not send event to slave, closing connection.",
slave->dcb->remote,
ntohs((slave->dcb->ipv4).sin_port),
slave->serverid,
binlog_name,
binlog_pos);
#ifndef BLFILE_IN_SLAVE
blr_close_binlog(router, file);
#endif
slave->state = BLRS_ERRORED;
dcb_close(slave->dcb);
return 0;
}
gwbuf_free(record);
record = NULL;
/* set lastReply for slave heartbeat check */
if (router->send_slave_heartbeat)
{
slave->lastReply = time(0);
}
}
if (record == NULL)
{
slave->stats.n_failed_read++;
if (hdr.ok == SLAVE_POS_BAD_FD)
{
MXS_ERROR("%s Slave %s:%i, server-id %d, binlog '%s', %s",
router->service->name,
slave->dcb->remote,
ntohs((slave->dcb->ipv4).sin_port),
slave->serverid,
slave->binlogfile,
read_errmsg);
}
if (hdr.ok == SLAVE_POS_BEYOND_EOF)
{
MXS_ERROR("%s Slave %s:%i, server-id %d, binlog '%s', %s",
router->service->name,
slave->dcb->remote,
ntohs((slave->dcb->ipv4).sin_port),
slave->serverid,
slave->binlogfile,
read_errmsg);
/*
* Close the slave session and socket
* The slave will try to reconnect
*/
dcb_close(slave->dcb);
#ifndef BLFILE_IN_SLAVE
blr_close_binlog(router, file);
#endif
return 0;
}
if (hdr.ok == SLAVE_POS_READ_ERR)
{
MXS_ERROR("%s Slave %s:%i, server-id %d, binlog '%s', %s",
router->service->name,
slave->dcb->remote,
ntohs((slave->dcb->ipv4).sin_port),
slave->serverid,
slave->binlogfile,
read_errmsg);
spinlock_acquire(&slave->catch_lock);
slave->state = BLRS_ERRORED;
spinlock_release(&slave->catch_lock);
/*
* Send an error that will stop slave replication
*/
blr_send_custom_error(slave->dcb, slave->seqno++, 0, read_errmsg, "HY000", 1236);
dcb_close(slave->dcb);
#ifndef BLFILE_IN_SLAVE
blr_close_binlog(router, file);
#endif
return 0;
}
if (hdr.ok == SLAVE_POS_READ_UNSAFE)
{
MXS_NOTICE("%s: Slave %s:%i, server-id %d, binlog '%s', read %d events, "
"current committed transaction event being sent: %lu, %s",
router->service->name,
slave->dcb->remote,
ntohs((slave->dcb->ipv4).sin_port),
slave->serverid,
slave->binlogfile,
slave->stats.n_events - events_before,
router->current_safe_event,
read_errmsg);
}
}
spinlock_acquire(&slave->catch_lock);
slave->cstate &= ~CS_BUSY;
spinlock_release(&slave->catch_lock);
if (record)
{
slave->stats.n_flows++;
spinlock_acquire(&slave->catch_lock);
slave->cstate |= CS_EXPECTCB;
spinlock_release(&slave->catch_lock);
poll_fake_write_event(slave->dcb);
}
else if (slave->binlog_pos == router->binlog_position &&
strcmp(slave->binlogfile, router->binlog_name) == 0)
{
int state_change = 0;
unsigned int cstate = 0;
spinlock_acquire(&router->binlog_lock);
spinlock_acquire(&slave->catch_lock);
cstate = slave->cstate;
/*
* Now check again since we hold the router->binlog_lock
* and slave->catch_lock.
*/
if (slave->binlog_pos != router->binlog_position ||
strcmp(slave->binlogfile, router->binlog_name) != 0)
{
slave->cstate &= ~CS_UPTODATE;
slave->cstate |= CS_EXPECTCB;
spinlock_release(&slave->catch_lock);
spinlock_release(&router->binlog_lock);
if ((cstate & CS_UPTODATE) == CS_UPTODATE)
{
#ifdef STATE_CHANGE_LOGGING_ENABLED
MXS_NOTICE("%s: Slave %s:%d, server-id %d transition from up-to-date to "
"catch-up in blr_slave_catchup, binlog file '%s', position %lu.",
router->service->name,
slave->dcb->remote,
ntohs((slave->dcb->ipv4).sin_port),
slave->serverid,
slave->binlogfile, (unsigned long)slave->binlog_pos);
#endif
}
poll_fake_write_event(slave->dcb);
}
else
{
if ((slave->cstate & CS_UPTODATE) == 0)
{
slave->stats.n_upd++;
slave->cstate |= CS_UPTODATE;
spinlock_release(&slave->catch_lock);
spinlock_release(&router->binlog_lock);
state_change = 1;
}
else
{
MXS_NOTICE("Execution entered branch were locks previously were NOT "
"released. Previously this would have caused a lock-up.");
spinlock_release(&slave->catch_lock);
spinlock_release(&router->binlog_lock);
}
}
if (state_change)
{
slave->stats.n_caughtup++;
#ifdef STATE_CHANGE_LOGGING_ENABLED
// TODO: The % 50 should be removed. Now only every 50th state change is logged.
if (slave->stats.n_caughtup == 1)
{
MXS_NOTICE("%s: Slave %s:%d, server-id %d is now up to date '%s', position %lu.",
router->service->name,
slave->dcb->remote,
ntohs((slave->dcb->ipv4).sin_port),
slave->serverid,
slave->binlogfile, (unsigned long)slave->binlog_pos);
}
else if ((slave->stats.n_caughtup % 50) == 0)
{
MXS_NOTICE("%s: Slave %s:%d, server-id %d is up to date '%s', position %lu.",
router->service->name,
slave->dcb->remote,
ntohs((slave->dcb->ipv4).sin_port),
slave->serverid,
slave->binlogfile, (unsigned long)slave->binlog_pos);
}
#endif
}
}
else
{
if (slave->binlog_pos >= blr_file_size(file)
&& router->rotating == 0
&& strcmp(router->binlog_name, slave->binlogfile) != 0
&& blr_file_next_exists(router, slave))
{
/* We may have reached the end of file of a non-current
* binlog file.
*
* Note if the master is rotating there is a window during
* which the rotate event has been written to the old binlog
* but the new binlog file has not yet been created. Therefore
* we ignore these issues during the rotate processing.
*/
MXS_ERROR("%s: Slave %s:%d, server-id %d reached end of file for binlog file %s "
"at %lu which is not the file currently being downloaded. "
"Master binlog is %s, %lu. This may be caused by a "
"previous failure of the master.",
router->service->name,
slave->dcb->remote,
ntohs((slave->dcb->ipv4).sin_port),
slave->serverid,
slave->binlogfile, (unsigned long)slave->binlog_pos,
router->binlog_name, router->binlog_position);
#ifdef BLFILE_IN_SLAVE
if (blr_slave_fake_rotate(router, slave, &slave->file))
#else
if (blr_slave_fake_rotate(router, slave, &file))
#endif
{
spinlock_acquire(&slave->catch_lock);
slave->cstate |= CS_EXPECTCB;
spinlock_release(&slave->catch_lock);
poll_fake_write_event(slave->dcb);
}
else
{
slave->state = BLRS_ERRORED;
dcb_close(slave->dcb);
}
}
else
{
spinlock_acquire(&slave->catch_lock);
slave->cstate |= CS_EXPECTCB;
spinlock_release(&slave->catch_lock);
poll_fake_write_event(slave->dcb);
}
}
#ifndef BLFILE_IN_SLAVE
if (file)
{
blr_close_binlog(router, file);
}
#endif
return rval;
}
/**
* The DCB callback used by the slave to obtain DCB_REASON_LOW_WATER callbacks
* when the server sends all the the queue data for a DCB. This is the mechanism
* that is used to implement the flow control mechanism for the sending of
* large quantities of binlog records during the catchup process.
*
* @param dcb The DCB of the slave connection
* @param reason The reason the callback was called
* @param data The user data, in this case the server structure
*/
int
blr_slave_callback(DCB *dcb, DCB_REASON reason, void *data)
{
ROUTER_SLAVE *slave = (ROUTER_SLAVE *)data;
ROUTER_INSTANCE *router = slave->router;
unsigned int cstate;
if (NULL == dcb->session->router_session)
{
/*
* The following processing will fail if there is no router session,
* because the "data" parameter will not contain meaningful data,
* so we have no choice but to stop here.
*/
return 0;
}
if (reason == DCB_REASON_DRAINED)
{
if (slave->state == BLRS_DUMPING)
{
spinlock_acquire(&slave->catch_lock);
if (slave->cstate & CS_BUSY)
{
spinlock_release(&slave->catch_lock);
return 0;
}
cstate = slave->cstate;
slave->cstate &= ~(CS_UPTODATE | CS_EXPECTCB);
slave->cstate |= CS_BUSY;
spinlock_release(&slave->catch_lock);
if ((cstate & CS_UPTODATE) == CS_UPTODATE)
{
#ifdef STATE_CHANGE_LOGGING_ENABLED
MXS_NOTICE("%s: Slave %s:%d, server-id %d transition from up-to-date to "
"catch-up in blr_slave_callback, binlog file '%s', position %lu.",
router->service->name,
slave->dcb->remote,
ntohs((slave->dcb->ipv4).sin_port),
slave->serverid,
slave->binlogfile, (unsigned long)slave->binlog_pos);
#endif
}
slave->stats.n_dcb++;
blr_slave_catchup(router, slave, true);
}
else
{
MXS_DEBUG("Ignored callback due to slave state %s",
blrs_states[slave->state]);
}
}
if (reason == DCB_REASON_LOW_WATER)
{
if (slave->state == BLRS_DUMPING)
{
slave->stats.n_cb++;
blr_slave_catchup(router, slave, true);
}
else
{
slave->stats.n_cbna++;
}
}
return 0;
}
/**
* Rotate the slave to the new binlog file
*
* @param slave The slave instance
* @param ptr The rotate event (minus header and OK byte)
*/
void
blr_slave_rotate(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, uint8_t *ptr)
{
int len = EXTRACT24(ptr + 9); // Extract the event length
len = len - (BINLOG_EVENT_HDR_LEN + 8); // Remove length of header and position
if (router->master_chksum)
{
len -= 4;
}
if (len > BINLOG_FNAMELEN)
{
len = BINLOG_FNAMELEN;
}
ptr += BINLOG_EVENT_HDR_LEN; // Skip header
slave->binlog_pos = extract_field(ptr, 32);
slave->binlog_pos += (((uint64_t)extract_field(ptr + 4, 32)) << 32);
memcpy(slave->binlogfile, ptr + 8, len);
slave->binlogfile[len] = 0;
}
/**
* Generate an internal rotate event that we can use to cause the slave to move beyond
* a binlog file that is misisng the rotate eent at the end.
*
* @param router The router instance
* @param slave The slave to rotate
* @return Non-zero if the rotate took place
*/
static int
blr_slave_fake_rotate(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, BLFILE** filep)
{
char *sptr;
int filenum;
GWBUF *resp;
uint8_t *ptr;
int len, binlognamelen;
REP_HEADER hdr;
uint32_t chksum;
if ((sptr = strrchr(slave->binlogfile, '.')) == NULL)
{
return 0;
}
blr_close_binlog(router, *filep);
filenum = atoi(sptr + 1);
sprintf(slave->binlogfile, BINLOG_NAMEFMT, router->fileroot, filenum + 1);
slave->binlog_pos = 4;
if ((*filep = blr_open_binlog(router, slave->binlogfile)) == NULL)
{
return 0;
}
binlognamelen = strlen(slave->binlogfile);
len = BINLOG_EVENT_HDR_LEN + 8 + 4 + binlognamelen;
/* no slave crc, remove 4 bytes */
if (slave->nocrc)
{
len -= 4;
}
// Build a fake rotate event
resp = gwbuf_alloc(len + 5);
hdr.payload_len = len + 1;
hdr.seqno = slave->seqno++;
hdr.ok = 0;
hdr.timestamp = 0L;
hdr.event_type = ROTATE_EVENT;
hdr.serverid = router->masterid;
hdr.event_size = len;
hdr.next_pos = 0;
hdr.flags = 0x20;
ptr = blr_build_header(resp, &hdr);
encode_value(ptr, slave->binlog_pos, 64);
ptr += 8;
memcpy(ptr, slave->binlogfile, binlognamelen);
ptr += binlognamelen;
/* if slave has crc add the chksum */
if (!slave->nocrc)
{
/*
* Now add the CRC to the fake binlog rotate event.
*
* The algorithm is first to compute the checksum of an empty buffer
* and then the checksum of the event portion of the message, ie we do not
* include the length, sequence number and ok byte that makes up the first
* 5 bytes of the message. We also do not include the 4 byte checksum itself.
*/
chksum = crc32(0L, NULL, 0);
chksum = crc32(chksum, GWBUF_DATA(resp) + 5, hdr.event_size - 4);
encode_value(ptr, chksum, 32);
}
slave->dcb->func.write(slave->dcb, resp);
return 1;
}
/**
* Send a "fake" format description event to the newly connected slave
*
* @param router The router instance
* @param slave The slave to send the event to
*/
static void
blr_slave_send_fde(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
{
BLFILE *file;
REP_HEADER hdr;
GWBUF *record, *head;
uint8_t *ptr;
uint32_t chksum;
char err_msg[BINLOG_ERROR_MSG_LEN + 1];
err_msg[BINLOG_ERROR_MSG_LEN] = '\0';
memset(&hdr, 0, BINLOG_EVENT_HDR_LEN);
if ((file = blr_open_binlog(router, slave->binlogfile)) == NULL)
{
return;
}
if ((record = blr_read_binlog(router, file, 4, &hdr, err_msg)) == NULL)
{
if (hdr.ok != SLAVE_POS_READ_OK)
{
MXS_ERROR("Slave %s:%i, server-id %d, binlog '%s', blr_read_binlog failure: %s",
slave->dcb->remote,
ntohs((slave->dcb->ipv4).sin_port),
slave->serverid,
slave->binlogfile,
err_msg);
}
blr_close_binlog(router, file);
return;
}
blr_close_binlog(router, file);
head = gwbuf_alloc(5);
ptr = GWBUF_DATA(head);
encode_value(ptr, hdr.event_size + 1, 24); // Payload length
ptr += 3;
*ptr++ = slave->seqno++;
*ptr++ = 0; // OK
head = gwbuf_append(head, record);
ptr = GWBUF_DATA(record);
encode_value(ptr, time(0), 32); // Overwrite timestamp
ptr += 13;
encode_value(ptr, 0, 32); // Set next position to 0
/*
* Since we have changed the timestamp we must recalculate the CRC
*
* Position ptr to the start of the event header,
* calculate a new checksum
* and write it into the header
*/
ptr = GWBUF_DATA(record) + hdr.event_size - 4;
chksum = crc32(0L, NULL, 0);
chksum = crc32(chksum, GWBUF_DATA(record), hdr.event_size - 4);
encode_value(ptr, chksum, 32);
slave->dcb->func.write(slave->dcb, head);
}
/**
* Send the field count packet in a response packet sequence.
*
* @param router The router
* @param slave The slave connection
* @param count Number of columns in the result set
* @return Non-zero on success
*/
static int
blr_slave_send_fieldcount(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, int count)
{
GWBUF *pkt;
uint8_t *ptr;
if ((pkt = gwbuf_alloc(5)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, 1, 24); // Add length of data packet
ptr += 3;
*ptr++ = 0x01; // Sequence number in response
*ptr++ = count; // Length of result string
return slave->dcb->func.write(slave->dcb, pkt);
}
/**
* Send the column definition packet in a response packet sequence.
*
* @param router The router
* @param slave The slave connection
* @param name Name of the column
* @param type Column type
* @param len Column length
* @param seqno Packet sequence number
* @return Non-zero on success
*/
static int
blr_slave_send_columndef(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, char *name, int type, int len,
uint8_t seqno)
{
GWBUF *pkt;
uint8_t *ptr;
if ((pkt = gwbuf_alloc(26 + strlen(name))) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, 22 + strlen(name), 24); // Add length of data packet
ptr += 3;
*ptr++ = seqno; // Sequence number in response
*ptr++ = 3; // Catalog is always def
*ptr++ = 'd';
*ptr++ = 'e';
*ptr++ = 'f';
*ptr++ = 0; // Schema name length
*ptr++ = 0; // virtual table name length
*ptr++ = 0; // Table name length
*ptr++ = strlen(name); // Column name length;
while (*name)
{
*ptr++ = *name++; // Copy the column name
}
*ptr++ = 0; // Orginal column name
*ptr++ = 0x0c; // Length of next fields always 12
*ptr++ = 0x3f; // Character set
*ptr++ = 0;
encode_value(ptr, len, 32); // Add length of column
ptr += 4;
*ptr++ = type;
*ptr++ = 0x81; // Two bytes of flags
if (type == 0xfd)
{
*ptr++ = 0x1f;
}
else
{
*ptr++ = 0x00;
}
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
return slave->dcb->func.write(slave->dcb, pkt);
}
/**
* Send an EOF packet in a response packet sequence.
*
* @param router The router
* @param slave The slave connection
* @param seqno The sequence number of the EOF packet
* @return Non-zero on success
*/
static int
blr_slave_send_eof(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, int seqno)
{
GWBUF *pkt;
uint8_t *ptr;
if ((pkt = gwbuf_alloc(9)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, 5, 24); // Add length of data packet
ptr += 3;
*ptr++ = seqno; // Sequence number in response
*ptr++ = 0xfe; // Length of result string
encode_value(ptr, 0, 16); // No errors
ptr += 2;
encode_value(ptr, 2, 16); // Autocommit enabled
return slave->dcb->func.write(slave->dcb, pkt);
}
/**
* Send the reply only to the SQL command "DISCONNECT SERVER $server_id'
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @return Non-zero if data was sent
*/
static int
blr_slave_send_disconnected_server(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, int server_id, int found)
{
GWBUF *pkt;
char state[40];
char serverid[40];
uint8_t *ptr;
int len, id_len, seqno = 2;
sprintf(serverid, "%d", server_id);
if (found)
{
strcpy(state, "disconnected");
}
else
{
strcpy(state, "not found");
}
id_len = strlen(serverid);
len = 4 + (1 + id_len) + (1 + strlen(state));
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
blr_slave_send_fieldcount(router, slave, 2);
blr_slave_send_columndef(router, slave, "server_id", BLR_TYPE_INT, 40, seqno++);
blr_slave_send_columndef(router, slave, "state", BLR_TYPE_STRING, 40, seqno++);
blr_slave_send_eof(router, slave, seqno++);
ptr = GWBUF_DATA(pkt);
encode_value(ptr, len - 4, 24); // Add length of data packet
ptr += 3;
*ptr++ = seqno++; // Sequence number in response
*ptr++ = id_len; // Length of result string
memcpy((char *)ptr, serverid, id_len); // Result string
ptr += id_len;
*ptr++ = strlen(state); // Length of result string
memcpy((char *)ptr, state, strlen(state)); // Result string
/* ptr += strlen(state); Not required unless more data is to be added */
slave->dcb->func.write(slave->dcb, pkt);
return blr_slave_send_eof(router, slave, seqno++);
}
/**
* Send the response to the SQL command "DISCONNECT SERVER $server_id'
* and close the connection to that server
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @param server_id The slave server_id to disconnect
* @return Non-zero if data was sent to the client
*/
static int
blr_slave_disconnect_server(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, int server_id)
{
ROUTER_SLAVE *sptr;
int n;
int server_found = 0;
spinlock_acquire(&router->lock);
sptr = router->slaves;
/* look for server_id among all registered slaves */
while (sptr)
{
/* don't examine slaves with state = 0 */
if ((sptr->state == BLRS_REGISTERED || sptr->state == BLRS_DUMPING) &&
sptr->serverid == server_id)
{
/* server_id found */
server_found = 1;
MXS_NOTICE("%s: Slave %s, server id %d, disconnected by %s@%s",
router->service->name,
sptr->dcb->remote,
server_id,
slave->dcb->user,
slave->dcb->remote);
/* send server_id with disconnect state to client */
n = blr_slave_send_disconnected_server(router, slave, server_id, 1);
sptr->state = BLRS_UNREGISTERED;
dcb_close(sptr->dcb);
break;
}
else
{
sptr = sptr->next;
}
}
spinlock_release(&router->lock);
/** server id was not found
* send server_id with not found state to the client
*/
if (!server_found)
{
n = blr_slave_send_disconnected_server(router, slave, server_id, 0);
}
if (n == 0)
{
MXS_ERROR("gwbuf memory allocation in "
"DISCONNECT SERVER server_id [%d]",
sptr->serverid);
blr_slave_send_error(router, slave, "Memory allocation error for DISCONNECT SERVER");
}
return 1;
}
/**
* Send the response to the SQL command "DISCONNECT ALL'
* and close the connection to all slave servers
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @return Non-zero if data was sent to the client
*/
static int
blr_slave_disconnect_all(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
{
ROUTER_SLAVE *sptr;
char server_id[40];
char state[40];
uint8_t *ptr;
int len, seqno;
GWBUF *pkt;
/* preparing output result */
blr_slave_send_fieldcount(router, slave, 2);
blr_slave_send_columndef(router, slave, "server_id", BLR_TYPE_INT, 40, 2);
blr_slave_send_columndef(router, slave, "state", BLR_TYPE_STRING, 40, 3);
blr_slave_send_eof(router, slave, 4);
seqno = 5;
spinlock_acquire(&router->lock);
sptr = router->slaves;
while (sptr)
{
/* skip servers with state = 0 */
if (sptr->state == BLRS_REGISTERED || sptr->state == BLRS_DUMPING)
{
sprintf(server_id, "%d", sptr->serverid);
sprintf(state, "disconnected");
len = 5 + strlen(server_id) + strlen(state) + 1;
if ((pkt = gwbuf_alloc(len)) == NULL)
{
MXS_ERROR("gwbuf memory allocation in "
"DISCONNECT ALL for [%s], server_id [%d]",
sptr->dcb->remote, sptr->serverid);
spinlock_release(&router->lock);
blr_slave_send_error(router, slave, "Memory allocation error for DISCONNECT ALL");
return 1;
}
MXS_NOTICE("%s: Slave %s, server id %d, disconnected by %s@%s",
router->service->name,
sptr->dcb->remote, sptr->serverid, slave->dcb->user, slave->dcb->remote);
ptr = GWBUF_DATA(pkt);
encode_value(ptr, len - 4, 24); // Add length of data packet
ptr += 3;
*ptr++ = seqno++; // Sequence number in response
*ptr++ = strlen(server_id); // Length of result string
memcpy((char *)ptr, server_id, strlen(server_id)); // Result string
ptr += strlen(server_id);
*ptr++ = strlen(state); // Length of result string
memcpy((char *)ptr, state, strlen(state)); // Result string
ptr += strlen(state);
slave->dcb->func.write(slave->dcb, pkt);
sptr->state = BLRS_UNREGISTERED;
dcb_close(sptr->dcb);
}
sptr = sptr->next;
}
spinlock_release(&router->lock);
blr_slave_send_eof(router, slave, seqno);
return 1;
}
/**
* Send a MySQL OK packet to the slave backend
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
*
* @return result of a write call, non-zero if write was successful
*/
static int
blr_slave_send_ok(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave)
{
GWBUF *pkt;
uint8_t ok_packet[] = {
7, 0, 0, // Payload length
1, // Seqno,
0, // OK,
0, 0, 2, 0, 0, 0
};
if ((pkt = gwbuf_alloc(sizeof(ok_packet))) == NULL)
{
return 0;
}
memcpy(GWBUF_DATA(pkt), ok_packet, sizeof(ok_packet));
return slave->dcb->func.write(slave->dcb, pkt);
}
/**
* Send a MySQL OK packet with a message to the slave backend
*
* @param router The binlog router instance
* @param message The message to send
* @param slave The slave server to which we are sending the response
*
* @return result of a write call, non-zero if write was successful
*/
static int
blr_slave_send_ok_message(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave, char *message)
{
GWBUF *pkt;
uint8_t *ptr;
if ((pkt = gwbuf_alloc(11 + strlen(message) + 1)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
*ptr++ = 7 + strlen(message) + 1; // Payload length
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 1; // Seqno
*ptr++ = 0; // ok
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 2;
*ptr++ = 0;
if (strlen(message) == 0)
{
*ptr++ = 0;
*ptr++ = 0;
}
else
{
*ptr++ = 1;
*ptr++ = 0;
*ptr++ = strlen(message);
strcpy((char *)ptr, message);
}
return slave->dcb->func.write(slave->dcb, pkt);
}
/**
* Stop current replication from master
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response*
* @return Always 1 for error, for send_ok the bytes sent
*
*/
static int
blr_stop_slave(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave)
{
/* if unconfigured return an error */
if (router->master_state == BLRM_UNCONFIGURED)
{
blr_slave_send_warning_message(router, slave, "1255:Slave already has been stopped");
return 1;
}
/* if already stopped return an error */
if (router->master_state == BLRM_SLAVE_STOPPED)
{
blr_slave_send_warning_message(router, slave, "1255:Slave already has been stopped");
return 1;
}
if (router->master)
{
if (router->master->fd != -1 && router->master->state == DCB_STATE_POLLING)
{
blr_master_close(router);
}
}
spinlock_acquire(&router->lock);
router->master_state = BLRM_SLAVE_STOPPED;
/* set last_safe_pos */
router->last_safe_pos = router->binlog_position;
/**
* Set router->prevbinlog to router->binlog_name
* The FDE event with current filename may arrive after STOP SLAVE is received
*/
if (strcmp(router->binlog_name, router->prevbinlog) != 0)
{
strcpy(router->prevbinlog, router->binlog_name); // Same size
}
if (router->client)
{
if (router->client->fd != -1 && router->client->state == DCB_STATE_POLLING)
{
dcb_close(router->client);
router->client = NULL;
}
}
/* Discard the queued residual data */
while (router->residual)
{
router->residual = gwbuf_consume(router->residual, GWBUF_LENGTH(router->residual));
}
router->residual = NULL;
/* Now it is safe to unleash other threads on this router instance */
router->reconnect_pending = 0;
router->active_logs = 0;
spinlock_release(&router->lock);
MXS_NOTICE("%s: STOP SLAVE executed by %s@%s. Disconnecting from master %s:%d, "
"read up to log %s, pos %lu, transaction safe pos %lu",
router->service->name,
slave->dcb->user,
slave->dcb->remote,
router->service->dbref->server->name,
router->service->dbref->server->port,
router->binlog_name, router->current_pos, router->binlog_position);
if (router->trx_safe && router->pending_transaction)
{
char message[BINLOG_ERROR_MSG_LEN + 1] = "";
snprintf(message, BINLOG_ERROR_MSG_LEN,
"1105:Stopped slave mid-transaction in binlog file %s, "
"pos %lu, incomplete transaction starts at pos %lu",
router->binlog_name, router->current_pos, router->binlog_position);
return blr_slave_send_warning_message(router, slave, message);
}
else
{
return blr_slave_send_ok(router, slave);
}
}
/**
* Start replication from current configured master
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @return Always 1 for error, for send_ok the bytes sent
*
*/
static int
blr_start_slave(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave)
{
int loaded;
/* if unconfigured return an error */
if (router->master_state == BLRM_UNCONFIGURED)
{
blr_slave_send_error_packet(slave,
"The server is not configured as slave; "
"fix in config file or with CHANGE MASTER TO", (unsigned int)1200,
NULL);
return 1;
}
/* if running return an error */
if (router->master_state != BLRM_UNCONNECTED && router->master_state != BLRM_SLAVE_STOPPED)
{
blr_slave_send_warning_message(router, slave, "1254:Slave is already running");
return 1;
}
spinlock_acquire(&router->lock);
router->master_state = BLRM_UNCONNECTED;
spinlock_release(&router->lock);
/* create a new binlog or just use current one */
if (strlen(router->prevbinlog) && strcmp(router->prevbinlog, router->binlog_name))
{
if (router->trx_safe && router->pending_transaction)
{
char msg[BINLOG_ERROR_MSG_LEN + 1] = "";
char file[PATH_MAX + 1] = "";
struct stat statb;
unsigned long filelen = 0;
snprintf(file, PATH_MAX, "%s/%s", router->binlogdir, router->prevbinlog);
/* Get file size */
if (stat(file, &statb) == 0)
{
filelen = statb.st_size;
}
/* Prepare warning message */
snprintf(msg, BINLOG_ERROR_MSG_LEN,
"1105:Truncated partial transaction in file %s, starting at pos %lu, "
"ending at pos %lu. File %s now has length %lu.",
router->prevbinlog,
router->last_safe_pos,
filelen,
router->prevbinlog,
router->last_safe_pos);
/* Truncate previous binlog file to last_safe pos */
truncate(file, router->last_safe_pos);
/* Log it */
MXS_WARNING("A transaction is still opened at pos %lu"
" File %s will be truncated. "
"Next binlog file is %s at pos %d, "
"START SLAVE is required again.",
router->last_safe_pos,
router->prevbinlog,
router->binlog_name,
4);
spinlock_acquire(&router->lock);
router->pending_transaction = 0;
router->last_safe_pos = 0;
router->master_state = BLRM_UNCONNECTED;
router->current_pos = 4;
router->binlog_position = 4;
router->current_safe_event = 4;
spinlock_release(&router->lock);
/* Send warning message to mysql command */
blr_slave_send_warning_message(router, slave, msg);
}
/* create new one */
blr_file_new_binlog(router, router->binlog_name);
}
else
{
if (router->binlog_fd == -1)
{
/* create new one */
blr_file_new_binlog(router, router->binlog_name);
}
else
{
/* use existing one */
blr_file_append(router, router->binlog_name);
}
}
/** Initialise SSL: exit on error */
if (router->ssl_enabled && router->service->dbref->server->server_ssl)
{
if (listener_init_SSL(router->service->dbref->server->server_ssl) != 0)
{
MXS_ERROR("%s: Unable to initialise SSL with backend server", router->service->name);
blr_slave_send_error_packet(slave,
"Unable to initialise SSL with backend server",
(unsigned int)1210, "HY000");
spinlock_acquire(&router->lock);
router->master_state = BLRM_SLAVE_STOPPED;
spinlock_release(&router->lock);
return 1;
}
}
/** Start replication from master */
blr_start_master(router);
MXS_NOTICE("%s: START SLAVE executed by %s@%s. Trying connection to master %s:%d, "
"binlog %s, pos %lu, transaction safe pos %lu",
router->service->name,
slave->dcb->user,
slave->dcb->remote,
router->service->dbref->server->name,
router->service->dbref->server->port,
router->binlog_name,
router->current_pos, router->binlog_position);
/* Try reloading new users and update cached credentials */
loaded = service_refresh_users(router->service);
if (loaded == 0)
{
for (SERV_LISTENER *port = router->service->ports; port; port = port->next)
{
char path[PATH_MAX];
sprintf(path, "%s/%s/%s/", router->binlogdir, BLR_DBUSERS_DIR, port->name);
if (mxs_mkdir_all(path, 0775))
{
strcat(path, BLR_DBUSERS_FILE);
dbusers_save(port->users, path);
}
}
}
else
{
MXS_NOTICE("Service %s: user credentials could not be refreshed. "
"Will use existing cached credentials (%s/%s) if possible.",
router->service->name, router->binlogdir, BLR_DBUSERS_DIR);
}
return blr_slave_send_ok(router, slave);
}
/**
* Construct an error packet reply with specified code and status
*
* @param slave The slave server instance
* @param msg The error message to send
* @param err_num The error number to send
* @param status The error status
*/
static void
blr_slave_send_error_packet(ROUTER_SLAVE *slave, char *msg, unsigned int err_num, char *status)
{
GWBUF *pkt;
unsigned char *data;
int len;
unsigned int mysql_errno = 0;
char *mysql_state;
if ((pkt = gwbuf_alloc(strlen(msg) + 13)) == NULL)
{
return;
}
if (status != NULL)
{
mysql_state = status;
}
else
{
mysql_state = "HY000";
}
if (err_num > 0)
{
mysql_errno = err_num;
}
else
{
mysql_errno = (unsigned int)2003;
}
data = GWBUF_DATA(pkt);
len = strlen(msg) + 9;
encode_value(&data[0], len, 24); // Payload length
data[3] = 1; // Sequence id
data[4] = 0xff; // Error indicator
encode_value(&data[5], mysql_errno, 16);// Error Code
data[7] = '#'; // Status message first char
memcpy((char *)&data[8], mysql_state, 5); // Status message
memcpy(&data[13], msg, strlen(msg)); // Error Message
slave->dcb->func.write(slave->dcb, pkt);
}
/**
* handle a 'change master' operation
*
* @param router The router instance
* @param command The change master SQL command
* @param error The error message, preallocated BINLOG_ERROR_MSG_LEN + 1 bytes
* @return 0 on success, 1 on success with new binlog, -1 on failure
*/
static
int blr_handle_change_master(ROUTER_INSTANCE* router, char *command, char *error)
{
char *master_logfile = NULL;
char *master_log_pos = NULL;
int change_binlog = 0;
long long pos = 0;
MASTER_SERVER_CFG *current_master = NULL;
CHANGE_MASTER_OPTIONS change_master;
int parse_ret;
char *cmd_ptr;
char *cmd_string;
if ((cmd_ptr = strcasestr(command, "TO")) == NULL)
{
static const char MESSAGE[] = "statement doesn't have the CHANGE MASTER TO syntax";
ss_dassert(sizeof(MESSAGE) <= BINLOG_ERROR_MSG_LEN);
strcpy(error, MESSAGE);
return -1;
}
if ((cmd_string = MXS_STRDUP(cmd_ptr + 2)) == NULL)
{
static const char MESSAGE[] ="error allocating memory for statement parsing";
ss_dassert(sizeof(MESSAGE) <= BINLOG_ERROR_MSG_LEN);
strcpy(error, MESSAGE);
MXS_ERROR("%s: %s", router->service->name, error);
return -1;
}
/* Parse SQL command and populate with found options the change_master struct */
memset(&change_master, 0, sizeof(change_master));
parse_ret = blr_parse_change_master_command(cmd_string, error, &change_master);
MXS_FREE(cmd_string);
if (parse_ret)
{
MXS_ERROR("%s CHANGE MASTER TO parse error: %s", router->service->name, error);
blr_master_free_parsed_options(&change_master);
return -1;
}
/* allocate struct for current replication parameters */
current_master = (MASTER_SERVER_CFG *)MXS_CALLOC(1, sizeof(MASTER_SERVER_CFG));
if (!current_master)
{
static const char MESSAGE[] = "error allocating memory for blr_master_get_config";
ss_dassert(sizeof(MESSAGE) <= BINLOG_ERROR_MSG_LEN);
strcpy(error, MESSAGE);
MXS_ERROR("%s: %s", router->service->name, error);
blr_master_free_parsed_options(&change_master);
return -1;
}
spinlock_acquire(&router->lock);
/* save current config option data */
blr_master_get_config(router, current_master);
/*
* Change values in the router->service->dbref->server structure
* Change filename and position in the router structure
*/
/* Set new binlog position from parsed SQL command */
master_log_pos = change_master.binlog_pos;
if (master_log_pos == NULL)
{
pos = 0;
}
else
{
pos = atoll(master_log_pos);
}
/* Change the replication user */
blr_set_master_user(router, change_master.user);
/* Change the replication password */
blr_set_master_password(router, change_master.password);
/* Change the master name/address */
blr_set_master_hostname(router, change_master.host);
/* Change the master port */
blr_set_master_port(router, change_master.port);
/* Handle SSL options */
int ssl_error;
ssl_error = blr_set_master_ssl(router, change_master, error);
if (ssl_error != -1 && (!change_master.ssl_cert || !change_master.ssl_ca || !change_master.ssl_key))
{
if (change_master.ssl_enabled && atoi(change_master.ssl_enabled))
{
snprintf(error, BINLOG_ERROR_MSG_LEN, "MASTER_SSL=1 but some required options are missing: check MASTER_SSL_CERT, MASTER_SSL_KEY, MASTER_SSL_CA");
ssl_error = -1;
}
}
if (ssl_error == -1)
{
MXS_ERROR("%s: %s", router->service->name, error);
/* restore previous master_host and master_port */
blr_master_restore_config(router, current_master);
blr_master_free_parsed_options(&change_master);
spinlock_release(&router->lock);
return -1;
}
/**
* Change the binlog filename as from MASTER_LOG_FILE
* New binlog file could be the next one or current one
*/
master_logfile = blr_set_master_logfile(router, change_master.binlog_file, error);
/**
* If MASTER_LOG_FILE is not set
* and master connection is configured
* set master_logfile to current binlog_name
* Otherwise return an error.
*/
if (master_logfile == NULL)
{
int change_binlog_error = 0;
/* Replication is not configured yet */
if (router->master_state == BLRM_UNCONFIGURED)
{
/* if there is another error message keep it */
if (!strlen(error))
{
snprintf(error, BINLOG_ERROR_MSG_LEN,
"Router is not configured for master connection, MASTER_LOG_FILE is required");
}
change_binlog_error = 1;
}
else
{
/* if errors returned set error */
if (strlen(error))
{
change_binlog_error = 1;
}
else
{
/* Use current binlog file */
master_logfile = MXS_STRDUP_A(router->binlog_name);
}
}
if (change_binlog_error)
{
MXS_ERROR("%s: %s", router->service->name, error);
/* restore previous master_host and master_port */
blr_master_restore_config(router, current_master);
blr_master_free_parsed_options(&change_master);
spinlock_release(&router->lock);
return -1;
}
}
/**
* If master connection is configured check new binlog name:
* If binlog name has changed to next one only position 4 is allowed
*/
if (strcmp(master_logfile, router->binlog_name) && router->master_state != BLRM_UNCONFIGURED)
{
int return_error = 0;
if (master_log_pos == NULL)
{
snprintf(error, BINLOG_ERROR_MSG_LEN,
"Please provide an explicit MASTER_LOG_POS for new MASTER_LOG_FILE %s: "
"Permitted binlog pos is %d. Current master_log_file=%s, master_log_pos=%lu",
master_logfile,
4,
router->binlog_name,
router->current_pos);
return_error = 1;
}
else
{
if (pos != 4)
{
snprintf(error, BINLOG_ERROR_MSG_LEN,
"Can not set MASTER_LOG_POS to %s for MASTER_LOG_FILE %s: "
"Permitted binlog pos is %d. Current master_log_file=%s, master_log_pos=%lu",
master_log_pos,
master_logfile,
4,
router->binlog_name,
router->current_pos);
return_error = 1;
}
}
/* return an error or set new binlog name at pos 4 */
if (return_error)
{
MXS_ERROR("%s: %s", router->service->name, error);
/* restore previous master_host and master_port */
blr_master_restore_config(router, current_master);
blr_master_free_parsed_options(&change_master);
MXS_FREE(master_logfile);
spinlock_release(&router->lock);
return -1;
}
else
{
/* set new filename at pos 4 */
strcpy(router->binlog_name, master_logfile);
router->current_pos = 4;
router->binlog_position = 4;
router->current_safe_event = 4;
/* close current file binlog file, next start slave will create the new one */
fsync(router->binlog_fd);
close(router->binlog_fd);
router->binlog_fd = -1;
MXS_INFO("%s: New MASTER_LOG_FILE is [%s]",
router->service->name,
router->binlog_name);
}
}
else
{
/**
* Same binlog or master connection not configured
* Position cannot be different from current pos or 4 (if BLRM_UNCONFIGURED)
*/
int return_error = 0;
if (router->master_state == BLRM_UNCONFIGURED)
{
if (master_log_pos != NULL && pos != 4)
{
snprintf(error, BINLOG_ERROR_MSG_LEN, "Can not set MASTER_LOG_POS to %s: "
"Permitted binlog pos is 4. Specified master_log_file=%s",
master_log_pos,
master_logfile);
return_error = 1;
}
}
else
{
if (master_log_pos != NULL && pos != router->current_pos)
{
snprintf(error, BINLOG_ERROR_MSG_LEN, "Can not set MASTER_LOG_POS to %s: "
"Permitted binlog pos is %lu. Current master_log_file=%s, master_log_pos=%lu",
master_log_pos,
router->current_pos,
router->binlog_name,
router->current_pos);
return_error = 1;
}
}
/* log error and return */
if (return_error)
{
MXS_ERROR("%s: %s", router->service->name, error);
/* restore previous master_host and master_port */
blr_master_restore_config(router, current_master);
blr_master_free_parsed_options(&change_master);
MXS_FREE(master_logfile);
spinlock_release(&router->lock);
return -1;
}
else
{
/**
* no pos change, set it to 4 if BLRM_UNCONFIGURED
* Also set binlog name if UNCOFIGURED
*/
if (router->master_state == BLRM_UNCONFIGURED)
{
router->current_pos = 4;
router->binlog_position = 4;
router->current_safe_event = 4;
strcpy(router->binlog_name, master_logfile);
MXS_INFO("%s: New MASTER_LOG_FILE is [%s]",
router->service->name,
router->binlog_name);
}
MXS_INFO("%s: New MASTER_LOG_POS is [%lu]",
router->service->name,
router->current_pos);
}
}
/* Log config changes (without passwords) */
MXS_NOTICE("%s: 'CHANGE MASTER TO executed'. Previous state "
"MASTER_HOST='%s', MASTER_PORT=%i, MASTER_LOG_FILE='%s', "
"MASTER_LOG_POS=%lu, MASTER_USER='%s'. New state is MASTER_HOST='%s', "
"MASTER_PORT=%i, MASTER_LOG_FILE='%s', MASTER_LOG_POS=%lu, MASTER_USER='%s'",
router->service->name,
current_master->host, current_master->port, current_master->logfile,
current_master->pos, current_master->user,
router->service->dbref->server->name,
router->service->dbref->server->port,
router->binlog_name,
router->current_pos,
router->user);
blr_master_free_config(current_master);
blr_master_free_parsed_options(&change_master);
MXS_FREE(master_logfile);
if (router->master_state == BLRM_UNCONFIGURED)
{
change_binlog = 1;
}
spinlock_release(&router->lock);
return change_binlog;
}
/*
* Set new master hostname
*
* @param router Current router instance
* @param hostname The hostname to set
* @return 1 for applied change, 0 otherwise
*/
static int
blr_set_master_hostname(ROUTER_INSTANCE *router, char *hostname)
{
if (hostname)
{
char *ptr;
char *end;
ptr = strchr(hostname, '\'');
if (ptr)
{
ptr++;
}
else
{
ptr = hostname;
}
end = strchr(ptr, '\'');
if (end)
{
*end = '\0';
}
server_update_address(router->service->dbref->server, ptr);
MXS_INFO("%s: New MASTER_HOST is [%s]",
router->service->name,
router->service->dbref->server->name);
return 1;
}
return 0;
}
/**
* Set new master port
*
* @param router Current router instance
* @param port The server TCP port
* @return 1 for applied change, 0 otherwise
*/
static int
blr_set_master_port(ROUTER_INSTANCE *router, char *port)
{
unsigned short new_port;
if (port != NULL)
{
new_port = atoi(port);
if (new_port)
{
server_update_port(router->service->dbref->server, new_port);
MXS_INFO("%s: New MASTER_PORT is [%i]",
router->service->name,
router->service->dbref->server->port);
return 1;
}
}
return 0;
}
/*
* Set new master binlog file
*
* The routing must be called holding router->lock
*
* @param router Current router instance
* @param filename Binlog file name
* @param error The error msg for command, pre-allocated BINLOG_ERROR_MSG_LEN + 1 bytes
* @return New binlog file or NULL on error
*/
static char *
blr_set_master_logfile(ROUTER_INSTANCE *router, char *filename, char *error)
{
char *new_binlog_file = NULL;
if (filename)
{
long next_binlog_seqname;
char *file_ptr;
char *end;
file_ptr = strchr(filename, '\'');
if (file_ptr)
{
file_ptr++;
}
else
{
file_ptr = filename;
}
end = strchr(file_ptr, '\'');
if (end)
{
*end = '\0';
}
/* check binlog filename format */
end = strchr(file_ptr, '.');
if (!end)
{
snprintf(error, BINLOG_ERROR_MSG_LEN, "Selected binlog [%s] is not in the format"
" '%s.yyyyyy'",
file_ptr,
router->fileroot);
return NULL;
}
end++;
if (router->master_state == BLRM_UNCONFIGURED)
{
char *stem_end;
stem_end = strrchr(file_ptr, '.');
/* set filestem */
if (stem_end)
{
if (router->fileroot)
{
MXS_FREE(router->fileroot);
}
router->fileroot = strndup(file_ptr, stem_end - file_ptr);
}
}
else
{
/* get next binlog file name, assuming filestem is the same */
next_binlog_seqname = blr_file_get_next_binlogname(router);
if (!next_binlog_seqname)
{
snprintf(error, BINLOG_ERROR_MSG_LEN,
"Cannot get the next MASTER_LOG_FILE name from current binlog [%s]",
router->binlog_name);
return NULL;
}
/* Compare binlog file name with current one */
if (strcmp(router->binlog_name, file_ptr) == 0)
{
/* No binlog name change, eventually new position will be checked later */
}
else
{
/*
* This is a new binlog file request
* If file is not the next one return an error
*/
if (atoi(end) != next_binlog_seqname)
{
snprintf(error, BINLOG_ERROR_MSG_LEN,
"Can not set MASTER_LOG_FILE to %s: Permitted binlog file names are "
"%s or %s.%06li. Current master_log_file=%s, master_log_pos=%lu",
file_ptr,
router->binlog_name,
router->fileroot,
next_binlog_seqname,
router->binlog_name,
router->current_pos);
return NULL;
}
/* Binlog file name succesfully changed */
}
}
if (strlen(file_ptr) <= BINLOG_FNAMELEN)
{
new_binlog_file = MXS_STRDUP(file_ptr);
}
else
{
snprintf(error, BINLOG_ERROR_MSG_LEN,
"Can not set MASTER_LOG_FILE to %s: Maximum length is %d.", file_ptr, BINLOG_FNAMELEN);
}
}
return new_binlog_file;
}
/**
* Get master configuration store it
*
* @param router Current router instance
* @param curr_master Preallocated struct to fill
*/
static void
blr_master_get_config(ROUTER_INSTANCE *router, MASTER_SERVER_CFG *curr_master)
{
SSL_LISTENER *server_ssl;
curr_master->port = router->service->dbref->server->port;
curr_master->host = MXS_STRDUP_A(router->service->dbref->server->name);
curr_master->pos = router->current_pos;
curr_master->safe_pos = router->binlog_position;
strcpy(curr_master->logfile, router->binlog_name); // Same size
curr_master->user = MXS_STRDUP_A(router->user);
curr_master->password = MXS_STRDUP_A(router->password);
curr_master->filestem = MXS_STRDUP_A(router->fileroot);
/* SSL options */
if (router->service->dbref->server->server_ssl)
{
server_ssl = router->service->dbref->server->server_ssl;
curr_master->ssl_enabled = router->ssl_enabled;
if (router->ssl_version)
{
curr_master->ssl_version = MXS_STRDUP_A(router->ssl_version);
}
if (server_ssl->ssl_key)
{
curr_master->ssl_key = MXS_STRDUP_A(server_ssl->ssl_key);
}
if (server_ssl->ssl_cert)
{
curr_master->ssl_cert = MXS_STRDUP_A(server_ssl->ssl_cert);
}
if (server_ssl->ssl_ca_cert)
{
curr_master->ssl_ca = MXS_STRDUP_A(server_ssl->ssl_ca_cert);
}
}
}
/**
* Free a master configuration struct
*
* @param master_cfg Saved master configuration to free
*/
static void
blr_master_free_config(MASTER_SERVER_CFG *master_cfg)
{
MXS_FREE(master_cfg->host);
MXS_FREE(master_cfg->user);
MXS_FREE(master_cfg->password);
MXS_FREE(master_cfg->filestem);
/* SSL options */
MXS_FREE(master_cfg->ssl_key);
MXS_FREE(master_cfg->ssl_cert);
MXS_FREE(master_cfg->ssl_ca);
MXS_FREE(master_cfg->ssl_version);
MXS_FREE(master_cfg);
}
/**
* Restore master configuration values for host and port
*
* @param router Current router instance
* @param prev_master Previous saved master configuration
*/
static void
blr_master_restore_config(ROUTER_INSTANCE *router, MASTER_SERVER_CFG *prev_master)
{
server_update_address(router->service->dbref->server, prev_master->host);
server_update_port(router->service->dbref->server, prev_master->port);
router->ssl_enabled = prev_master->ssl_enabled;
if (prev_master->ssl_version)
{
MXS_FREE(router->ssl_version);
router->ssl_version = MXS_STRDUP_A(prev_master->ssl_version);
}
blr_master_free_config(prev_master);
}
/**
* Set all the master configuration fields to empty values
*
* @param router Current router instance
*/
static void
blr_master_set_empty_config(ROUTER_INSTANCE *router)
{
server_update_address(router->service->dbref->server, "none");
server_update_port(router->service->dbref->server, (unsigned short)3306);
router->current_pos = 4;
router->binlog_position = 4;
router->current_safe_event = 4;
strcpy(router->binlog_name, "");
}
/**
* Restore all master configuration values
*
* @param router Current router instance
* @param prev_master Previous saved master configuration
*/
static void
blr_master_apply_config(ROUTER_INSTANCE *router, MASTER_SERVER_CFG *prev_master)
{
server_update_address(router->service->dbref->server, prev_master->host);
server_update_port(router->service->dbref->server, prev_master->port);
router->current_pos = prev_master->pos;
router->binlog_position = prev_master->safe_pos;
router->current_safe_event = prev_master->safe_pos;
strcpy(router->binlog_name, prev_master->logfile);
if (router->user)
{
MXS_FREE(router->user);
router->user = MXS_STRDUP_A(prev_master->user);
}
if (router->password)
{
MXS_FREE(router->password);
router->password = MXS_STRDUP_A(prev_master->password);
}
if (router->fileroot)
{
MXS_FREE(router->fileroot);
router->fileroot = MXS_STRDUP_A(prev_master->filestem);
}
}
/**
* Change the replication user
*
* @param router Current router instance
* @param user The userto set
* @return 1 for applied change, 0 otherwise
*/
static int
blr_set_master_user(ROUTER_INSTANCE *router, char *user)
{
if (user != NULL)
{
char *ptr;
char *end;
ptr = strchr(user, '\'');
if (ptr)
{
ptr++;
}
else
{
ptr = user;
}
end = strchr(ptr, '\'');
if (end)
{
*end = '\0';
}
if (router->user)
{
MXS_FREE(router->user);
}
router->user = MXS_STRDUP_A(ptr);
MXS_INFO("%s: New MASTER_USER is [%s]",
router->service->name,
router->user);
return 1;
}
return 0;
}
/**
* Change the replication password
*
* @param router Current router instance
* @param password The password to set
* @return 1 for applied change, 0 otherwise
*/
static int
blr_set_master_password(ROUTER_INSTANCE *router, char *password)
{
if (password != NULL)
{
char *ptr;
char *end;
ptr = strchr(password, '\'');
if (ptr)
{
ptr++;
}
else
{
ptr = password;
}
end = strchr(ptr, '\'');
if (end)
{
*end = '\0';
}
if (router->password)
{
MXS_FREE(router->password);
}
router->password = MXS_STRDUP_A(ptr);
/* don't log new password */
return 1;
}
return 0;
}
/**
* Parse a CHANGE MASTER TO SQL command
*
* @param input The command to be parsed
* @param error_string Pre-allocated string for error message, BINLOG_ERROR_MSG_LEN + 1 bytes
* @param config master option struct to fill
* @return 0 on success, 1 on failure
*/
static int
blr_parse_change_master_command(char *input, char *error_string, CHANGE_MASTER_OPTIONS *config)
{
char *sep = ",";
char *word, *brkb;
if ((word = strtok_r(input, sep, &brkb)) == NULL)
{
snprintf(error_string, BINLOG_ERROR_MSG_LEN, "Unable to parse query [%s]", input);
return 1;
}
else
{
/* parse options key=val */
if (blr_handle_change_master_token(word, error_string, config))
{
return 1;
}
}
while ((word = strtok_r(NULL, sep, &brkb)) != NULL)
{
/* parse options key=val */
if (blr_handle_change_master_token(word, error_string, config))
{
return 1;
}
}
return 0;
}
/**
* Validate option and set the value for a change master option
*
* @param input Current option with value
* @param error pre-allocted string for error message, BINLOG_ERROR_MSG_LEN + 1 bytes
* @param config master option struct to fill
* @return 0 on success, 1 on error
*/
static int
blr_handle_change_master_token(char *input, char *error, CHANGE_MASTER_OPTIONS *config)
{
/* space+TAB+= */
char *sep = " =";
char *word, *brkb;
char *value = NULL;
char **option_field = NULL;
if ((word = strtok_r(input, sep, &brkb)) == NULL)
{
snprintf(error, BINLOG_ERROR_MSG_LEN, "error parsing %s", brkb);
return 1;
}
else
{
if ((option_field = blr_validate_change_master_option(word, config)) == NULL)
{
snprintf(error, BINLOG_ERROR_MSG_LEN, "option '%s' is not supported", word);
return 1;
}
/* value must be freed after usage */
if ((value = blr_get_parsed_command_value(brkb)) == NULL)
{
snprintf(error, BINLOG_ERROR_MSG_LEN, "missing value for '%s'", word);
return 1;
}
else
{
*option_field = value;
}
}
return 0;
}
/**
* Get value of a change master option
*
* @param input Current option with value
* @return The new allocated option value or NULL
*/
static char *
blr_get_parsed_command_value(char *input)
{
/* space+TAB+= */
char *sep = " =";
char *ret = NULL;
char *word;
char *value = NULL;
if (strlen(input))
{
value = MXS_STRDUP_A(input);
}
else
{
return ret;
}
if ((word = strtok_r(NULL, sep, &input)) != NULL)
{
char *ptr;
/* remove trailing spaces */
ptr = value + strlen(value) - 1;
while (ptr > value && isspace(*ptr))
{
*ptr-- = 0;
}
ret = MXS_STRDUP_A(strstr(value, word));
MXS_FREE(value);
}
return ret;
}
/**
* Validate a change master option
*
* @param option The option to check
* @param config The option structure
* @return A pointer to the field in the option strucure or NULL
*/
static char
**blr_validate_change_master_option(char *option, CHANGE_MASTER_OPTIONS *config)
{
if (strcasecmp(option, "master_host") == 0)
{
return &config->host;
}
else if (strcasecmp(option, "master_port") == 0)
{
return &config->port;
}
else if (strcasecmp(option, "master_log_file") == 0)
{
return &config->binlog_file;
}
else if (strcasecmp(option, "master_log_pos") == 0)
{
return &config->binlog_pos;
}
else if (strcasecmp(option, "master_user") == 0)
{
return &config->user;
}
else if (strcasecmp(option, "master_password") == 0)
{
return &config->password;
}
else if (strcasecmp(option, "master_ssl") == 0)
{
return &config->ssl_enabled;
}
else if (strcasecmp(option, "master_ssl_key") == 0)
{
return &config->ssl_key;
}
else if (strcasecmp(option, "master_ssl_cert") == 0)
{
return &config->ssl_cert;
}
else if (strcasecmp(option, "master_ssl_ca") == 0)
{
return &config->ssl_ca;
}
else if (strcasecmp(option, "master_ssl_version") == 0 || strcasecmp(option, "master_tls_version") == 0)
{
return &config->ssl_version;
}
else
{
return NULL;
}
}
/**
* Free parsed master options struct pointers
*
* @param options Parsed option struct
*/
static void
blr_master_free_parsed_options(CHANGE_MASTER_OPTIONS *options)
{
MXS_FREE(options->host);
options->host = NULL;
MXS_FREE(options->port);
options->port = NULL;
MXS_FREE(options->user);
options->user = NULL;
MXS_FREE(options->password);
options->password = NULL;
MXS_FREE(options->binlog_file);
options->binlog_file = NULL;
MXS_FREE(options->binlog_pos);
options->binlog_pos = NULL;
/* SSL options */
MXS_FREE(options->ssl_enabled);
options->ssl_enabled = NULL;
MXS_FREE(options->ssl_key);
options->ssl_key = NULL;
MXS_FREE(options->ssl_ca);
options->ssl_ca = NULL;
MXS_FREE(options->ssl_cert);
options->ssl_cert = NULL;
MXS_FREE(options->ssl_version);
options->ssl_version = NULL;
}
/**
* Send a MySQL protocol response for selected variable
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @param variable The variable name
* @param value The variable value
* @param column_type The variable value type (string or int)
* @return Non-zero if data was sent
*/
static int
blr_slave_send_var_value(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, char *variable, char *value,
int column_type)
{
GWBUF *pkt;
uint8_t *ptr;
int len, vers_len;
if (value == NULL)
{
return blr_slave_send_ok(router, slave);
}
vers_len = strlen(value);
blr_slave_send_fieldcount(router, slave, 1);
blr_slave_send_columndef(router, slave, variable, column_type, vers_len, 2);
blr_slave_send_eof(router, slave, 3);
len = 5 + vers_len;
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, vers_len + 1, 24); // Add length of data packet
ptr += 3;
*ptr++ = 0x04; // Sequence number in response
*ptr++ = vers_len; // Length of result string
memcpy((char *)ptr, value, vers_len); // Result string
/* ptr += vers_len; Not required unless more data is added */
slave->dcb->func.write(slave->dcb, pkt);
return blr_slave_send_eof(router, slave, 5);
}
/**
* Send the response to the SQL command "SHOW VARIABLES LIKE 'xxx'
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @param variable The variable name
* @param value The variable value
* @param column_type The variable value type (string or int)
* @return Non-zero if data was sent
*/
static int
blr_slave_send_variable(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *variable,
char *value,
int column_type)
{
GWBUF *pkt;
uint8_t *ptr;
int len, vers_len, seqno = 2;
char *p = MXS_STRDUP_A(variable);
int var_len;
char *old_ptr = p;
if (value == NULL)
{
return 0;
}
/* Remove heading and trailing "'" */
if (*p == '\'')
{
p++;
}
if (p[strlen(p) - 1] == '\'')
{
p[strlen(p) - 1] = '\0';
}
var_len = strlen(p);
/* force lowercase */
for (int i = 0; i < var_len; i++)
{
p[i] = tolower(p[i]);
}
blr_slave_send_fieldcount(router, slave, 2);
blr_slave_send_columndef_with_info_schema(router, slave, "Variable_name", BLR_TYPE_STRING, 40, seqno++);
blr_slave_send_columndef_with_info_schema(router, slave, "Value", column_type, 40, seqno++);
blr_slave_send_eof(router, slave, seqno++);
vers_len = strlen(value);
len = 5 + vers_len + var_len + 1;
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, vers_len + 2 + var_len, 24); // Add length of data packet
ptr += 3;
*ptr++ = seqno++; // Sequence number in response
*ptr++ = var_len; // Length of result string
memcpy((char *)ptr, p, var_len); // Result string with var name
ptr += var_len;
*ptr++ = vers_len; // Length of result string
memcpy((char *)ptr, value, vers_len); // Result string with var value
/* ptr += vers_len; Not required unless more data is added */
slave->dcb->func.write(slave->dcb, pkt);
MXS_FREE(old_ptr);
return blr_slave_send_eof(router, slave, seqno++);
}
/**
* Send the column definition packet for a variable in a response packet sequence.
*
* It adds information_schema and variables and variable_name
*
* @param router The router
* @param slave The slave connection
* @param name Name of the column
* @param type Column type
* @param len Column length
* @param seqno Packet sequence number
* @return Non-zero on success
*/
static int
blr_slave_send_columndef_with_info_schema(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *name,
int type,
int len,
uint8_t seqno)
{
GWBUF *pkt;
uint8_t *ptr;
int info_len = strlen("information_schema");
int virtual_table_name_len = strlen("VARIABLES");
int table_name_len = strlen("VARIABLES");
int column_name_len = strlen(name);
int orig_column_name_len = strlen("VARIABLE_NAME");
int packet_data_len = 22 + strlen(name) + info_len + virtual_table_name_len + table_name_len +
orig_column_name_len;
if ((pkt = gwbuf_alloc(4 + packet_data_len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, packet_data_len, 24); // Add length of data packet
ptr += 3;
*ptr++ = seqno; // Sequence number in response
*ptr++ = 3; // Catalog is always def
*ptr++ = 'd';
*ptr++ = 'e';
*ptr++ = 'f';
*ptr++ = info_len; // Schema name length
strcpy((char *)ptr, "information_schema");
ptr += info_len;
*ptr++ = virtual_table_name_len; // virtual table name length
strcpy((char *)ptr, "VARIABLES");
ptr += virtual_table_name_len;
*ptr++ = table_name_len; // Table name length
strcpy((char *)ptr, "VARIABLES");
ptr += table_name_len;
*ptr++ = column_name_len; // Column name length;
while (*name)
{
*ptr++ = *name++; // Copy the column name
}
*ptr++ = orig_column_name_len; // Orginal column name
strcpy((char *)ptr, "VARIABLE_NAME");
ptr += orig_column_name_len;
*ptr++ = 0x0c; // Length of next fields always 12
*ptr++ = 0x3f; // Character set
*ptr++ = 0;
encode_value(ptr, len, 32); // Add length of column
ptr += 4;
*ptr++ = type;
*ptr++ = 0x81; // Two bytes of flags
if (type == 0xfd)
{
*ptr++ = 0x1f;
}
else
{
*ptr++ = 0x00;
}
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
return slave->dcb->func.write(slave->dcb, pkt);
}
/**
* Interface for testing blr_parse_change_master_command()
*
* @param input The command to be parsed
* @param error_string Pre-allocated string for error message, BINLOG_ERROR_MSG_LEN + 1 bytes
* @param config master option struct to fill
* @return 0 on success, 1 on failure
*/
int
blr_test_parse_change_master_command(char *input, char *error_string, CHANGE_MASTER_OPTIONS *config)
{
return blr_parse_change_master_command(input, error_string, config);
}
/*
* Interface for testing set new master binlog file
*
*
* @param router Current router instance
* @param filename Binlog file name
* @param error The error msg for command, pre-allocated BINLOG_ERROR_MSG_LEN + 1 bytes
* @return New binlog file or NULL on error
*/
char *
blr_test_set_master_logfile(ROUTER_INSTANCE *router, char *filename, char *error)
{
return blr_set_master_logfile(router, filename, error);
}
/**
* Interface for testing a 'change master' operation
*
* @param router The router instance
* @param command The change master SQL command
* @param error The error message, preallocated BINLOG_ERROR_MSG_LEN + 1 bytes
* @return 0 on success, 1 on success with new binlog, -1 on failure
*/
int
blr_test_handle_change_master(ROUTER_INSTANCE* router, char *command, char *error)
{
return blr_handle_change_master(router, command, error);
}
/**
* Handle the response to the SQL command "SHOW GLOBAL VARIABLES LIKE or SHOW VARIABLES LIKE
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @param stmt The SQL statement
* @return Non-zero if the variable is handled, 0 if variable is unknown, -1 for syntax error
*/
static int
blr_slave_handle_variables(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, char *stmt)
{
char *brkb;
char *word;
/* SPACE,TAB,= */
char *sep = " ,=";
if ((word = strtok_r(stmt, sep, &brkb)) == NULL)
{
return -1;
}
else if (strcasecmp(word, "LIKE") == 0)
{
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Missing LIKE clause in SHOW [GLOBAL] VARIABLES.",
router->service->name);
return -1;
}
else if (strcasecmp(word, "'SERVER_ID'") == 0)
{
if (router->set_master_server_id)
{
char server_id[40];
sprintf(server_id, "%d", router->masterid);
return blr_slave_send_variable(router, slave, "'SERVER_ID'", server_id, BLR_TYPE_INT);
}
else
{
return blr_slave_replay(router, slave, router->saved_master.server_id);
}
}
else if (strcasecmp(word, "'SERVER_UUID'") == 0)
{
if (router->set_master_uuid)
{
return blr_slave_send_variable(router, slave, "'SERVER_UUID'",
router->master_uuid, BLR_TYPE_STRING);
}
else
{
return blr_slave_replay(router, slave, router->saved_master.uuid);
}
}
else if (strcasecmp(word, "'MAXSCALE%'") == 0)
{
return blr_slave_send_maxscale_variables(router, slave);
}
else
{
return 0;
}
}
else
{
return -1;
}
}
/**
* Send a MySQL OK packet with a warning flag to the slave backend
* and set the warning message in slave structure
* The message should be retrieved by SHOW WARNINGS command
*
* @param router The binlog router instance
* @param message The message to send
* @param slave The slave server to which we are sending the response
*
* @return result of a write call, non-zero if write was successful
*/
static int
blr_slave_send_warning_message(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave, char *message)
{
GWBUF *pkt;
uint8_t *ptr;
if ((pkt = gwbuf_alloc(11)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
*ptr++ = 7; // Payload length
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 1; // Seqno
*ptr++ = 0; // ok
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 2;
*ptr++ = 0;
if (strlen(message) == 0)
{
*ptr++ = 0;
*ptr++ = 0;
}
else
{
*ptr++ = 1; /* warning byte set to 1 */
*ptr++ = 0;
}
/* set the new warning in this slave connection */
if (slave->warning_msg)
{
MXS_FREE(slave->warning_msg);
}
slave->warning_msg = MXS_STRDUP_A(message);
return slave->dcb->func.write(slave->dcb, pkt);
}
/**
* Send a MySQL SHOW WARNINGS packet with a message that has been stored in slave struct
*
* If there is no wanring message an OK packet is sent
*
* @param router The binlog router instance
* @param message The message to send
* @param slave The slave server to which we are sending the response
*
* @return result of a write call, non-zero if write was successful
*/
static int
blr_slave_show_warnings(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave)
{
GWBUF *pkt;
uint8_t *ptr;
int len;
int msg_len = 0;
int code_len = 0;
int level_len = 0;
/* check whether a warning message is available */
if (slave->warning_msg)
{
char *level = "Warning";
char *msg_ptr;
char err_code[16 + 1] = "";
msg_ptr = strchr(slave->warning_msg, ':');
if (msg_ptr)
{
size_t len = (msg_ptr - slave->warning_msg > 16) ? 16 : (msg_ptr - slave->warning_msg);
memcpy(err_code, slave->warning_msg, len);
err_code[len] = 0;
code_len = strlen(err_code);
msg_ptr++;
}
else
{
msg_ptr = slave->warning_msg;
}
msg_len = strlen(msg_ptr);
level_len = strlen(level);
blr_slave_send_fieldcount(router, slave, 3); // 3 columns
blr_slave_send_columndef(router, slave, "Level", BLR_TYPE_STRING, 40, 2);
blr_slave_send_columndef(router, slave, "Code", BLR_TYPE_STRING, 40, 3);
blr_slave_send_columndef(router, slave, "Message", BLR_TYPE_STRING, 80, 4);
blr_slave_send_eof(router, slave, 5);
len = 4 + (1 + level_len) + (1 + code_len) + (1 + msg_len);
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return blr_slave_send_ok(router, slave);
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, len - 4, 24); // Add length of data packet
ptr += 3;
*ptr++ = 0x06; // Sequence number in response
*ptr++ = level_len; // Length of result string
memcpy((char *)ptr, level, level_len); // Result string
ptr += level_len;
*ptr++ = code_len; // Length of result string
if (code_len)
{
memcpy((char *)ptr, err_code, code_len); // Result string
ptr += code_len;
}
*ptr++ = msg_len; // Length of result string
if (msg_len)
{
memcpy((char *)ptr, msg_ptr, msg_len); // Result string
/* ptr += msg_len; Not required unless more data is added */
}
slave->dcb->func.write(slave->dcb, pkt);
return blr_slave_send_eof(router, slave, 7);
}
else
{
return blr_slave_send_ok(router, slave);
}
}
/**
* Handle the response to the SQL command "SHOW [GLOBAL] STATUS LIKE or SHOW STATUS LIKE
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @param stmt The SQL statement
* @return Non-zero if the variable is handled, 0 if variable is unknown, -1 for syntax error
*/
static int
blr_slave_handle_status_variables(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, char *stmt)
{
char *brkb = NULL;
char *word = NULL;
/* SPACE,TAB,= */
char *sep = " ,=";
if ((word = strtok_r(stmt, sep, &brkb)) == NULL)
{
return -1;
}
else if (strcasecmp(word, "LIKE") == 0)
{
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
{
MXS_ERROR("%s: Missing LIKE clause in SHOW [GLOBAL] STATUS.",
router->service->name);
return -1;
}
else if (strcasecmp(word, "'Uptime'") == 0)
{
char uptime[41] = "";
snprintf(uptime, 40, "%d", maxscale_uptime());
return blr_slave_send_status_variable(router, slave, "Uptime", uptime, BLR_TYPE_INT);
}
else
{
return 0;
}
}
else
{
return -1;
}
}
/**
* Send the response to the SQL command "SHOW [GLOBAL] STATUS LIKE 'xxx'
*
* @param router The binlog router instance
* @param slave The slave server to which we are sending the response
* @param variable The variable name
* @param value The variable value
* @param column_type The variable value type (string or int)
* @return Non-zero if data was sent
*/
static int
blr_slave_send_status_variable(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, char *variable, char *value,
int column_type)
{
GWBUF *pkt;
uint8_t *ptr;
int len, vers_len, seqno = 2;
char *p = MXS_STRDUP_A(variable);
int var_len;
char *old_ptr = p;
/* Remove heading and trailing "'" */
if (*p == '\'')
{
p++;
}
if (p[strlen(p) - 1] == '\'')
{
p[strlen(p) - 1] = '\0';
}
var_len = strlen(p);
/* force lowercase */
for (int i = 0; i < var_len; i++)
{
p[i] = tolower(p[i]);
}
/* First char is uppercase */
p[0] = toupper(p[0]);
blr_slave_send_fieldcount(router, slave, 2);
blr_slave_send_columndef_with_status_schema(router, slave, "Variable_name", BLR_TYPE_STRING, 40, seqno++);
blr_slave_send_columndef_with_status_schema(router, slave, "Value", column_type, 40, seqno++);
blr_slave_send_eof(router, slave, seqno++);
vers_len = strlen(value);
len = 5 + vers_len + var_len + 1;
if ((pkt = gwbuf_alloc(len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, vers_len + 2 + var_len, 24); // Add length of data packet
ptr += 3;
*ptr++ = seqno++; // Sequence number in response
*ptr++ = var_len; // Length of result string
memcpy((char *)ptr, p, var_len); // Result string with var name
ptr += var_len;
*ptr++ = vers_len; // Length of result string
memcpy((char *)ptr, value, vers_len); // Result string with var value
/* ptr += vers_len; Not required unless more data is added */
slave->dcb->func.write(slave->dcb, pkt);
MXS_FREE(old_ptr);
return blr_slave_send_eof(router, slave, seqno++);
}
/**
* Send the column definition packet for a STATUS variable in a response packet sequence.
*
* It adds information_schema.STATUS and variables and variable_name
*
* @param router The router
* @param slave The slave connection
* @param name Name of the column
* @param type Column type
* @param len Column length
* @param seqno Packet sequence number
* @return Non-zero on success
*/
static int
blr_slave_send_columndef_with_status_schema(ROUTER_INSTANCE *router,
ROUTER_SLAVE *slave,
char *name,
int type,
int len,
uint8_t seqno)
{
GWBUF *pkt;
uint8_t *ptr;
int info_len = strlen("information_schema");
int virtual_table_name_len = strlen("STATUS");
int table_name_len = strlen("STATUS");
int column_name_len = strlen(name);
int orig_column_name_len = strlen("VARIABLE_NAME");
int packet_data_len = 0;
char *ptr_name_start = name;
if (strcasecmp(ptr_name_start, "value") == 0)
{
orig_column_name_len = strlen("VARIABLE_VALUE");
}
packet_data_len = 22 + strlen(name) + info_len + virtual_table_name_len + table_name_len +
orig_column_name_len;
if ((pkt = gwbuf_alloc(4 + packet_data_len)) == NULL)
{
return 0;
}
ptr = GWBUF_DATA(pkt);
encode_value(ptr, packet_data_len, 24); // Add length of data packet
ptr += 3;
*ptr++ = seqno; // Sequence number in response
*ptr++ = 3; // Catalog is always def
*ptr++ = 'd';
*ptr++ = 'e';
*ptr++ = 'f';
*ptr++ = info_len; // Schema name length
strcpy((char *)ptr, "information_schema");
ptr += info_len;
*ptr++ = virtual_table_name_len; // virtual table name length
strcpy((char *)ptr, "STATUS");
ptr += virtual_table_name_len;
*ptr++ = table_name_len; // Table name length
strcpy((char *)ptr, "STATUS");
ptr += table_name_len;
*ptr++ = column_name_len; // Column name length;
while (*name)
{
*ptr++ = *name++; // Copy the column name
}
*ptr++ = orig_column_name_len; // Orginal column name
if (strcasecmp(ptr_name_start, "value") == 0)
{
strcpy((char *)ptr, "VARIABLE_VALUE");
}
else
{
strcpy((char *)ptr, "VARIABLE_NAME");
}
ptr += orig_column_name_len;
*ptr++ = 0x0c; // Length of next fields always 12
*ptr++ = 0x3f; // Character set
*ptr++ = 0;
encode_value(ptr, len, 32); // Add length of column
ptr += 4;
*ptr++ = type;
*ptr++ = 0x81; // Two bytes of flags
if (type == 0xfd)
{
*ptr++ = 0x1f;
}
else
{
*ptr++ = 0x00;
}
*ptr++ = 0;
*ptr++ = 0;
*ptr++ = 0;
return slave->dcb->func.write(slave->dcb, pkt);
}
/**
* The heartbeat check function called from the housekeeper for registered slaves.
*
* @param router Current router instance
*/
static void
blr_send_slave_heartbeat(void *inst)
{
ROUTER_SLAVE *sptr = NULL;
ROUTER_INSTANCE *router = (ROUTER_INSTANCE *) inst;
time_t t_now = time(0);
spinlock_acquire(&router->lock);
sptr = router->slaves;
while (sptr)
{
/* skip servers with state = 0 */
if ( (sptr->state == BLRS_DUMPING) && (sptr->heartbeat > 0) &&
((t_now + 1 - sptr->lastReply) >= sptr->heartbeat) )
{
MXS_NOTICE("Sending Heartbeat to slave server-id %d. "
"Heartbeat interval is %d, last event time is %lu",
sptr->serverid, sptr->heartbeat,
(unsigned long)sptr->lastReply);
blr_slave_send_heartbeat(router, sptr);
sptr->lastReply = t_now;
}
sptr = sptr->next;
}
spinlock_release(&router->lock);
}
/**
* Create and send an hearbeat packet to be sent to a registered slave server
*
* @param router The current route rinstance
* @param slave The current slave connection
* @return Number of bytes sent
*/
static int
blr_slave_send_heartbeat(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave)
{
REP_HEADER hdr;
GWBUF *resp;
uint8_t *ptr;
int len = BINLOG_EVENT_HDR_LEN;
uint32_t chksum;
int filename_len = strlen(slave->binlogfile);
/* Add CRC32 4 bytes */
if (!slave->nocrc)
{
len += 4;
}
/* add binlogname to data content len */
len += filename_len;
/**
* Alloc buffer for network binlog stream:
*
* 4 bytes header (3 for pkt len + 1 seq.no)
* 1 byte for Ok / ERR
* n bytes data content
*
* Total = 5 bytes + len
*/
resp = gwbuf_alloc(5 + len);
/* The OK/Err byte is part of payload */
hdr.payload_len = len + 1;
/* Add sequence no */
hdr.seqno = slave->seqno++;
/* Add OK */
hdr.ok = 0;
/* Add timestamp: 0 */
hdr.timestamp = 0L;
/* Set Event Type */
hdr.event_type = HEARTBEAT_EVENT;
/* Add master server id */
hdr.serverid = router->masterid;
/* Add event size */
hdr.event_size = len;
/* Add Next Pos */
hdr.next_pos = slave->binlog_pos;
/* Add flags */
hdr.flags = 0x20;
/* point just after the header */
ptr = blr_build_header(resp, &hdr);
/* Copy binlog name */
memcpy(ptr, slave->binlogfile, filename_len);
ptr += filename_len;
/* Add the CRC32 */
if (!slave->nocrc)
{
chksum = crc32(0L, NULL, 0);
chksum = crc32(chksum, GWBUF_DATA(resp) + 5, hdr.event_size - 4);
encode_value(ptr, chksum, 32);
}
/* Write the packet */
return slave->dcb->func.write(slave->dcb, resp);
}
/**
* Skip the ' char and return pointer to new start position.
* The last ' char is removed.
*
* @param input The input string
* @return Position after first '
*/
char *
blr_escape_config_string(char *input)
{
char *ptr;
char *end;
ptr = strchr(input, '\'');
if (!ptr)
{
return input;
}
else
{
if (ptr+1)
{
ptr++;
}
else
{
*ptr = '\0';
}
}
end = strchr(ptr, '\'');
if (end)
{
*end = '\0';
}
return ptr;
}
/**
* Change the replication SSL options
*
* @param router Current router instance
* @param config The current config
* @param error_message Pre-allocated string for error message, BINLOG_ERROR_MSG_LEN + 1 bytes
* @return 1 for applied change, 0 no changes and -1 for errors
*/
static int
blr_set_master_ssl(ROUTER_INSTANCE *router, CHANGE_MASTER_OPTIONS config, char *error_message)
{
SSL_LISTENER *server_ssl = NULL;
int updated = 0;
if (config.ssl_enabled)
{
router->ssl_enabled = atoi(config.ssl_enabled);
updated++;
}
if (router->ssl_enabled == false)
{
/* Free SSL struct */
blr_free_ssl_data(router);
}
else
{
/* Check for existing SSL struct */
if (router->service->dbref->server->server_ssl)
{
server_ssl = router->service->dbref->server->server_ssl;
server_ssl->ssl_init_done = false;
}
else
{
/* Allocate SSL struct for backend connection */
if ((server_ssl = MXS_CALLOC(1, sizeof(SSL_LISTENER))) == NULL)
{
router->ssl_enabled = false;
/* Report back the error */
snprintf(error_message, BINLOG_ERROR_MSG_LEN,
"CHANGE MASTER TO: Error allocating memory for SSL struct"
" in blr_set_master_ssl");
return -1;
}
/* Set some SSL defaults */
server_ssl->ssl_init_done = false;
server_ssl->ssl_method_type = SERVICE_SSL_TLS_MAX;
server_ssl->ssl_cert_verify_depth = 9;
/* Set the pointer */
router->service->dbref->server->server_ssl = server_ssl;
}
}
/* Update options in router fields and in server_ssl struct, if present */
if (config.ssl_key)
{
if (server_ssl)
{
MXS_FREE(server_ssl->ssl_key);
server_ssl->ssl_key = MXS_STRDUP_A(blr_escape_config_string(config.ssl_key));
}
MXS_FREE(router->ssl_key);
router->ssl_key = MXS_STRDUP_A(blr_escape_config_string(config.ssl_key));
updated++;
}
if (config.ssl_ca)
{
if (server_ssl)
{
MXS_FREE(server_ssl->ssl_ca_cert);
server_ssl->ssl_ca_cert = MXS_STRDUP_A(blr_escape_config_string(config.ssl_ca));
}
MXS_FREE(router->ssl_ca);
router->ssl_ca = MXS_STRDUP_A(blr_escape_config_string(config.ssl_ca));
updated++;
}
if (config.ssl_cert)
{
if (server_ssl)
{
MXS_FREE(server_ssl->ssl_cert);
server_ssl->ssl_cert = MXS_STRDUP_A(blr_escape_config_string(config.ssl_cert));
}
MXS_FREE(router->ssl_cert);
router->ssl_cert = MXS_STRDUP_A(blr_escape_config_string(config.ssl_cert));
updated++;
}
if (config.ssl_version && server_ssl)
{
char *ssl_version = blr_escape_config_string(config.ssl_version);
if (ssl_version && strlen(ssl_version))
{
if (listener_set_ssl_version(server_ssl, ssl_version) != 0)
{
/* Report back the error */
snprintf(error_message, BINLOG_ERROR_MSG_LEN,
"Unknown parameter value for 'ssl_version': %s",
ssl_version);
return -1;
}
/* Set provided ssl_version in router SSL cfg anyway */
MXS_FREE(router->ssl_version);
router->ssl_version = MXS_STRDUP_A(ssl_version);
updated++;
}
}
if (updated)
return 1;
else
return 0;
}