9817 lines
313 KiB
C++
9817 lines
313 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/bsl11.
|
|
*
|
|
* Change Date: 2023-11-12
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include "blr.hh"
|
|
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <zlib.h>
|
|
#include <vector>
|
|
#include <maxbase/alloc.h>
|
|
#include <maxbase/atomic.h>
|
|
#include <maxscale/clock.h>
|
|
#include <maxscale/dcb.hh>
|
|
#include <maxscale/housekeeper.h>
|
|
#include <maxscale/maxscale.h>
|
|
#include <maxscale/poll.hh>
|
|
#include <maxscale/router.hh>
|
|
#include <maxscale/server.hh>
|
|
#include <maxscale/service.hh>
|
|
#include <maxscale/utils.h>
|
|
#include <maxscale/version.h>
|
|
#include <maxscale/routingworker.hh>
|
|
|
|
using std::string;
|
|
using std::vector;
|
|
|
|
/**
|
|
* This struct is used by sqlite3_exec callback routine
|
|
* for SHOW BINARY LOGS.
|
|
*
|
|
* It stores the next row sequence number,
|
|
* the last binlog file name read from gtid_maps storage
|
|
* and the connected client DCB.
|
|
*/
|
|
typedef struct
|
|
{
|
|
int seq_no; /* Output sequence in result set */
|
|
char* last_file; /* Last binlog file found in GTID repo */
|
|
const char* binlogdir; /* Binlog files cache dir */
|
|
DCB* client; /* Connected client DCB */
|
|
bool use_tree; /* Binlog structure type */
|
|
size_t n_files; /* How many files */
|
|
uint64_t rowid; /* ROWID of router current file*/
|
|
} BINARY_LOG_DATA_RESULT;
|
|
|
|
/** Slave file read EOF handling */
|
|
typedef enum
|
|
{
|
|
SLAVE_EOF_ROTATE = 0,
|
|
SLAVE_EOF_WARNING,
|
|
SLAVE_EOF_ERROR
|
|
} slave_eof_action_t;
|
|
|
|
static char* get_next_token(char* str, const char* delim, char** saveptr);
|
|
extern int load_mysql_users(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);
|
|
void blr_extract_header(register uint8_t* ptr, register REP_HEADER* hdr);
|
|
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,
|
|
const 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,
|
|
const char* new_file);
|
|
static uint32_t blr_slave_send_fde(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
GWBUF* fde);
|
|
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,
|
|
bool all_slaves);
|
|
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,
|
|
const 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,
|
|
const char* msg,
|
|
unsigned int err_num,
|
|
const char* status);
|
|
static int blr_apply_change_master(ROUTER_INSTANCE* router,
|
|
int index,
|
|
const ChangeMasterConfig& new_config,
|
|
char* error);
|
|
static int blr_handle_change_master(ROUTER_INSTANCE* router,
|
|
char* command,
|
|
char* error);
|
|
static int blr_set_master_hostname(ROUTER_INSTANCE* router, const char* hostname);
|
|
static int blr_set_master_hostname(ROUTER_INSTANCE* router, const std::string& hostname);
|
|
static int blr_set_master_port(ROUTER_INSTANCE* router, int port);
|
|
static char* blr_set_master_logfile(ROUTER_INSTANCE* router,
|
|
const char* filename,
|
|
char* error);
|
|
static void blr_master_get_config(ROUTER_INSTANCE* router,
|
|
MasterServerConfig* current_master);
|
|
static void blr_master_restore_config(ROUTER_INSTANCE* router,
|
|
const MasterServerConfig& current_master);
|
|
static void blr_master_set_empty_config(ROUTER_INSTANCE* router);
|
|
static void blr_master_apply_config(ROUTER_INSTANCE* router,
|
|
const MasterServerConfig& prev_master);
|
|
static int blr_slave_send_ok_message(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
char* message);
|
|
static bool blr_get_parsed_command_value(char* input, std::string* output);
|
|
static std::string* blr_validate_change_master_option(const char* option, ChangeMasterOptions* config);
|
|
static int blr_set_master_user(ROUTER_INSTANCE* router, const char* user);
|
|
static int blr_set_master_user(ROUTER_INSTANCE* router, const std::string& user);
|
|
static int blr_set_master_password(ROUTER_INSTANCE* router, const char* password);
|
|
static int blr_set_master_password(ROUTER_INSTANCE* router, const std::string& password);
|
|
|
|
static int blr_parse_change_master_command(char* input,
|
|
char* error_string,
|
|
ChangeMasterOptions* config);
|
|
static int blr_handle_change_master_token(char* input,
|
|
char* error,
|
|
ChangeMasterOptions* config);
|
|
static int blr_slave_send_var_value(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
const char* variable,
|
|
const char* value,
|
|
int column_type);
|
|
static int blr_slave_send_variable(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
const char* variable,
|
|
const char* value,
|
|
int column_type);
|
|
static int blr_slave_send_columndef_with_info_schema(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
const char* name,
|
|
int type,
|
|
int len,
|
|
uint8_t seqno);
|
|
int blr_test_parse_change_master_command(char* input,
|
|
char* error_string,
|
|
ChangeMasterOptions* config);
|
|
char* blr_test_set_master_logfile(ROUTER_INSTANCE* router,
|
|
const 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,
|
|
const 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,
|
|
const char* variable,
|
|
const 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,
|
|
const char* name,
|
|
int type,
|
|
int len,
|
|
uint8_t seqno);
|
|
static bool blr_send_slave_heartbeat(void* inst);
|
|
static void blr_slave_send_heartbeat(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave);
|
|
static int blr_set_master_ssl(ROUTER_INSTANCE* router,
|
|
const ChangeMasterConfig& config,
|
|
char* error_message);
|
|
static int blr_slave_read_ste(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
uint32_t fde_end_pos);
|
|
static GWBUF* blr_slave_read_fde(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave);
|
|
static bool blr_handle_simple_select_stmt(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
char* select_stmt);
|
|
static GWBUF* blr_build_fake_rotate_event(ROUTER_SLAVE* slave,
|
|
unsigned long pos,
|
|
const char* filename,
|
|
unsigned long serverid);
|
|
static int blr_send_connect_fake_rotate(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave);
|
|
static bool blr_slave_gtid_request(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
bool req_file,
|
|
unsigned long req_pos);
|
|
|
|
static int blr_send_fake_gtid_list(ROUTER_SLAVE* slave,
|
|
const char* gtid,
|
|
uint32_t serverid);
|
|
static bool blr_handle_maxwell_stmt(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
const char* maxwell_stmt);
|
|
static bool blr_handle_show_stmt(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
char* show_stmt);
|
|
static bool blr_handle_set_stmt(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
char* set_stmt);
|
|
static bool blr_handle_admin_stmt(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
char* admin_stmt,
|
|
char* admin_options);
|
|
extern unsigned int blr_file_get_next_seqno(const char* filename);
|
|
extern uint32_t blr_slave_get_file_size(const char* filename);
|
|
static void blr_slave_skip_empty_files(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave);
|
|
|
|
static inline void blr_get_file_fullpath(const char* binlog_file,
|
|
const char* root_dir,
|
|
char* full_path,
|
|
const char* f_prefix);
|
|
static int blr_show_binary_logs(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
const char* extra_data);
|
|
|
|
extern bool blr_parse_gtid(const char* gtid, MARIADB_GTID_ELEMS* info);
|
|
static int binary_logs_select_cb(void* data,
|
|
int cols,
|
|
char** values,
|
|
char** names);
|
|
static GWBUF* blr_create_result_row(const char* name,
|
|
const char* value,
|
|
int seq_no);
|
|
static int blr_slave_send_id_ro(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave);
|
|
static bool blr_handle_complex_select(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
const char* col1,
|
|
const char* coln);
|
|
extern bool blr_is_current_binlog(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave);
|
|
extern bool blr_compare_binlogs(const ROUTER_INSTANCE* router,
|
|
const MARIADB_GTID_ELEMS* info,
|
|
const char* r_file,
|
|
const char* s_file);
|
|
static bool blr_purge_binary_logs(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
char* purge_stmt);
|
|
static int binary_logs_find_file_cb(void* data,
|
|
int cols,
|
|
char** values,
|
|
char** names);
|
|
static void blr_log_config_changes(ROUTER_INSTANCE* router,
|
|
const MasterServerConfig& current_master,
|
|
const ChangeMasterConfig& change_master);
|
|
extern void blr_log_disabled_heartbeat(const ROUTER_INSTANCE* inst);
|
|
extern void blr_close_master_in_main(void* data);
|
|
static bool blr_check_connecting_slave(const ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
enum blr_slave_check check);
|
|
static void blr_abort_change_master(ROUTER_INSTANCE* router,
|
|
const MasterServerConfig& current_master,
|
|
const char* error);
|
|
static void blr_slave_abort_dump_request(ROUTER_SLAVE* slave,
|
|
const char* errmsg);
|
|
static bool blr_binlog_change_check(const ROUTER_INSTANCE* router,
|
|
const ChangeMasterConfig& new_config,
|
|
char* error);
|
|
static bool blr_change_binlog_name(ROUTER_INSTANCE* router,
|
|
const char* log_file,
|
|
char** new_logfile,
|
|
char* error);
|
|
static bool blr_change_binlog_name(ROUTER_INSTANCE* router,
|
|
const std::string& log_file,
|
|
char** new_logfile,
|
|
char* error);
|
|
static bool blr_apply_changes(ROUTER_INSTANCE* router,
|
|
const ChangeMasterConfig& new_config,
|
|
char* new_logfile,
|
|
char* error);
|
|
static void blr_slave_info_save(const MARIADB_GTID_INFO* info,
|
|
MARIADB_GTID_INFO* save_info,
|
|
char* save_prefix);
|
|
static void blr_slave_log_next_file_action(const ROUTER_INSTANCE* router,
|
|
const ROUTER_SLAVE* slave,
|
|
const char* c_prefix,
|
|
const char* next_file,
|
|
slave_eof_action_t log_action);
|
|
|
|
/**
|
|
* 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: 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)
|
|
{
|
|
int rv = 0;
|
|
if (slave->state < 0 || slave->state > BLRS_MAXSTATE)
|
|
{
|
|
MXS_ERROR("Invalid slave state machine state (%d) for binlog router.",
|
|
slave->state);
|
|
gwbuf_free(queue);
|
|
return 0;
|
|
}
|
|
|
|
slave->stats.n_requests++;
|
|
switch (MYSQL_COMMAND(queue))
|
|
{
|
|
case COM_QUERY:
|
|
slave->stats.n_queries++;
|
|
rv = blr_slave_query(router, slave, queue);
|
|
break;
|
|
|
|
case COM_REGISTER_SLAVE:
|
|
/* Continue with slave registration */
|
|
rv = blr_slave_register(router, slave, queue);
|
|
break;
|
|
|
|
case COM_BINLOG_DUMP:
|
|
/* Check whether binlog server can accept slave requests */
|
|
if (!blr_check_connecting_slave(router,
|
|
slave,
|
|
BLR_SLAVE_CONNECTING)
|
|
|| /* Check whether connecting slaves can be only MariaDB 10 ones */
|
|
!blr_check_connecting_slave(router,
|
|
slave,
|
|
BLR_SLAVE_IS_MARIADB10)
|
|
|| /**
|
|
* If MariaDB 10 GTID master replication is set
|
|
* only MariaDB 10 GTID slaves can continue the registration.
|
|
*/
|
|
!blr_check_connecting_slave(router,
|
|
slave,
|
|
BLR_SLAVE_HAS_MARIADB10_GTID))
|
|
{
|
|
dcb_close(slave->dcb);
|
|
return 1;
|
|
}
|
|
|
|
/* Request now the binlog records */
|
|
rv = blr_slave_binlog_dump(router, slave, queue);
|
|
|
|
/* Check whether to add the heartbeat check for this slave */
|
|
if (rv && slave->state == BLRS_DUMPING
|
|
&& router->send_slave_heartbeat
|
|
&& slave->heartbeat > 0
|
|
&& !router->slave_heartbeat_task_active)
|
|
{
|
|
router->slave_heartbeat_task_active = true;
|
|
char task_name[BLRM_TASK_NAME_LEN + 1] = "";
|
|
snprintf(task_name,
|
|
BLRM_TASK_NAME_LEN,
|
|
"%s slaves heartbeat send",
|
|
router->service->name());
|
|
|
|
/* Add slave heartbeat check task with 1 second frequency */
|
|
hktask_add(task_name, blr_send_slave_heartbeat, router, 1);
|
|
}
|
|
break;
|
|
|
|
case COM_STATISTICS:
|
|
rv = blr_statistics(router, slave, queue);
|
|
break;
|
|
|
|
case COM_PING:
|
|
rv = blr_ping(router, slave, queue);
|
|
break;
|
|
|
|
case COM_QUIT:
|
|
MXS_DEBUG("COM_QUIT received from slave with server_id %d",
|
|
slave->serverid);
|
|
rv = 1;
|
|
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 rv;
|
|
}
|
|
|
|
/*
|
|
* Return a pointer to where the actual SQL query starts, skipping initial
|
|
* comments and whitespace characters, if there are any.
|
|
*/
|
|
const char* blr_skip_leading_sql_comments(const char* sql_query)
|
|
{
|
|
const char* p = sql_query;
|
|
|
|
while (*p)
|
|
{
|
|
if (*p == '/' && p[1] == '*')
|
|
{
|
|
++p; // skip '/'
|
|
++p; // skip '*'
|
|
while (*p)
|
|
{
|
|
if (*p == '*' && p[1] == '/')
|
|
{
|
|
++p; // skip '*'
|
|
++p; // skip '/'
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
++p;
|
|
}
|
|
}
|
|
}
|
|
else if (isspace(*p))
|
|
{
|
|
++p;
|
|
}
|
|
else
|
|
{
|
|
return p;
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* 16 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()
|
|
* SELECT @@GLOBAL.gtid_domain_id
|
|
* SELECT @@[GLOBAL].gtid_current_pos
|
|
* SELECT @@[global.]server_id, @@[global.]read_only
|
|
*
|
|
* 10 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'
|
|
* SHOW [GLOBAL] STATUS LIKE 'slave_received_heartbeats'
|
|
* SHOW BINARY LOGS
|
|
*
|
|
* 13 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=
|
|
* SET @slave_connect_state=
|
|
* SET @slave_gtid_strict_mode=
|
|
* SET @slave_gtid_ignore_duplicates=
|
|
* SET SQL_MODE=''
|
|
*
|
|
* 4 administrative commands are supported:
|
|
* STOP SLAVE
|
|
* START SLAVE
|
|
* CHANGE MASTER TO
|
|
* RESET SLAVE
|
|
*
|
|
* @param router The router instance
|
|
* @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;
|
|
const char* sep = " ,=";
|
|
char* word, * brkb;
|
|
int query_len;
|
|
char* ptr;
|
|
bool unexpected = true;
|
|
|
|
qtext = (char*)GWBUF_DATA(queue);
|
|
query_len = extract_field((uint8_t*)qtext, 24) - 1;
|
|
qtext += MYSQL_HEADER_LEN + 1; // 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.
|
|
*/
|
|
|
|
/* - 1 - Check and handle possible Maxwell input statement */
|
|
if (blr_handle_maxwell_stmt(router,
|
|
slave,
|
|
query_text))
|
|
{
|
|
MXS_FREE(query_text);
|
|
return 1;
|
|
} /* - 2 - Handle SELECT, SET, SHOW and Admin commands */
|
|
else if ((word = strtok_r(query_text, sep, &brkb)) == NULL)
|
|
{
|
|
MXS_ERROR("%s: Incomplete query.", router->service->name());
|
|
}
|
|
else if (strcasecmp(word, "SELECT") == 0)
|
|
{
|
|
/* Handle SELECT */
|
|
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
|
|
{
|
|
MXS_ERROR("%s: Incomplete select query.", router->service->name());
|
|
}
|
|
else
|
|
{
|
|
if (brkb && strlen(brkb)
|
|
&& blr_handle_complex_select(router,
|
|
slave,
|
|
word,
|
|
brkb))
|
|
{
|
|
MXS_FREE(query_text);
|
|
return 1;
|
|
}
|
|
|
|
if (blr_handle_simple_select_stmt(router,
|
|
slave,
|
|
word))
|
|
{
|
|
MXS_FREE(query_text);
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
/* Handle a special case */
|
|
unexpected = strcasestr(word, "binlog_gtid_pos") == NULL;
|
|
}
|
|
}
|
|
}
|
|
else if (strcasecmp(word, "SHOW") == 0)
|
|
{
|
|
/* Handle SHOW */
|
|
if (blr_handle_show_stmt(router,
|
|
slave,
|
|
brkb))
|
|
{
|
|
MXS_FREE(query_text);
|
|
return 1;
|
|
}
|
|
}
|
|
else if (strcasecmp(query_text, "SET") == 0)
|
|
{
|
|
/* Handle SET */
|
|
if (blr_handle_set_stmt(router,
|
|
slave,
|
|
brkb))
|
|
{
|
|
MXS_FREE(query_text);
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Handle ADMIN commands */
|
|
if (blr_handle_admin_stmt(router,
|
|
slave,
|
|
word,
|
|
brkb))
|
|
{
|
|
MXS_FREE(query_text);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* - 3 - Handle unsuppored statements from client */
|
|
MXS_FREE(query_text);
|
|
|
|
query_text = strndup(qtext, query_len);
|
|
|
|
if (unexpected)
|
|
{
|
|
MXS_ERROR("Unexpected query from '%s'@'%s': %s",
|
|
slave->dcb->user,
|
|
slave->dcb->remote,
|
|
query_text);
|
|
}
|
|
else
|
|
{
|
|
MXS_INFO("Unexpected query from '%s'@'%s', possibly a 10.1 slave: %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)
|
|
{
|
|
if (router->master_state == BLRM_UNCONFIGURED)
|
|
{
|
|
return blr_slave_send_ok(router, slave);
|
|
}
|
|
|
|
if (!master)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Note: The following manipulation of the buffers bypasses the thread-local checks that are present
|
|
* to prevent cross-thread usage of buffers. As the binlogrouter doesn't care about that, we have to
|
|
* avoid the checks in order to prevent debug assertions. This is definitely not something that should
|
|
* be done and a better solution would be to just store the data inside a vector and protect access with
|
|
* a lock.
|
|
*/
|
|
size_t len = 0;
|
|
|
|
for (GWBUF* b = master; b; b = b->next)
|
|
{
|
|
len += GWBUF_LENGTH(b);
|
|
}
|
|
|
|
GWBUF* clone = gwbuf_alloc(len);
|
|
|
|
if (clone)
|
|
{
|
|
uint8_t* data = GWBUF_DATA(clone);
|
|
|
|
for (GWBUF* b = master; b; b = b->next)
|
|
{
|
|
memcpy(data, GWBUF_DATA(b), GWBUF_LENGTH(b));
|
|
data += GWBUF_LENGTH(b);
|
|
}
|
|
|
|
return MXS_SESSION_ROUTE_REPLY(slave->dcb->session, 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, const 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
|
|
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, 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)
|
|
+ MYSQL_HEADER_LEN + 1 + 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;
|
|
// EOF packet to terminate result
|
|
memcpy(ptr, timestamp_eof, sizeof(timestamp_eof));
|
|
return MXS_SESSION_ROUTE_REPLY(slave->dcb->session, 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 = MYSQL_HEADER_LEN + 1 + 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 */
|
|
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, 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 = MYSQL_HEADER_LEN + 1 + 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 */
|
|
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, 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 = MYSQL_HEADER_LEN + 1 + vers_len + strlen(name) + 1;
|
|
if ((pkt = gwbuf_alloc(len)) == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
ptr = GWBUF_DATA(pkt);
|
|
// Add length of data packet
|
|
encode_value(ptr, vers_len + 2 + strlen(name), 24);
|
|
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 */
|
|
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, 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[BINLOG_FNAMELEN + 1];
|
|
char position[BINLOG_FNAMELEN + 1];
|
|
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);
|
|
|
|
snprintf(file, sizeof(file), "%s", router->binlog_name);
|
|
file_len = strlen(file);
|
|
|
|
snprintf(position, sizeof(position), "%lu", router->binlog_position);
|
|
|
|
len = MYSQL_HEADER_LEN + 1 + file_len + strlen(position) + 1 + 3;
|
|
if ((pkt = gwbuf_alloc(len)) == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
ptr = GWBUF_DATA(pkt);
|
|
// Add length of data packet
|
|
encode_value(ptr, len - MYSQL_HEADER_LEN, 24);
|
|
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
|
|
// Result string
|
|
memcpy((char*)ptr, position, strlen(position));
|
|
ptr += strlen(position);
|
|
*ptr++ = 0; // Send 3 empty values
|
|
*ptr++ = 0;
|
|
*ptr++ = 0;
|
|
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
|
|
return blr_slave_send_eof(router, slave, 9);
|
|
}
|
|
|
|
/*
|
|
* Columns to send for GTID in "SHOW SLAVE STATUS" command
|
|
*/
|
|
static const 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",
|
|
NULL
|
|
};
|
|
|
|
/*
|
|
* New columns to send for GTID in "SHOW ALL SLAVES STATUS" command
|
|
*/
|
|
static const char* all_slaves_status_columns[] =
|
|
{
|
|
"Connection_name",
|
|
"Slave_SQL_State",
|
|
NULL
|
|
};
|
|
|
|
/*
|
|
* Columns to send for GTID in "SHOW SLAVE STATUS" MySQL 5.6/7 command
|
|
*/
|
|
static const char* mysql_gtid_status_columns[] =
|
|
{
|
|
"Retrieved_Gtid_Set",
|
|
"Executed_Gtid_Set",
|
|
"Auto_Position",
|
|
NULL
|
|
};
|
|
|
|
/*
|
|
* Columns to send for GTID in "SHOW SLAVE STATUS" MariaDB 10 command
|
|
* and SHOW ALL SLAVES STATUS as well
|
|
*/
|
|
static const char* mariadb10_gtid_status_columns[] =
|
|
{
|
|
"Using_Gtid",
|
|
"Gtid_IO_Pos",
|
|
NULL
|
|
};
|
|
|
|
/*
|
|
* Extra Columns to send in "SHOW ALL SLAVES STATUS" MariaDB 10 command
|
|
*/
|
|
static const char* mariadb10_extra_status_columns[] =
|
|
{
|
|
"Retried_transactions",
|
|
"Max_relay_log_size",
|
|
"Executed_log_entries",
|
|
"Slave_received_heartbeats",
|
|
"Slave_heartbeat_period",
|
|
"Gtid_Slave_Pos",
|
|
NULL
|
|
};
|
|
|
|
/**
|
|
* Send the response to the SQL command "SHOW SLAVE STATUS" or
|
|
* SHOW ALL SLAVES STATUS
|
|
*
|
|
* @param router The binlog router instance
|
|
* @param slave The slave server to which we are sending the response
|
|
* @param all_slaves Whether to use SHOW ALL SLAVES STATUS
|
|
* @return Non-zero if data was sent
|
|
*/
|
|
static int blr_slave_send_slave_status(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
bool all_slaves)
|
|
{
|
|
GWBUF* pkt;
|
|
char column[2048] = "";
|
|
uint8_t* ptr;
|
|
int len, actual_len, col_len, seqno, i;
|
|
char* dyn_column = NULL;
|
|
int max_column_size = sizeof(column);
|
|
int ncols = 0;
|
|
int gtid_cols = 0;
|
|
|
|
/* Count SHOW SLAVE STATUS the columns */
|
|
ncols += MXS_ARRAY_NELEMS(slave_status_columns) - 1;
|
|
|
|
/* Add the new SHOW ALL SLAVES STATUS columns */
|
|
if (all_slaves)
|
|
{
|
|
ncols += MXS_ARRAY_NELEMS(all_slaves_status_columns) - 1;
|
|
ncols += MXS_ARRAY_NELEMS(mariadb10_extra_status_columns) - 1;
|
|
}
|
|
|
|
/* Get the right GTID columns array */
|
|
const char** gtid_status_columns = router->mariadb10_gtid ?
|
|
mariadb10_gtid_status_columns :
|
|
mysql_gtid_status_columns;
|
|
/* Increment ncols with the right GTID columns */
|
|
while (gtid_status_columns[gtid_cols++])
|
|
{
|
|
ncols++;
|
|
}
|
|
|
|
/* Send number of columns */
|
|
blr_slave_send_fieldcount(router, slave, ncols);
|
|
|
|
seqno = 2;
|
|
if (all_slaves)
|
|
{
|
|
/* Send first the column definitions for the all_slaves */
|
|
for (i = 0; all_slaves_status_columns[i]; i++)
|
|
{
|
|
blr_slave_send_columndef(router,
|
|
slave,
|
|
all_slaves_status_columns[i],
|
|
BLR_TYPE_STRING,
|
|
40,
|
|
seqno++);
|
|
}
|
|
}
|
|
|
|
/* Now send column definitions for slave status */
|
|
for (i = 0; slave_status_columns[i]; i++)
|
|
{
|
|
blr_slave_send_columndef(router,
|
|
slave,
|
|
slave_status_columns[i],
|
|
BLR_TYPE_STRING,
|
|
40,
|
|
seqno++);
|
|
}
|
|
|
|
/* Send MariaDB 10 or MySQL 5.6/7 GTID columns */
|
|
for (i = 0; gtid_status_columns[i]; i++)
|
|
{
|
|
blr_slave_send_columndef(router,
|
|
slave,
|
|
gtid_status_columns[i],
|
|
BLR_TYPE_STRING,
|
|
40,
|
|
seqno++);
|
|
}
|
|
|
|
/* Send extra columns for SHOW ALL SLAVES STATUS */
|
|
if (all_slaves)
|
|
{
|
|
for (i = 0; mariadb10_extra_status_columns[i]; i++)
|
|
{
|
|
blr_slave_send_columndef(router,
|
|
slave,
|
|
mariadb10_extra_status_columns[i],
|
|
BLR_TYPE_STRING,
|
|
40,
|
|
seqno++);
|
|
}
|
|
}
|
|
|
|
/* Send EOF for columns def */
|
|
blr_slave_send_eof(router, slave, seqno++);
|
|
|
|
// Max length + 250 bytes error message
|
|
len = MYSQL_HEADER_LEN + 1 + ncols * max_column_size + 250;
|
|
|
|
if ((pkt = gwbuf_alloc(len)) == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
ptr = GWBUF_DATA(pkt);
|
|
// Add length of data packet
|
|
encode_value(ptr, len - MYSQL_HEADER_LEN, 24);
|
|
ptr += 3;
|
|
// Sequence number in response
|
|
*ptr++ = seqno++;
|
|
|
|
if (all_slaves)
|
|
{
|
|
for (i = 0; all_slaves_status_columns[i]; i++)
|
|
{
|
|
*ptr++ = 0; // Empty value
|
|
}
|
|
}
|
|
|
|
// Slave_IO_State
|
|
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;
|
|
|
|
// Master_Host
|
|
snprintf(column,
|
|
max_column_size,
|
|
"%s",
|
|
router->service->dbref->server->address ?
|
|
router->service->dbref->server->address :
|
|
"");
|
|
col_len = strlen(column);
|
|
*ptr++ = col_len; // Length of result string
|
|
memcpy((char*)ptr, column, col_len); // Result string
|
|
ptr += col_len;
|
|
|
|
// Master_User
|
|
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;
|
|
|
|
// Master_Port
|
|
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", router->retry_interval); // 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->orig_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", router->retry_limit);
|
|
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;
|
|
|
|
if (!router->mariadb10_gtid)
|
|
{
|
|
// No GTID support send empty values
|
|
*ptr++ = 0;
|
|
*ptr++ = 0;
|
|
*ptr++ = 0;
|
|
}
|
|
else
|
|
{
|
|
// MariaDB 10 GTID
|
|
// 1 - Add "Using_Gtid"
|
|
sprintf(column,
|
|
"%s",
|
|
router->mariadb10_master_gtid ?
|
|
"Slave_pos" :
|
|
"No");
|
|
col_len = strlen(column);
|
|
*ptr++ = col_len; // Length of result string
|
|
memcpy(ptr, column, col_len); // Result string
|
|
ptr += col_len;
|
|
|
|
// 2 - Add "Gtid_IO_Pos"
|
|
sprintf(column,
|
|
"%s",
|
|
router->last_mariadb_gtid);
|
|
col_len = strlen(column);
|
|
*ptr++ = col_len; // Length of result string
|
|
memcpy(ptr, column, col_len); // Result string
|
|
ptr += col_len;
|
|
}
|
|
|
|
if (all_slaves)
|
|
{
|
|
// Retried_transactions
|
|
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; // Max_relay_log_size
|
|
*ptr++ = 0; // Executed_log_entries
|
|
|
|
// Slave_received_heartbeats
|
|
sprintf(column, "%d", router->stats.n_heartbeats);
|
|
col_len = strlen(column);
|
|
*ptr++ = col_len; // Length of result string
|
|
memcpy((char*)ptr, column, col_len); // Result string
|
|
ptr += col_len;
|
|
|
|
// Slave_heartbeat_period
|
|
sprintf(column, "%lu", router->heartbeat);
|
|
col_len = strlen(column);
|
|
*ptr++ = col_len; // Length of result string
|
|
memcpy((char*)ptr, column, col_len); // Result string
|
|
ptr += col_len;
|
|
|
|
// Gtid_Slave_Pos
|
|
if (!router->mariadb10_gtid)
|
|
{
|
|
// No GTID support send empty values
|
|
*ptr++ = 0;
|
|
}
|
|
else
|
|
{
|
|
sprintf(column,
|
|
"%s",
|
|
router->last_mariadb_gtid);
|
|
col_len = strlen(column);
|
|
*ptr++ = col_len; // Length of result string
|
|
memcpy(ptr, column, col_len); // Result string
|
|
ptr += col_len;
|
|
}
|
|
}
|
|
|
|
*ptr++ = 0;
|
|
|
|
actual_len = ptr - (uint8_t*)GWBUF_DATA(pkt);
|
|
ptr = GWBUF_DATA(pkt);
|
|
// Add length of data packet
|
|
encode_value(ptr, actual_len - MYSQL_HEADER_LEN, 24);
|
|
|
|
// Trim the buffer to the actual size
|
|
pkt = gwbuf_rtrim(pkt, len - actual_len);
|
|
|
|
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, 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 connected slave server
|
|
* @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;
|
|
pthread_mutex_lock(&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 = MYSQL_HEADER_LEN + 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 - MYSQL_HEADER_LEN, 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);
|
|
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
|
|
}
|
|
sptr = sptr->next;
|
|
}
|
|
pthread_mutex_unlock(&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, binlognamelen;
|
|
REP_HEADER hdr;
|
|
uint32_t chksum;
|
|
uint32_t fde_end_pos;
|
|
uint32_t requested_pos;
|
|
|
|
ptr = GWBUF_DATA(queue);
|
|
len = extract_field(ptr, 24);
|
|
binlognamelen = len - 11;
|
|
|
|
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));
|
|
slave->state = BLRS_ERRORED;
|
|
dcb_close(slave->dcb);
|
|
return 1;
|
|
}
|
|
|
|
/* Get the current router binlog file */
|
|
pthread_mutex_lock(&router->binlog_lock);
|
|
strcpy(slave->binlog_name, router->binlog_name);
|
|
pthread_mutex_unlock(&router->binlog_lock);
|
|
|
|
/* Set the safe pos */
|
|
slave->binlog_pos = 4;
|
|
|
|
/* Get the requested pos from packet */
|
|
requested_pos = extract_field(ptr, 32);
|
|
|
|
/* Go ahead: after 4 bytes pos, 2 bytes flag and 4 bytes serverid */
|
|
ptr += 4;
|
|
|
|
uint16_t flags = gw_mysql_get_byte2(ptr);
|
|
/* Check whether connected slave is asking for ANNOTATE_ROWS events */
|
|
if (flags & BLR_REQUEST_ANNOTATE_ROWS_EVENT)
|
|
{
|
|
slave->annotate_rows = true;
|
|
MXS_INFO("Registering slave (server-id %d) asks "
|
|
"for ANNOTATE_ROWS events.",
|
|
slave->serverid);
|
|
}
|
|
|
|
/* Go ahead: after 2 bytes flags and 4 bytes serverid */
|
|
ptr += 2;
|
|
ptr += 4;
|
|
|
|
/* ptr now points to requested filename, if present */
|
|
if (binlognamelen)
|
|
{
|
|
if (binlognamelen > BINLOG_FNAMELEN)
|
|
{
|
|
/* Abort the request */
|
|
char req_file[binlognamelen + 1];
|
|
char errmsg[BINLOG_ERROR_MSG_LEN + 1];
|
|
memcpy(req_file, (char*)ptr, binlognamelen);
|
|
req_file[binlognamelen] = 0;
|
|
|
|
snprintf(errmsg,
|
|
BINLOG_ERROR_MSG_LEN,
|
|
"Requested filename %s is longer than max %d chars.",
|
|
req_file,
|
|
BINLOG_FNAMELEN);
|
|
errmsg[BINLOG_ERROR_MSG_LEN] = '\0';
|
|
|
|
// ERROR
|
|
slave->seqno++;
|
|
blr_slave_abort_dump_request(slave, errmsg);
|
|
|
|
slave->state = BLRS_ERRORED;
|
|
dcb_close(slave->dcb);
|
|
return 1;
|
|
}
|
|
|
|
/* Set the received filename from packet: it could be changed later */
|
|
memcpy(slave->binlog_name, (char*)ptr, binlognamelen);
|
|
slave->binlog_name[binlognamelen] = 0;
|
|
}
|
|
|
|
/**
|
|
* Check MariaDB GTID request
|
|
*/
|
|
if (slave->mariadb10_compat
|
|
&& slave->mariadb_gtid)
|
|
{
|
|
/* Set file and pos accordingly to GTID lookup */
|
|
if (!blr_slave_gtid_request(router,
|
|
slave,
|
|
binlognamelen > 0,
|
|
requested_pos))
|
|
{
|
|
// ERROR
|
|
slave->state = BLRS_ERRORED;
|
|
dcb_close(slave->dcb);
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/**
|
|
* Binlog file has been set from packet data.
|
|
* Now just set the position from packet as well.
|
|
*/
|
|
slave->binlog_pos = requested_pos;
|
|
}
|
|
|
|
/**
|
|
* Check for a pending transaction and possible unsafe position.
|
|
* Force slave disconnection if requested position is unsafe.
|
|
*/
|
|
if (router->trx_safe)
|
|
{
|
|
bool force_disconnect = false;
|
|
|
|
pthread_mutex_lock(&router->binlog_lock);
|
|
if (router->pending_transaction.state > BLRM_NO_TRANSACTION
|
|
&& blr_is_current_binlog(router, slave)
|
|
&& (slave->binlog_pos > router->binlog_position))
|
|
{
|
|
force_disconnect = true;
|
|
}
|
|
pthread_mutex_unlock(&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,
|
|
dcb_get_port(slave->dcb),
|
|
slave->serverid,
|
|
slave->binlog_name,
|
|
(unsigned long)slave->binlog_pos,
|
|
router->binlog_position,
|
|
router->current_pos);
|
|
|
|
slave->state = BLRS_ERRORED;
|
|
|
|
/*
|
|
* Close the slave session and socket
|
|
* The slave will try to reconnect
|
|
*/
|
|
dcb_close(slave->dcb);
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
MXS_DEBUG("%s: Slave %s:%i, COM_BINLOG_DUMP: binlog name '%s', length %lu, "
|
|
"from position %lu.",
|
|
router->service->name(),
|
|
slave->dcb->remote,
|
|
dcb_get_port(slave->dcb),
|
|
slave->binlog_name,
|
|
strlen(slave->binlog_name),
|
|
(unsigned long)slave->binlog_pos);
|
|
|
|
/* Check first the requested file exists */
|
|
if (!blr_binlog_file_exists(router, &slave->f_info))
|
|
{
|
|
char errmsg[BINLOG_ERROR_MSG_LEN + 1];
|
|
|
|
snprintf(errmsg,
|
|
BINLOG_ERROR_MSG_LEN,
|
|
"Requested file name '%s' doesn't exist",
|
|
slave->binlog_name);
|
|
errmsg[BINLOG_ERROR_MSG_LEN] = '\0';
|
|
|
|
// ERROR
|
|
slave->seqno++;
|
|
blr_slave_abort_dump_request(slave, errmsg);
|
|
|
|
slave->state = BLRS_ERRORED;
|
|
dcb_close(slave->dcb);
|
|
return 1;
|
|
}
|
|
|
|
/* First reply starts from seq = 1 */
|
|
slave->seqno = 1;
|
|
|
|
/**
|
|
* Check whether the request file is empty
|
|
* and try using next file in sequence or next one
|
|
* based on GTID mpas.
|
|
* If one or more files have been skipped then
|
|
* the slave->binlog_pos is set to 4 and
|
|
* slave->binlogname set to new filename.
|
|
*/
|
|
blr_slave_skip_empty_files(router, slave);
|
|
|
|
/* Build and send Fake Rotate Event */
|
|
if (!blr_send_connect_fake_rotate(router, slave))
|
|
{
|
|
char errmsg[BINLOG_ERROR_MSG_LEN + 1];
|
|
snprintf(errmsg,
|
|
BINLOG_ERROR_MSG_LEN,
|
|
"Cannot send Fake Rotate Event for '%s'",
|
|
slave->binlog_name);
|
|
errmsg[BINLOG_ERROR_MSG_LEN] = '\0';
|
|
// ERROR
|
|
blr_slave_abort_dump_request(slave, errmsg);
|
|
slave->state = BLRS_ERRORED;
|
|
dcb_close(slave->dcb);
|
|
return 1;
|
|
}
|
|
|
|
/* set lastEventReceived */
|
|
slave->lastEventReceived = ROTATE_EVENT;
|
|
|
|
/* set lastReply for slave heartbeat check */
|
|
if (router->send_slave_heartbeat)
|
|
{
|
|
slave->lastReply = time(0);
|
|
}
|
|
|
|
/* Read Format Description Event */
|
|
GWBUF* fde = blr_slave_read_fde(router, slave);
|
|
if (fde == NULL)
|
|
{
|
|
char errmsg[BINLOG_ERROR_MSG_LEN + 1];
|
|
snprintf(errmsg,
|
|
BINLOG_ERROR_MSG_LEN,
|
|
"Cannot read FDE event from file '%s'",
|
|
slave->binlog_name);
|
|
errmsg[BINLOG_ERROR_MSG_LEN] = '\0';
|
|
// ERROR
|
|
blr_slave_abort_dump_request(slave, errmsg);
|
|
slave->state = BLRS_ERRORED;
|
|
dcb_close(slave->dcb);
|
|
return 1;
|
|
}
|
|
|
|
/* FDE ends at pos 4 + FDE size */
|
|
fde_end_pos = 4 + GWBUF_LENGTH(fde);
|
|
|
|
/* Send a Fake FORMAT_DESCRIPTION_EVENT */
|
|
if (slave->binlog_pos != 4)
|
|
{
|
|
if (!blr_slave_send_fde(router, slave, fde))
|
|
{
|
|
char errmsg[BINLOG_ERROR_MSG_LEN + 1];
|
|
snprintf(errmsg,
|
|
BINLOG_ERROR_MSG_LEN,
|
|
"Cannot send FDE for file '%s'",
|
|
slave->binlog_name);
|
|
errmsg[BINLOG_ERROR_MSG_LEN] = '\0';
|
|
// ERROR
|
|
blr_slave_abort_dump_request(slave, errmsg);
|
|
slave->state = BLRS_ERRORED;
|
|
dcb_close(slave->dcb);
|
|
gwbuf_free(fde);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* set lastEventReceived */
|
|
slave->lastEventReceived = FORMAT_DESCRIPTION_EVENT;
|
|
|
|
/**
|
|
* Check for MXS_START_ENCRYPTION_EVENT (after FDE) if
|
|
* client request pos is greater than 4
|
|
*
|
|
* TODO: If router has binlog encryption take it
|
|
* otherwise error
|
|
* If no encryption and event found return error
|
|
*
|
|
* If event is found the contest is set into slave struct
|
|
*/
|
|
if (slave->binlog_pos != 4)
|
|
{
|
|
blr_slave_read_ste(router, slave, fde_end_pos);
|
|
}
|
|
|
|
/**
|
|
* Add GTID_LIST Fake Event before sending any new event
|
|
* Note: slave->binlog_pos must not be 4
|
|
*/
|
|
if (slave->binlog_pos != 4
|
|
&& slave->mariadb10_compat
|
|
&& slave->mariadb_gtid)
|
|
{
|
|
if (!blr_send_fake_gtid_list(slave,
|
|
slave->mariadb_gtid,
|
|
router->masterid))
|
|
{
|
|
char errmsg[BINLOG_ERROR_MSG_LEN + 1];
|
|
snprintf(errmsg,
|
|
BINLOG_ERROR_MSG_LEN,
|
|
"Cannot send Fake GTID List Event for '%s'",
|
|
slave->binlog_name);
|
|
errmsg[BINLOG_ERROR_MSG_LEN] = '\0';
|
|
// ERROR
|
|
blr_slave_abort_dump_request(slave, errmsg);
|
|
slave->state = BLRS_ERRORED;
|
|
dcb_close(slave->dcb);
|
|
gwbuf_free(fde);
|
|
return 1;
|
|
}
|
|
slave->lastEventReceived = MARIADB10_GTID_GTID_LIST_EVENT;
|
|
}
|
|
|
|
/* Set dcb_callback for the events reading routine */
|
|
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,
|
|
dcb_get_port(slave->dcb),
|
|
slave->serverid,
|
|
slave->binlog_name,
|
|
(unsigned long)slave->binlog_pos);
|
|
|
|
/* Force the slave to call catchup routine */
|
|
poll_fake_write_event(slave->dcb);
|
|
|
|
gwbuf_free(fde);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @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];
|
|
MARIADB_GTID_INFO* f_tree = router->storage_type == BLR_BINLOG_STORAGE_TREE ?
|
|
&slave->f_info :
|
|
NULL;
|
|
|
|
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;
|
|
|
|
pthread_mutex_lock(&router->binlog_lock);
|
|
|
|
do_return = 0;
|
|
|
|
/* check for a pending transaction and safe position */
|
|
if (router->pending_transaction.state > BLRM_NO_TRANSACTION
|
|
&& blr_is_current_binlog(router, slave)
|
|
&& (slave->binlog_pos > router->binlog_position))
|
|
{
|
|
do_return = 1;
|
|
}
|
|
|
|
pthread_mutex_unlock(&router->binlog_lock);
|
|
|
|
if (do_return)
|
|
{
|
|
pthread_mutex_lock(&slave->catch_lock);
|
|
slave->cstate &= ~CS_BUSY;
|
|
slave->cstate |= CS_EXPECTCB;
|
|
pthread_mutex_unlock(&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
|
|
|
|
// Prefix for BLR_BINLOG_STORAGE_TREE
|
|
char t_prefix[BINLOG_FILE_EXTRA_INFO] = "";
|
|
|
|
if (file == NULL)
|
|
{
|
|
rotating = router->rotating;
|
|
if ((file = blr_open_binlog(router,
|
|
slave->binlog_name,
|
|
f_tree)) == NULL)
|
|
{
|
|
char err_msg[BINLOG_ERROR_MSG_LEN + 1];
|
|
err_msg[BINLOG_ERROR_MSG_LEN] = '\0';
|
|
|
|
if (rotating)
|
|
{
|
|
pthread_mutex_lock(&slave->catch_lock);
|
|
slave->cstate |= CS_EXPECTCB;
|
|
slave->cstate &= ~CS_BUSY;
|
|
pthread_mutex_unlock(&slave->catch_lock);
|
|
poll_fake_write_event(slave->dcb);
|
|
return rval;
|
|
}
|
|
|
|
/* Fill the file prefix */
|
|
if (f_tree)
|
|
{
|
|
sprintf(t_prefix,
|
|
"%" PRIu32 "/%" PRIu32 "/",
|
|
f_tree->gtid_elms.domain_id,
|
|
f_tree->gtid_elms.server_id);
|
|
}
|
|
|
|
MXS_ERROR("Slave %s:%i, server-id %d, binlog '%s%s': blr_slave_catchup "
|
|
"failed to open binlog file.",
|
|
slave->dcb->remote,
|
|
dcb_get_port(slave->dcb),
|
|
slave->serverid,
|
|
t_prefix,
|
|
slave->binlog_name);
|
|
|
|
slave->cstate &= ~CS_BUSY;
|
|
slave->state = BLRS_ERRORED;
|
|
|
|
snprintf(err_msg,
|
|
BINLOG_ERROR_MSG_LEN,
|
|
"Failed to open binlog '%s'",
|
|
slave->binlog_name);
|
|
|
|
/* Send error that stops slave replication */
|
|
blr_send_custom_error(slave->dcb,
|
|
slave->seqno++,
|
|
0,
|
|
err_msg,
|
|
"HY000",
|
|
BINLOG_FATAL_ERROR_READING);
|
|
|
|
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;
|
|
|
|
/* Loop read binlog events from slave binlog file */
|
|
while (burst-- && burst_size > 0
|
|
&& /* Read one binlog event */
|
|
(record = blr_read_binlog(router,
|
|
file,
|
|
slave->binlog_pos,
|
|
&hdr,
|
|
read_errmsg,
|
|
slave->encryption_ctx)) != NULL)
|
|
{
|
|
char binlog_name[BINLOG_FNAMELEN + 1];
|
|
uint32_t binlog_pos;
|
|
uint32_t event_size;
|
|
|
|
/* Get up to date file prefix */
|
|
if (f_tree)
|
|
{
|
|
sprintf(t_prefix,
|
|
"%" PRIu32 "/%" PRIu32 "/",
|
|
slave->f_info.gtid_elms.domain_id,
|
|
slave->f_info.gtid_elms.server_id);
|
|
}
|
|
|
|
strcpy(binlog_name, slave->binlog_name);
|
|
binlog_pos = slave->binlog_pos;
|
|
|
|
/**
|
|
* Don't sent special events generated by MaxScale
|
|
* or ANNOTATE_ROWS events if not requested
|
|
*/
|
|
if (hdr.event_type == MARIADB10_START_ENCRYPTION_EVENT
|
|
|| hdr.event_type == IGNORABLE_EVENT
|
|
|| (!slave->annotate_rows
|
|
&& hdr.event_type == MARIADB_ANNOTATE_ROWS_EVENT))
|
|
{
|
|
/* In case of file rotation or pos = 4 the events
|
|
* are sent from position 4 and the new FDE at pos 4 is read.
|
|
* We need to check whether the first event after FDE
|
|
* is the MARIADB10_MXS_START_ENCRYPTION_EVENT of the new file.
|
|
*
|
|
* Read it if slave->encryption_ctx is NULL and
|
|
* set the slave->encryption_ctx accordingly
|
|
*/
|
|
pthread_mutex_lock(&slave->catch_lock);
|
|
|
|
if (hdr.event_type == MARIADB10_START_ENCRYPTION_EVENT
|
|
&& !slave->encryption_ctx)
|
|
{
|
|
/* read it, set slave & file context */
|
|
uint8_t* record_ptr = GWBUF_DATA(record);
|
|
void* mem = MXS_CALLOC(1, sizeof(SLAVE_ENCRYPTION_CTX));
|
|
SLAVE_ENCRYPTION_CTX* encryption_ctx;
|
|
encryption_ctx = static_cast<SLAVE_ENCRYPTION_CTX*>(mem);
|
|
|
|
MXS_ABORT_IF_NULL(encryption_ctx);
|
|
record_ptr += BINLOG_EVENT_HDR_LEN;
|
|
encryption_ctx->binlog_crypto_scheme = record_ptr[0];
|
|
memcpy(&encryption_ctx->binlog_key_version,
|
|
record_ptr + 1,
|
|
BLRM_KEY_VERSION_LENGTH);
|
|
memcpy(encryption_ctx->nonce,
|
|
record_ptr + 1 + BLRM_KEY_VERSION_LENGTH,
|
|
BLRM_NONCE_LENGTH);
|
|
|
|
/* Save current first_enc_event_pos */
|
|
encryption_ctx->first_enc_event_pos = hdr.next_pos;
|
|
|
|
/* set the encryption ctx into slave */
|
|
slave->encryption_ctx = encryption_ctx;
|
|
|
|
MXS_INFO("Start Encryption event found while reading. "
|
|
"Binlog '%s%s' is encrypted. First event at %lu",
|
|
t_prefix,
|
|
slave->binlog_name,
|
|
(unsigned long)hdr.next_pos);
|
|
}
|
|
/* MARIADB_ANNOTATE_ROWS_EVENT is skipped: just log that */
|
|
else if (hdr.event_type == MARIADB_ANNOTATE_ROWS_EVENT)
|
|
{
|
|
MXS_INFO("Skipping ANNOTATE_ROWS event [%s] of size %lu while "
|
|
"reading binlog '%s%s' at %lu",
|
|
blr_get_event_description(router, hdr.event_type),
|
|
(unsigned long)hdr.event_size,
|
|
t_prefix,
|
|
slave->binlog_name,
|
|
(unsigned long)slave->binlog_pos);
|
|
}
|
|
else
|
|
{
|
|
MXS_INFO("Found ignorable event [%s] of size %lu while "
|
|
"reading binlog '%s%s' at %lu",
|
|
blr_get_event_description(router, hdr.event_type),
|
|
(unsigned long)hdr.event_size,
|
|
t_prefix,
|
|
slave->binlog_name,
|
|
(unsigned long)slave->binlog_pos);
|
|
}
|
|
|
|
/* set next pos */
|
|
slave->binlog_pos = hdr.next_pos;
|
|
|
|
pthread_mutex_unlock(&slave->catch_lock);
|
|
|
|
gwbuf_free(record);
|
|
record = NULL;
|
|
|
|
break;
|
|
}
|
|
|
|
/* Handle ROTATE_EVENT */
|
|
if (hdr.event_type == ROTATE_EVENT)
|
|
{
|
|
int64_t beat1 = mxs_clock();
|
|
blr_close_binlog(router, file);
|
|
int64_t beat2 = mxs_clock();
|
|
if (beat2 - beat1 > 1)
|
|
{
|
|
MXS_ERROR("blr_close_binlog took %ld maxscale beats", beat2 - beat1);
|
|
}
|
|
/* Set new file in slave->binlog_name */
|
|
blr_slave_rotate(router, slave, GWBUF_DATA(record));
|
|
|
|
/* reset the encryption context */
|
|
MXS_FREE(slave->encryption_ctx);
|
|
slave->encryption_ctx = NULL;
|
|
|
|
beat1 = mxs_clock();
|
|
|
|
#ifdef BLFILE_IN_SLAVE
|
|
if ((slave->file = blr_open_binlog(router,
|
|
slave->binlog_name,
|
|
f_tree)) == NULL)
|
|
#else
|
|
if ((file = blr_open_binlog(router,
|
|
slave->binlog_name,
|
|
f_tree)) == NULL)
|
|
#endif
|
|
{
|
|
char err_msg[BINLOG_ERROR_MSG_LEN + 1];
|
|
err_msg[BINLOG_ERROR_MSG_LEN] = '\0';
|
|
if (rotating)
|
|
{
|
|
pthread_mutex_lock(&slave->catch_lock);
|
|
slave->cstate |= CS_EXPECTCB;
|
|
slave->cstate &= ~CS_BUSY;
|
|
pthread_mutex_unlock(&slave->catch_lock);
|
|
poll_fake_write_event(slave->dcb);
|
|
return rval;
|
|
}
|
|
|
|
/* Refresh file prefix */
|
|
if (f_tree)
|
|
{
|
|
sprintf(t_prefix,
|
|
"%" PRIu32 "/%" PRIu32 "/",
|
|
slave->f_info.gtid_elms.domain_id,
|
|
slave->f_info.gtid_elms.server_id);
|
|
}
|
|
|
|
MXS_ERROR("Slave %s:%i, server-id %d, binlog '%s%s': blr_slave_catchup "
|
|
"failed to open binlog file in rotate event",
|
|
slave->dcb->remote,
|
|
dcb_get_port(slave->dcb),
|
|
slave->serverid,
|
|
t_prefix,
|
|
slave->binlog_name);
|
|
|
|
slave->state = BLRS_ERRORED;
|
|
|
|
snprintf(err_msg,
|
|
BINLOG_ERROR_MSG_LEN,
|
|
"Failed to open binlog '%s' in rotate event",
|
|
slave->binlog_name);
|
|
|
|
/* Send error that stops slave replication */
|
|
blr_send_custom_error(slave->dcb,
|
|
slave->seqno,
|
|
0,
|
|
err_msg,
|
|
"HY000",
|
|
BINLOG_FATAL_ERROR_READING);
|
|
|
|
gwbuf_free(record);
|
|
record = NULL;
|
|
|
|
slave->state = BLRS_ERRORED;
|
|
dcb_close(slave->dcb);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef BLFILE_IN_SLAVE
|
|
file = slave->file;
|
|
#endif
|
|
if (mxs_clock() - beat1 > 1)
|
|
{
|
|
MXS_ERROR("blr_open_binlog took %lu beats",
|
|
mxs_clock() - beat1);
|
|
}
|
|
}
|
|
|
|
/* Send the binlog event */
|
|
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%s', position %u: "
|
|
"Slave-thread could not send event to slave, "
|
|
"closing connection.",
|
|
slave->dcb->remote,
|
|
dcb_get_port(slave->dcb),
|
|
slave->serverid,
|
|
t_prefix,
|
|
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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* End of while reading
|
|
*
|
|
* Note:
|
|
* - the reading routine can set 'record' pointer to NULL
|
|
* - after reading & sending it is always set to NULL
|
|
* - if the above loop ends due to burst and burstsize values,
|
|
* the 'record' is NULL as well (no reads at all or at least
|
|
* one succesfull read).
|
|
*
|
|
* Now checking read error indicator.
|
|
*/
|
|
|
|
mxb_assert(record == NULL);
|
|
|
|
/* Refresh file prefix */
|
|
if (f_tree)
|
|
{
|
|
sprintf(t_prefix,
|
|
"%" PRIu32 "/%" PRIu32 "/",
|
|
slave->f_info.gtid_elms.domain_id,
|
|
slave->f_info.gtid_elms.server_id);
|
|
}
|
|
|
|
if (hdr.ok != SLAVE_POS_READ_OK)
|
|
{
|
|
slave->stats.n_failed_read++;
|
|
|
|
if (hdr.ok == SLAVE_POS_BAD_FD)
|
|
{
|
|
MXS_ERROR("%s Slave %s:%i, server-id %d, binlog '%s%s', %s",
|
|
router->service->name(),
|
|
slave->dcb->remote,
|
|
dcb_get_port(slave->dcb),
|
|
slave->serverid,
|
|
t_prefix,
|
|
slave->binlog_name,
|
|
read_errmsg);
|
|
}
|
|
|
|
if (hdr.ok == SLAVE_POS_BEYOND_EOF)
|
|
{
|
|
MXS_ERROR("%s Slave %s:%i, server-id %d, binlog '%s%s', %s",
|
|
router->service->name(),
|
|
slave->dcb->remote,
|
|
dcb_get_port(slave->dcb),
|
|
slave->serverid,
|
|
t_prefix,
|
|
slave->binlog_name,
|
|
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', %s",
|
|
router->service->name(),
|
|
slave->dcb->remote,
|
|
dcb_get_port(slave->dcb),
|
|
slave->serverid,
|
|
t_prefix,
|
|
slave->binlog_name,
|
|
read_errmsg);
|
|
|
|
pthread_mutex_lock(&slave->catch_lock);
|
|
|
|
slave->state = BLRS_ERRORED;
|
|
|
|
pthread_mutex_unlock(&slave->catch_lock);
|
|
|
|
/*
|
|
* Send an error that will stop slave replication
|
|
*/
|
|
blr_send_custom_error(slave->dcb,
|
|
slave->seqno++,
|
|
0,
|
|
read_errmsg,
|
|
"HY000",
|
|
BINLOG_FATAL_ERROR_READING);
|
|
|
|
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%s', read %d events, "
|
|
"current committed transaction event being sent: %lu, %s",
|
|
router->service->name(),
|
|
slave->dcb->remote,
|
|
dcb_get_port(slave->dcb),
|
|
slave->serverid,
|
|
t_prefix,
|
|
slave->binlog_name,
|
|
slave->stats.n_events - events_before,
|
|
router->current_safe_event,
|
|
read_errmsg);
|
|
hdr.ok = SLAVE_POS_READ_OK;
|
|
}
|
|
}
|
|
|
|
/* Remove BUSY state */
|
|
pthread_mutex_lock(&slave->catch_lock);
|
|
slave->cstate &= ~CS_BUSY;
|
|
pthread_mutex_unlock(&slave->catch_lock);
|
|
|
|
mxb_assert(hdr.ok == SLAVE_POS_READ_OK);
|
|
|
|
/**
|
|
* Check now slave position with read indicator = SLAVE_POS_READ_OK
|
|
*
|
|
* Two cases handled:
|
|
* (1) The slave is Up To Date
|
|
* (2) The slave is at EOF of a file which is not the current router file
|
|
*
|
|
*/
|
|
if (slave->binlog_pos == router->binlog_position
|
|
&& blr_is_current_binlog(router, slave))
|
|
{
|
|
/**
|
|
* (1) Same name and pos as current router file: aka Up To Date
|
|
*/
|
|
pthread_mutex_lock(&router->binlog_lock);
|
|
pthread_mutex_lock(&slave->catch_lock);
|
|
|
|
/*
|
|
* Now check again since we hold the router->binlog_lock
|
|
* and slave->catch_lock.
|
|
*/
|
|
if (slave->binlog_pos != router->binlog_position
|
|
|| !blr_is_current_binlog(router, slave))
|
|
{
|
|
slave->cstate |= CS_EXPECTCB;
|
|
pthread_mutex_unlock(&slave->catch_lock);
|
|
pthread_mutex_unlock(&router->binlog_lock);
|
|
|
|
/* Force slave to read events via catchup routine */
|
|
poll_fake_write_event(slave->dcb);
|
|
}
|
|
else
|
|
{
|
|
/**
|
|
* The slave server is up to date!
|
|
*
|
|
* set the CS_WAIT_DATA: this allows notification
|
|
* when new events are received from master server,
|
|
* the call back routine will be called later.
|
|
*/
|
|
slave->cstate |= CS_WAIT_DATA;
|
|
|
|
pthread_mutex_unlock(&slave->catch_lock);
|
|
pthread_mutex_unlock(&router->binlog_lock);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/**
|
|
* (2) Checking End Of File of the slave binlog file
|
|
* and current router file
|
|
*/
|
|
if (slave->binlog_pos >= blr_file_size(file)
|
|
&& router->rotating == 0
|
|
&& !blr_is_current_binlog(router, slave))
|
|
{
|
|
/**
|
|
* This is end of current slave file
|
|
* which is not the current router binlog file
|
|
*/
|
|
char next_file[BINLOG_FNAMELEN + 1] = "";
|
|
MARIADB_GTID_INFO current_info;
|
|
char c_prefix[BINLOG_FILE_EXTRA_INFO] = "";
|
|
bool have_heartbeat = router->send_slave_heartbeat
|
|
&& (slave->heartbeat > 0);
|
|
|
|
/**
|
|
* Save current MARIADB_GTID_INFO detail because
|
|
* calling blr_file_next_exists() overwrites that
|
|
*/
|
|
if (f_tree)
|
|
{
|
|
pthread_mutex_lock(&slave->catch_lock);
|
|
blr_slave_info_save(&slave->f_info, ¤t_info, c_prefix);
|
|
pthread_mutex_unlock(&slave->catch_lock);
|
|
}
|
|
|
|
/**
|
|
* Check now whether the next file exists and it's readable
|
|
*
|
|
* If not, handle some cases
|
|
* if found issue a fake_rotate event
|
|
*/
|
|
if (!blr_file_next_exists(router, slave, next_file))
|
|
{
|
|
/**
|
|
* The next binlog file to read doesn't exist
|
|
* or it's not set.
|
|
*/
|
|
|
|
if (router->mariadb10_master_gtid
|
|
&& router->master_state == BLRM_SLAVE_STOPPED
|
|
&& !router->binlog_name[0])
|
|
{
|
|
/**
|
|
* (1) Don't care about empty router->binlogname in
|
|
* BLRM_SLAVE_STOPPED state when GTID
|
|
* registration is on:
|
|
* set CS_WAIT_DATA and return.
|
|
*/
|
|
pthread_mutex_lock(&slave->catch_lock);
|
|
|
|
if (f_tree)
|
|
{
|
|
/**
|
|
* We need to deal with current slave file:
|
|
* restore first the GTID info into slave->f_info
|
|
*/
|
|
memcpy(&slave->f_info,
|
|
¤t_info,
|
|
sizeof(MARIADB_GTID_INFO));
|
|
}
|
|
|
|
/**
|
|
* We force cachtup state to CS_WAIT_DATA now:
|
|
*
|
|
* The slave can be called by any new master
|
|
* event received (no matter which is the binlog file)
|
|
* or by an heartbeat event.
|
|
*/
|
|
slave->cstate = CS_WAIT_DATA;
|
|
|
|
pthread_mutex_unlock(&slave->catch_lock);
|
|
|
|
#ifndef BLFILE_IN_SLAVE
|
|
/* Close file */
|
|
blr_close_binlog(router, file);
|
|
#endif
|
|
return rval;
|
|
}
|
|
|
|
/**
|
|
* (2) The next file is not available/existent, actions:
|
|
*
|
|
* If router state is BLRM_BINLOGDUMP
|
|
* - abort slave connection if MISSING_FILE_READ_RETRIES is hit
|
|
* or
|
|
* - just log a warning message
|
|
*
|
|
* Note: in any other router state we don't log messages
|
|
*/
|
|
if (router->master_state == BLRM_BINLOGDUMP)
|
|
{
|
|
pthread_mutex_lock(&slave->catch_lock);
|
|
/* Router state is BLRM_BINLOGDUMP (aka replicating) */
|
|
if (slave->stats.n_failed_read < MISSING_FILE_READ_RETRIES)
|
|
{
|
|
slave->stats.n_failed_read++;
|
|
|
|
pthread_mutex_unlock(&slave->catch_lock);
|
|
|
|
/* Log warning for missing file */
|
|
blr_slave_log_next_file_action(router,
|
|
slave,
|
|
c_prefix,
|
|
next_file,
|
|
SLAVE_EOF_WARNING);
|
|
}
|
|
else
|
|
{
|
|
/**
|
|
* Force error and disconnect
|
|
* when exceeding error counter limit
|
|
*/
|
|
slave->state = BLRS_ERRORED;
|
|
|
|
pthread_mutex_unlock(&slave->catch_lock);
|
|
|
|
/* Log error for missing file */
|
|
blr_slave_log_next_file_action(router,
|
|
slave,
|
|
c_prefix,
|
|
next_file,
|
|
SLAVE_EOF_ERROR);
|
|
|
|
/* Send error that stops slave replication */
|
|
blr_send_custom_error(slave->dcb,
|
|
slave->seqno++,
|
|
0,
|
|
"next binlog file to read doesn't exist",
|
|
"HY000",
|
|
BINLOG_FATAL_ERROR_READING);
|
|
|
|
#ifndef BLFILE_IN_SLAVE
|
|
/* Close file */
|
|
blr_close_binlog(router, file);
|
|
#endif
|
|
/* Disconnect client */
|
|
dcb_close(slave->dcb);
|
|
|
|
return 0;
|
|
}
|
|
} // No else branch: no further actions
|
|
|
|
/**
|
|
* We need to deal with current slave file:
|
|
* restore first the GTID info into slave->f_info
|
|
*/
|
|
pthread_mutex_lock(&slave->catch_lock);
|
|
if (f_tree)
|
|
{
|
|
memcpy(&slave->f_info,
|
|
¤t_info,
|
|
sizeof(MARIADB_GTID_INFO));
|
|
}
|
|
|
|
/**
|
|
* We force cachtup state to CS_WAIT_DATA now:
|
|
*
|
|
* The slave can be called by any new master
|
|
* event received (no matter which is the binlog file)
|
|
* or by an heartbeat event.
|
|
*/
|
|
slave->cstate = CS_WAIT_DATA;
|
|
|
|
pthread_mutex_unlock(&slave->catch_lock);
|
|
}
|
|
else
|
|
{
|
|
/* 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.
|
|
*
|
|
* We send a fake_rotate_event to 'next_file'
|
|
* Note:
|
|
* slave->f_info updated by previous call to
|
|
* blr_file_next_exists()
|
|
*/
|
|
blr_slave_log_next_file_action(router,
|
|
slave,
|
|
c_prefix,
|
|
next_file,
|
|
SLAVE_EOF_ROTATE);
|
|
|
|
/* Reset encryption context */
|
|
MXS_FREE(slave->encryption_ctx);
|
|
slave->encryption_ctx = NULL;
|
|
|
|
/* Now pass the next_file to blr_slave_fake_rotate() */
|
|
#ifdef BLFILE_IN_SLAVE
|
|
if (blr_slave_fake_rotate(router,
|
|
slave,
|
|
&slave->file,
|
|
next_file))
|
|
#else
|
|
if (blr_slave_fake_rotate(router,
|
|
slave,
|
|
&file,
|
|
next_file))
|
|
#endif
|
|
{
|
|
pthread_mutex_lock(&slave->catch_lock);
|
|
slave->cstate |= CS_EXPECTCB;
|
|
pthread_mutex_unlock(&slave->catch_lock);
|
|
/**
|
|
* Note:
|
|
* Fake rotate just written to client,
|
|
* no need to call poll_fake_write_event()
|
|
*/
|
|
|
|
// blr_slave_fake_rotate closes the file on success
|
|
file = NULL;
|
|
}
|
|
else
|
|
{
|
|
/* Set ERROR */
|
|
slave->state = BLRS_ERRORED;
|
|
/* Disconnect client */
|
|
dcb_close(slave->dcb);
|
|
#ifndef BLFILE_IN_SLAVE
|
|
/* Close file */
|
|
blr_close_binlog(router, file);
|
|
#endif
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{ /**
|
|
* Still reading from current slave file but
|
|
* nothing has been written to client right now
|
|
* (perhaps some ignorable / skipped events)
|
|
* just retry to read again.
|
|
*/
|
|
pthread_mutex_lock(&slave->catch_lock);
|
|
slave->cstate |= CS_EXPECTCB;
|
|
pthread_mutex_unlock(&slave->catch_lock);
|
|
poll_fake_write_event(slave->dcb);
|
|
}
|
|
}
|
|
|
|
#ifndef BLFILE_IN_SLAVE
|
|
if (file)
|
|
{
|
|
/* Close 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;
|
|
|
|
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)
|
|
{
|
|
pthread_mutex_lock(&slave->catch_lock);
|
|
if (slave->cstate & CS_BUSY)
|
|
{
|
|
pthread_mutex_unlock(&slave->catch_lock);
|
|
return 0;
|
|
}
|
|
slave->cstate &= ~(CS_EXPECTCB);
|
|
slave->cstate |= CS_BUSY;
|
|
pthread_mutex_unlock(&slave->catch_lock);
|
|
|
|
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 router The router instance
|
|
* @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
|
|
|
|
// Remove length of header and position
|
|
len = len - (BINLOG_EVENT_HDR_LEN + 8);
|
|
if (router->master_chksum)
|
|
{
|
|
len -= MYSQL_HEADER_LEN;
|
|
}
|
|
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->binlog_name, ptr + 8, len);
|
|
slave->binlog_name[len] = 0;
|
|
}
|
|
|
|
/**
|
|
* Generate an internal rotate event that we can use to cause
|
|
* the slave to move beyond a binlog file
|
|
* that is missisng the rotate event at the end.
|
|
*
|
|
* The curret binlog file is only closed on success.
|
|
*
|
|
* @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,
|
|
const char* new_file)
|
|
{
|
|
const char* sptr;
|
|
int filenum;
|
|
GWBUF* r_event;
|
|
MARIADB_GTID_INFO* f_tree = router->storage_type == BLR_BINLOG_STORAGE_TREE ?
|
|
&slave->f_info :
|
|
NULL;
|
|
|
|
if ((sptr = strrchr(new_file, '.')) == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* Set Pos = 4 */
|
|
slave->binlog_pos = 4;
|
|
/* Set Filename */
|
|
strcpy(slave->binlog_name, new_file);
|
|
|
|
if ((*filep = blr_open_binlog(router,
|
|
new_file,
|
|
f_tree)) == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* Build Fake Rotate Event */
|
|
r_event = blr_build_fake_rotate_event(slave,
|
|
slave->binlog_pos,
|
|
new_file,
|
|
router->masterid);
|
|
|
|
int ret = r_event ? MXS_SESSION_ROUTE_REPLY(slave->dcb->session, r_event) : 0;
|
|
|
|
/* Close binlog file on success */
|
|
if (ret)
|
|
{
|
|
blr_close_binlog(router, *filep);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Read the format description event FDE from current slave logfile
|
|
*
|
|
* @param router The router instance
|
|
* @param slave The slave to send the event to
|
|
* @return The read FDE event on success or NULL on error
|
|
*/
|
|
static GWBUF* blr_slave_read_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];
|
|
MARIADB_GTID_INFO* f_tree = router->storage_type == BLR_BINLOG_STORAGE_TREE ?
|
|
&slave->f_info :
|
|
NULL;
|
|
|
|
err_msg[BINLOG_ERROR_MSG_LEN] = '\0';
|
|
|
|
memset(&hdr, 0, BINLOG_EVENT_HDR_LEN);
|
|
|
|
if ((file = blr_open_binlog(router,
|
|
slave->binlog_name,
|
|
f_tree)) == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
/* FDE, at pos 4, is not encrypted, pass NULL to last parameter */
|
|
if ((record = blr_read_binlog(router,
|
|
file,
|
|
4,
|
|
&hdr,
|
|
err_msg,
|
|
NULL)) == 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,
|
|
dcb_get_port(slave->dcb),
|
|
slave->serverid,
|
|
slave->binlog_name,
|
|
err_msg);
|
|
}
|
|
|
|
blr_close_binlog(router, file);
|
|
return NULL;
|
|
}
|
|
blr_close_binlog(router, file);
|
|
|
|
return record;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
* @return The FDE event size on success or 0 on error
|
|
*/
|
|
static uint32_t blr_slave_send_fde(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave, GWBUF* fde)
|
|
{
|
|
GWBUF* event;
|
|
uint8_t* ptr;
|
|
uint32_t chksum;
|
|
uint32_t event_size;
|
|
uint8_t* event_ptr;
|
|
|
|
if (fde == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
event_size = GWBUF_LENGTH(fde);
|
|
|
|
if ((event = gwbuf_alloc(MYSQL_HEADER_LEN + 1 + event_size)) == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
ptr = GWBUF_DATA(event);
|
|
|
|
event_size = GWBUF_LENGTH(fde);
|
|
|
|
/* Set payload to event_size + 1 (the ok/err byte) */
|
|
encode_value(ptr, event_size + 1, 32);
|
|
ptr += 3;
|
|
*ptr++ = slave->seqno++;
|
|
*ptr++ = 0; // OK/ERR byte
|
|
|
|
// Copy FDE data
|
|
memcpy(ptr, GWBUF_DATA(fde), event_size);
|
|
|
|
encode_value(ptr, time(0), 32); // Overwrite timestamp
|
|
ptr += 13; // 4 time + 1 type + 4 server_id + 4 event_size
|
|
|
|
/* event_ptr points to position of the next event */
|
|
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(event) + MYSQL_HEADER_LEN + 1 + event_size - BINLOG_EVENT_CRC_SIZE;
|
|
chksum = crc32(0L, NULL, 0);
|
|
chksum = crc32(chksum,
|
|
GWBUF_DATA(event) + MYSQL_HEADER_LEN + 1,
|
|
event_size - BINLOG_EVENT_CRC_SIZE);
|
|
encode_value(ptr, chksum, 32);
|
|
|
|
return MXS_SESSION_ROUTE_REPLY(slave->dcb->session, event);
|
|
}
|
|
|
|
|
|
/**
|
|
* 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(MYSQL_HEADER_LEN + 1)) == 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; // Number of columns
|
|
return MXS_SESSION_ROUTE_REPLY(slave->dcb->session, 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,
|
|
const 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 MXS_SESSION_ROUTE_REPLY(slave->dcb->session, 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 MXS_SESSION_ROUTE_REPLY(slave->dcb->session, 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 = MYSQL_HEADER_LEN + (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 - MYSQL_HEADER_LEN, 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 */
|
|
|
|
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, 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 connected slave server
|
|
* @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;
|
|
|
|
pthread_mutex_lock(&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_in_owning_thread(sptr->dcb);
|
|
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
sptr = sptr->next;
|
|
}
|
|
}
|
|
|
|
pthread_mutex_unlock(&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 connected slave server
|
|
* @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 = 2;
|
|
GWBUF* pkt;
|
|
|
|
/* preparing output result */
|
|
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++);
|
|
|
|
pthread_mutex_lock(&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 = MYSQL_HEADER_LEN + 1 + 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);
|
|
|
|
pthread_mutex_unlock(&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 - MYSQL_HEADER_LEN, 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);
|
|
|
|
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
|
|
|
|
sptr->state = BLRS_UNREGISTERED;
|
|
dcb_close_in_owning_thread(sptr->dcb);
|
|
}
|
|
sptr = sptr->next;
|
|
}
|
|
|
|
pthread_mutex_unlock(&router->lock);
|
|
|
|
blr_slave_send_eof(router, slave, seqno);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Send a MySQL OK packet to the connected client
|
|
*
|
|
* @param router The binlog router instance
|
|
* @param slave The slave server to which we are sending data
|
|
*
|
|
* @return Result of a write call, non-zero if 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 MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
|
|
}
|
|
|
|
/**
|
|
* Send a MySQL OK packet with a message to the client
|
|
*
|
|
* @param router The binlog router instance
|
|
* @param message The message to send
|
|
* @param slave The slave server to which we are sending data
|
|
*
|
|
* @return The write call result: non-zero on success
|
|
*/
|
|
|
|
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 MXS_SESSION_ROUTE_REPLY(slave->dcb->session, 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_close_master_in_main(router);
|
|
}
|
|
}
|
|
|
|
pthread_mutex_lock(&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)
|
|
{
|
|
// Is this dead code? dcb->fd for internal DCBs is always -1
|
|
dcb_close(router->client);
|
|
router->client = NULL;
|
|
}
|
|
}
|
|
|
|
/* Now it is safe to unleash other threads on this router instance */
|
|
router->reconnect_pending = 0;
|
|
router->active_logs = 0;
|
|
|
|
pthread_mutex_unlock(&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->address,
|
|
router->service->dbref->server->port,
|
|
router->binlog_name,
|
|
router->current_pos,
|
|
router->binlog_position);
|
|
|
|
if (router->trx_safe
|
|
&& router->pending_transaction.state > BLRM_NO_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)
|
|
{
|
|
/* 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
|
|
&& router->master_state != BLRM_CONNECTING)
|
|
{
|
|
blr_slave_send_warning_message(router,
|
|
slave,
|
|
"1254:Slave is already running");
|
|
|
|
return 1;
|
|
}
|
|
|
|
pthread_mutex_lock(&router->lock);
|
|
router->master_state = BLRM_UNCONNECTED;
|
|
router->retry_count = 0;
|
|
router->config_index = 0; // Always start from the default configuration.
|
|
pthread_mutex_unlock(&router->lock);
|
|
|
|
/**
|
|
* Check whether to create the new binlog (router->binlog_name)
|
|
*
|
|
* File handling happens only if mariadb10_master_gtid is off:
|
|
* with Master GTID the first file will be created/opened
|
|
* by the fake Rotate Event.
|
|
*/
|
|
|
|
/* Check first for incomplete transaction */
|
|
if (strlen(router->prevbinlog)
|
|
&& strcmp(router->prevbinlog, router->binlog_name) != 0)
|
|
{
|
|
if (router->trx_safe
|
|
&& router->pending_transaction.state > BLRM_NO_TRANSACTION)
|
|
{
|
|
char msg[BINLOG_ERROR_MSG_LEN + 1] = "";
|
|
char file[PATH_MAX + 1] = "";
|
|
struct stat statb;
|
|
unsigned long filelen = 0;
|
|
char t_prefix[BINLOG_FILE_EXTRA_INFO] = "";
|
|
|
|
// Add file prefix
|
|
if (router->storage_type == BLR_BINLOG_STORAGE_TREE)
|
|
{
|
|
sprintf(t_prefix,
|
|
"%" PRIu32 "/%" PRIu32 "/",
|
|
router->mariadb10_gtid_domain,
|
|
router->orig_masterid);
|
|
}
|
|
|
|
// Router current file
|
|
snprintf(file,
|
|
PATH_MAX,
|
|
"%s/%s%s",
|
|
router->binlogdir,
|
|
t_prefix,
|
|
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%s, "
|
|
"starting at pos %lu, "
|
|
"ending at pos %lu. File %s now has length %lu.",
|
|
t_prefix,
|
|
router->prevbinlog,
|
|
router->last_safe_pos,
|
|
filelen,
|
|
router->prevbinlog,
|
|
router->last_safe_pos);
|
|
|
|
/* Truncate previous binlog file to last_safe pos */
|
|
if (truncate(file, router->last_safe_pos) == -1)
|
|
{
|
|
MXS_ERROR("Failed to truncate file: %d, %s",
|
|
errno,
|
|
mxs_strerror(errno));
|
|
}
|
|
|
|
/* Log it */
|
|
MXS_WARNING("A transaction is still opened at pos %lu"
|
|
" File %s%s will be truncated. "
|
|
"Next binlog file is %s at pos %d, "
|
|
"START SLAVE is required again.",
|
|
router->last_safe_pos,
|
|
t_prefix,
|
|
router->prevbinlog,
|
|
router->binlog_name,
|
|
4);
|
|
|
|
pthread_mutex_lock(&router->lock);
|
|
|
|
router->pending_transaction.state = BLRM_NO_TRANSACTION;
|
|
router->last_safe_pos = 0;
|
|
router->master_state = BLRM_UNCONNECTED;
|
|
router->current_pos = 4;
|
|
router->binlog_position = 4;
|
|
router->current_safe_event = 4;
|
|
|
|
pthread_mutex_unlock(&router->lock);
|
|
|
|
/* Send warning message to mysql command */
|
|
blr_slave_send_warning_message(router, slave, msg);
|
|
|
|
return 1;
|
|
}
|
|
/* No pending transaction */
|
|
else
|
|
{
|
|
/**
|
|
* If router->mariadb10_master_gtid is Off then
|
|
* handle file create/append.
|
|
* This means the domain_id and server_id
|
|
* are not taken into account for filename prefix.
|
|
*/
|
|
if (!router->mariadb10_master_gtid)
|
|
{
|
|
/* If the router file is not open, create a new binlog file */
|
|
if (router->binlog_fd == -1)
|
|
{
|
|
blr_file_new_binlog(router, router->binlog_name);
|
|
}
|
|
else
|
|
{
|
|
/* A new binlog file has been created and opened
|
|
* by CHANGE MASTER TO: use it
|
|
*/
|
|
blr_file_append(router, router->binlog_name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Start replication from master */
|
|
blr_start_master_in_main(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->address,
|
|
router->service->dbref->server->port,
|
|
router->binlog_name,
|
|
router->current_pos,
|
|
router->binlog_position);
|
|
|
|
/* Try reloading new users and update cached credentials */
|
|
service_refresh_users(router->service);
|
|
|
|
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,
|
|
const char* msg,
|
|
unsigned int err_num,
|
|
const char* status)
|
|
{
|
|
GWBUF* pkt;
|
|
unsigned char* data;
|
|
int len;
|
|
unsigned int mysql_errno = 0;
|
|
const 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
|
|
|
|
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
|
|
}
|
|
|
|
bool ChangeMasterOptions::validate(ROUTER_INSTANCE* router,
|
|
char* error,
|
|
ChangeMasterConfig* config)
|
|
{
|
|
/* Abort if MASTER_USE_GTID is in use and
|
|
* router->mariadb10_master_gtid is not set
|
|
*/
|
|
if (!router->mariadb10_master_gtid && !this->use_mariadb10_gtid.empty())
|
|
{
|
|
snprintf(error,
|
|
BINLOG_ERROR_MSG_LEN,
|
|
"Cannot use MASTER_USE_GTID. "
|
|
"Enable 'mariadb10_master_gtid' option first.");
|
|
|
|
MXS_ERROR("%s: %s", router->service->name(), error);
|
|
|
|
return false;
|
|
}
|
|
|
|
int heartbeat_period = -1;
|
|
if (!this->heartbeat_period.empty())
|
|
{
|
|
heartbeat_period = (int)strtol(this->heartbeat_period.c_str(), NULL, 10);
|
|
|
|
if (heartbeat_period < 0
|
|
|| (errno == ERANGE)
|
|
|| heartbeat_period > BLR_HEARTBEAT_MAX_INTERVAL)
|
|
{
|
|
snprintf(error,
|
|
BINLOG_ERROR_MSG_LEN,
|
|
"The requested value for the heartbeat period is "
|
|
"either negative or exceeds the maximum allowed "
|
|
"(%d seconds).",
|
|
BLR_HEARTBEAT_MAX_INTERVAL);
|
|
|
|
MXS_ERROR("%s: %s", router->service->name(), error);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int connect_retry = -1;
|
|
if (!this->connect_retry.empty())
|
|
{
|
|
connect_retry = (int)strtol(this->connect_retry.c_str(), NULL, 10);
|
|
if (connect_retry <= 0
|
|
|| (errno == ERANGE))
|
|
{
|
|
snprintf(error,
|
|
BINLOG_ERROR_MSG_LEN,
|
|
"The requested value for MASTER_CONNECT_RETRY "
|
|
"interval is not valid: %s.",
|
|
this->connect_retry.c_str());
|
|
|
|
MXS_ERROR("%s: %s", router->service->name(), error);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int port = -1;
|
|
if (!this->port.empty())
|
|
{
|
|
port = (int)strtol(this->port.c_str(), NULL, 10);
|
|
if ((port < 0) || (port > std::numeric_limits<unsigned short>::max()))
|
|
{
|
|
snprintf(error,
|
|
BINLOG_ERROR_MSG_LEN,
|
|
"The specified value for MASTER_PORT "
|
|
"is not valid: %s.",
|
|
this->port.c_str());
|
|
|
|
MXS_ERROR("%s: %s", router->service->name(), error);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
config->connection_name = this->connection_name;
|
|
config->host = this->host;
|
|
config->port = port;
|
|
config->binlog_file = this->binlog_file;
|
|
config->binlog_pos = this->binlog_pos;
|
|
config->user = this->user;
|
|
config->password = this->password;
|
|
config->ssl_key = this->ssl_key;
|
|
config->ssl_cert = this->ssl_cert;
|
|
config->ssl_ca = this->ssl_ca;
|
|
config->ssl_enabled = this->ssl_enabled.empty() ? false : atoi(this->ssl_enabled.c_str());
|
|
config->ssl_version = this->ssl_version;
|
|
config->use_mariadb10_gtid = this->use_mariadb10_gtid;
|
|
config->heartbeat_period = heartbeat_period;
|
|
config->connect_retry = connect_retry;
|
|
|
|
return true;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
|
|
int validate_connection_name(ROUTER_INSTANCE* router, const std::string& name, char* error)
|
|
{
|
|
static const char DEFAULT_MESSAGE[] =
|
|
"If a connection name is provided, it must be of the format ':N' where N "
|
|
"is an integer larger than 1.";
|
|
|
|
int index = -1;
|
|
|
|
char custom_message[BINLOG_ERROR_MSG_LEN];
|
|
|
|
const char* message = DEFAULT_MESSAGE;
|
|
|
|
if (name.length() == 0)
|
|
{
|
|
index = 0;
|
|
message = nullptr;
|
|
}
|
|
else if (name.length() == 1)
|
|
{
|
|
// We are fine with 'index' == -1, and the message being the default message.
|
|
}
|
|
else // name.length() >= 2.
|
|
{
|
|
if (name.front() == ':')
|
|
{
|
|
string tail = name.substr(1);
|
|
int n = strtol(tail.c_str(), NULL, 10);
|
|
|
|
if ((n > 1) && (std::to_string(n) == tail)) // Nothing funky in the string
|
|
{
|
|
if (router->configs.size() == static_cast<size_t>(n - 1))
|
|
{
|
|
index = n - 1;
|
|
message = nullptr;
|
|
}
|
|
else if (router->configs.size() == 0)
|
|
{
|
|
snprintf(custom_message,
|
|
BINLOG_ERROR_MSG_LEN,
|
|
"The provided connection name '%s' is not valid. Currently "
|
|
"no default connection exists and it must be specified using "
|
|
"a 'CHANGE MASTER TO ...' command without a connection name.",
|
|
name.c_str());
|
|
message = custom_message;
|
|
}
|
|
else
|
|
{
|
|
snprintf(custom_message,
|
|
BINLOG_ERROR_MSG_LEN,
|
|
"The provided connection name '%s' is not valid. Currently "
|
|
"the default connection and %d secondary connections have "
|
|
"been specified and the next valid name for an secondary "
|
|
"connection is ':%d'.",
|
|
name.c_str(),
|
|
(int)router->configs.size() - 1,
|
|
(int)router->configs.size() + 1);
|
|
message = custom_message;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (message)
|
|
{
|
|
snprintf(error, BINLOG_ERROR_MSG_LEN, "%s", message);
|
|
}
|
|
|
|
return index;
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
|
|
int blr_apply_change_master_0(ROUTER_INSTANCE* router,
|
|
const ChangeMasterConfig& new_config,
|
|
char* error)
|
|
{
|
|
mxb_assert(new_config.connection_name.empty());
|
|
|
|
MasterServerConfig current_master;
|
|
|
|
/* save current config option data */
|
|
blr_master_get_config(router, ¤t_master);
|
|
|
|
/* Change the heartbeat */
|
|
if (new_config.heartbeat_period != -1)
|
|
{
|
|
if (new_config.heartbeat_period == 0)
|
|
{
|
|
blr_log_disabled_heartbeat(router);
|
|
}
|
|
router->heartbeat = new_config.heartbeat_period;
|
|
}
|
|
|
|
/* Change the connect retry */
|
|
if (new_config.connect_retry != -1)
|
|
{
|
|
router->retry_interval = new_config.connect_retry;
|
|
}
|
|
|
|
/* Change the replication user */
|
|
blr_set_master_user(router, new_config.user);
|
|
|
|
/* Change the replication password */
|
|
blr_set_master_password(router, new_config.password);
|
|
|
|
/* Change the master name/address */
|
|
blr_set_master_hostname(router, new_config.host);
|
|
|
|
/* Change the master port */
|
|
blr_set_master_port(router, new_config.port);
|
|
|
|
/* Handle SSL options */
|
|
int ssl_error;
|
|
ssl_error = blr_set_master_ssl(router, new_config, error);
|
|
|
|
if (ssl_error != -1
|
|
&& // No CA cert is defined or only one of CERT or KEY is defined
|
|
(new_config.ssl_ca.empty() || new_config.ssl_cert.empty() != new_config.ssl_key.empty()))
|
|
{
|
|
if (new_config.ssl_enabled)
|
|
{
|
|
snprintf(error,
|
|
BINLOG_ERROR_MSG_LEN,
|
|
"MASTER_SSL=1 but some required options are missing: "
|
|
"check that at least MASTER_SSL_CA is defined");
|
|
ssl_error = -1;
|
|
}
|
|
}
|
|
|
|
if (ssl_error == -1)
|
|
{
|
|
blr_abort_change_master(router,
|
|
current_master,
|
|
error);
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Check if binlog file can be changed
|
|
*
|
|
* Change the binlog filename as from MASTER_LOG_FILE
|
|
* New binlog file could be the next one or current one
|
|
* or empty if router->mariadb10_master_gtid is set.
|
|
*/
|
|
char* master_logfile = NULL;
|
|
if (!blr_binlog_change_check(router,
|
|
new_config,
|
|
error)
|
|
|| !blr_change_binlog_name(router,
|
|
new_config.binlog_file,
|
|
&master_logfile,
|
|
error)
|
|
|| !blr_apply_changes(router,
|
|
new_config,
|
|
master_logfile,
|
|
error))
|
|
{
|
|
blr_abort_change_master(router,
|
|
current_master,
|
|
error);
|
|
MXS_FREE(master_logfile);
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* Log config changes (without passwords) */
|
|
blr_log_config_changes(router, current_master, new_config);
|
|
|
|
MXS_FREE(master_logfile);
|
|
|
|
int change_binlog = 0;
|
|
if (router->master_state == BLRM_UNCONFIGURED)
|
|
{
|
|
change_binlog = 1;
|
|
}
|
|
|
|
return change_binlog;
|
|
}
|
|
}
|
|
|
|
static int blr_apply_change_master(ROUTER_INSTANCE* router,
|
|
int index,
|
|
const ChangeMasterConfig& config,
|
|
char* error)
|
|
{
|
|
int rc = 0;
|
|
|
|
pthread_mutex_lock(&router->lock);
|
|
|
|
if (index == static_cast<int>(router->configs.size()))
|
|
{
|
|
router->configs.push_back(config);
|
|
}
|
|
else
|
|
{
|
|
mxb_assert(index < static_cast<int>(router->configs.size()));
|
|
router->configs[index] = config;
|
|
}
|
|
|
|
if (index == 0)
|
|
{
|
|
rc = blr_apply_change_master_0(router, config, error);
|
|
}
|
|
|
|
pthread_mutex_unlock(&router->lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Get the connection name from a CHANGE MASTER command.
|
|
*
|
|
* @param command What follows "CHANGE MASTER", that is " ['connection-name'] TO ..."
|
|
* @param [out] pConnection_name The connection name, if provided.
|
|
*
|
|
* @return The string following "TO".
|
|
*/
|
|
static char* get_connection_name(char* command, std::string* pConnection_name)
|
|
{
|
|
command = mxb::ltrim(command);
|
|
|
|
char* to = strcasestr(command, "TO");
|
|
|
|
if (!to)
|
|
{
|
|
// No "TO", can't be valid.
|
|
command = nullptr;
|
|
}
|
|
else if (to == command)
|
|
{
|
|
// No connection name.
|
|
command = to + 2;
|
|
}
|
|
else
|
|
{
|
|
// We may have the case "'connection-name' TO"
|
|
char quote = *command;
|
|
|
|
if ((quote == '\'') || (quote == '"'))
|
|
{
|
|
// At least there was a quote.
|
|
++command;
|
|
|
|
char* end = strchr(command, quote);
|
|
|
|
if (!end || (end > to))
|
|
{
|
|
// No closing quote.
|
|
command = nullptr;
|
|
}
|
|
else
|
|
{
|
|
*pConnection_name = std::string(command, end);
|
|
command = to + 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No, must be an invalid command.
|
|
command = nullptr;
|
|
}
|
|
}
|
|
|
|
return command;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
std::string connection_name;
|
|
command = get_connection_name(command, &connection_name);
|
|
if (!command)
|
|
{
|
|
static const char MESSAGE[] = "statement doesn't have the CHANGE MASTER TO syntax";
|
|
mxb_assert(sizeof(MESSAGE) <= BINLOG_ERROR_MSG_LEN);
|
|
strcpy(error, MESSAGE);
|
|
return -1;
|
|
}
|
|
|
|
int index = validate_connection_name(router, connection_name, error);
|
|
|
|
if (index == -1)
|
|
{
|
|
// An error was already generated.
|
|
return -1;
|
|
}
|
|
|
|
std::vector<char> cmd_string(command, command + strlen(command) + 1); // Include the NULL
|
|
|
|
/* Parse SQL command and populate the change_master struct */
|
|
ChangeMasterOptions options(connection_name);
|
|
|
|
if (index < static_cast<int>(router->configs.size()))
|
|
{
|
|
// An existing configuration, pick defaults from it.
|
|
options.set_defaults(router->configs[index]);
|
|
}
|
|
else if (index != 0)
|
|
{
|
|
mxb_assert(index == static_cast<int>(router->configs.size()));
|
|
// A new configuration, pick defaults from the default configuration.
|
|
options.set_defaults(router->configs[0]);
|
|
options.host.clear();
|
|
}
|
|
|
|
string host = options.host;
|
|
options.host.clear(); // So that we can detect whether it is set, even to the same value.
|
|
|
|
if (blr_parse_change_master_command(&cmd_string.front(),
|
|
error,
|
|
&options) != 0)
|
|
{
|
|
MXS_ERROR("%s CHANGE MASTER TO parse error: %s",
|
|
router->service->name(),
|
|
error);
|
|
|
|
return -1;
|
|
}
|
|
|
|
string use_gtid = options.use_mariadb10_gtid;
|
|
|
|
if (!use_gtid.empty() && (strcasecmp(use_gtid.c_str(), "slave_pos") != 0))
|
|
{
|
|
static const char MESSAGE[] =
|
|
"Only MASTER_USE_GTID=Slave_pos is allowed.";
|
|
mxb_assert(sizeof(MESSAGE) <= BINLOG_ERROR_MSG_LEN);
|
|
strcpy(error, MESSAGE);
|
|
return -1;
|
|
}
|
|
|
|
ChangeMasterConfig config;
|
|
if (!options.validate(router, error, &config))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (config.host.empty()) // Empty, if it was not specified in the options.
|
|
{
|
|
config.host = host;
|
|
}
|
|
|
|
if ((index == 0) && !options.host.empty())
|
|
{
|
|
// If we are manipulating the default configuration and a host is specified,
|
|
// even if it would be the same, then we reset the setup.
|
|
router->configs.clear();
|
|
}
|
|
|
|
return blr_apply_change_master(router, index, config, error);
|
|
}
|
|
|
|
/*
|
|
* 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, const char* hostname)
|
|
{
|
|
if (hostname)
|
|
{
|
|
mxb_assert((*hostname != '\'') && (*hostname != '"'));
|
|
|
|
router->service->dbref->server->server_update_address(hostname);
|
|
|
|
MXS_INFO("%s: New MASTER_HOST is [%s]",
|
|
router->service->name(),
|
|
router->service->dbref->server->address);
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int blr_set_master_hostname(ROUTER_INSTANCE* router, const std::string& hostname)
|
|
{
|
|
return blr_set_master_hostname(router, hostname.empty() ? nullptr : hostname.c_str());
|
|
}
|
|
|
|
/**
|
|
* 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, int port)
|
|
{
|
|
if (port > 0)
|
|
{
|
|
router->service->dbref->server->update_port(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,
|
|
const char* filename,
|
|
char* error)
|
|
{
|
|
char* new_binlog_file = NULL;
|
|
|
|
if (filename)
|
|
{
|
|
mxb_assert((*filename != '\'') && (*filename != '"'));
|
|
|
|
long next_binlog_seqname;
|
|
char* file_ptr;
|
|
char* end;
|
|
|
|
file_ptr = (char*)filename;
|
|
|
|
/* 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,
|
|
* a 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;
|
|
}
|
|
|
|
static char* blr_set_master_logfile(ROUTER_INSTANCE* router,
|
|
const std::string& filename,
|
|
char* error)
|
|
{
|
|
return blr_set_master_logfile(router, filename.empty() ? nullptr : filename.c_str(), error);
|
|
}
|
|
|
|
/**
|
|
* 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, MasterServerConfig* curr_master)
|
|
{
|
|
curr_master->port = router->service->dbref->server->port;
|
|
curr_master->host = router->service->dbref->server->address;
|
|
curr_master->pos = router->current_pos;
|
|
curr_master->safe_pos = router->binlog_position;
|
|
curr_master->logfile = router->binlog_name;
|
|
curr_master->user = router->user;
|
|
curr_master->password = router->password;
|
|
curr_master->filestem = router->fileroot;
|
|
/* SSL options */
|
|
auto server_ssl = router->service->dbref->server->ssl().config();
|
|
|
|
if (server_ssl && !server_ssl->empty())
|
|
{
|
|
curr_master->ssl_enabled = router->ssl_enabled;
|
|
if (router->ssl_version)
|
|
{
|
|
curr_master->ssl_version = router->ssl_version;
|
|
}
|
|
if (!server_ssl->key.empty())
|
|
{
|
|
curr_master->ssl_key = server_ssl->key;
|
|
}
|
|
if (!server_ssl->cert.empty())
|
|
{
|
|
curr_master->ssl_cert = server_ssl->cert;
|
|
}
|
|
if (!server_ssl->ca.empty())
|
|
{
|
|
curr_master->ssl_ca = server_ssl->ca;
|
|
}
|
|
}
|
|
/* Connect options */
|
|
curr_master->heartbeat = router->heartbeat;
|
|
}
|
|
|
|
/**
|
|
* 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,
|
|
const MasterServerConfig& prev_master)
|
|
{
|
|
router->service->dbref->server->server_update_address(prev_master.host);
|
|
router->service->dbref->server->update_port(prev_master.port);
|
|
|
|
router->ssl_enabled = prev_master.ssl_enabled;
|
|
if (!prev_master.ssl_version.empty())
|
|
{
|
|
MXS_FREE(router->ssl_version);
|
|
router->ssl_version = MXS_STRDUP_A(prev_master.ssl_version.c_str());
|
|
}
|
|
|
|
router->heartbeat = prev_master.heartbeat;
|
|
}
|
|
|
|
/**
|
|
* Set all the master configuration fields to empty values
|
|
*
|
|
* @param router Current router instance
|
|
*/
|
|
static void blr_master_set_empty_config(ROUTER_INSTANCE* router)
|
|
{
|
|
router->service->dbref->server->server_update_address("none");
|
|
router->service->dbref->server->update_port(3306);
|
|
|
|
router->current_pos = 4;
|
|
router->binlog_position = 4;
|
|
router->current_safe_event = 4;
|
|
strcpy(router->binlog_name, "");
|
|
strcpy(router->prevbinlog, "");
|
|
/* Set Empty master id */
|
|
router->orig_masterid = 0;
|
|
/* Set Default GTID domain */
|
|
router->mariadb10_gtid_domain = BLR_DEFAULT_GTID_DOMAIN_ID;
|
|
}
|
|
|
|
/**
|
|
* 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, const MasterServerConfig& prev_master)
|
|
{
|
|
router->service->dbref->server->server_update_address(prev_master.host);
|
|
router->service->dbref->server->update_port(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.c_str());
|
|
if (router->user)
|
|
{
|
|
MXS_FREE(router->user);
|
|
router->user = MXS_STRDUP_A(prev_master.user.c_str());
|
|
}
|
|
if (router->password)
|
|
{
|
|
MXS_FREE(router->password);
|
|
router->password = MXS_STRDUP_A(prev_master.password.c_str());
|
|
}
|
|
if (router->fileroot)
|
|
{
|
|
MXS_FREE(router->fileroot);
|
|
router->fileroot = MXS_STRDUP_A(prev_master.filestem.c_str());
|
|
}
|
|
|
|
router->heartbeat = prev_master.heartbeat;
|
|
}
|
|
|
|
/**
|
|
* 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, const char* user)
|
|
{
|
|
if (user != NULL)
|
|
{
|
|
mxb_assert((*user != '\'') && (*user != '"'));
|
|
|
|
if (router->user)
|
|
{
|
|
MXS_FREE(router->user);
|
|
}
|
|
router->user = MXS_STRDUP_A(user);
|
|
|
|
MXS_INFO("%s: New MASTER_USER is [%s]",
|
|
router->service->name(),
|
|
router->user);
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int blr_set_master_user(ROUTER_INSTANCE* router, const std::string& user)
|
|
{
|
|
return blr_set_master_user(router, user.empty() ? nullptr : user.c_str());
|
|
}
|
|
|
|
/**
|
|
* 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, const char* password)
|
|
{
|
|
if (password != NULL)
|
|
{
|
|
mxb_assert((*password != '\'') && (*password != '"'));
|
|
|
|
if (router->password)
|
|
{
|
|
MXS_FREE(router->password);
|
|
}
|
|
router->password = MXS_STRDUP_A(password);
|
|
|
|
/* don't log new password */
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int blr_set_master_password(ROUTER_INSTANCE* router, const std::string& password)
|
|
{
|
|
return blr_set_master_password(router, password.empty() ? nullptr : password.c_str());
|
|
}
|
|
|
|
/**
|
|
* Get next token
|
|
*
|
|
* Works exactly like strtok_t except that a delim character which appears
|
|
* anywhere within quotes is ignored. For instance, if delim is "," then
|
|
* a string like "MASTER_USER='maxscale_repl_user',MASTER_PASSWORD='a,a'"
|
|
* will be tokenized into the following two tokens:
|
|
*
|
|
* MASTER_USER='maxscale_repl_user'
|
|
* MASTER_PASSWORD='a,a'
|
|
*
|
|
* @see strtok_r
|
|
*/
|
|
static char* get_next_token(char* str, const char* delim, char** saveptr)
|
|
{
|
|
if (str)
|
|
{
|
|
*saveptr = str;
|
|
}
|
|
|
|
if (!*saveptr)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
bool delim_found = true;
|
|
|
|
// Skip any delims in the beginning.
|
|
while (**saveptr && delim_found)
|
|
{
|
|
const char* d = delim;
|
|
|
|
while (*d)
|
|
{
|
|
if (*d == **saveptr)
|
|
{
|
|
break;
|
|
}
|
|
|
|
++d;
|
|
}
|
|
|
|
if (*d == 0)
|
|
{
|
|
delim_found = false;
|
|
}
|
|
else
|
|
{
|
|
++*saveptr;
|
|
}
|
|
}
|
|
|
|
if (!**saveptr)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
delim_found = false;
|
|
|
|
char* token = *saveptr;
|
|
char* p = *saveptr;
|
|
|
|
char quote = 0;
|
|
|
|
while (*p && !delim_found)
|
|
{
|
|
switch (*p)
|
|
{
|
|
case '\'':
|
|
case '"':
|
|
case '`':
|
|
if (!quote)
|
|
{
|
|
quote = *p;
|
|
}
|
|
else if (quote == *p)
|
|
{
|
|
quote = 0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if (!quote)
|
|
{
|
|
const char* d = delim;
|
|
while (*d && !delim_found)
|
|
{
|
|
if (*p == *d)
|
|
{
|
|
delim_found = true;
|
|
*p = 0;
|
|
}
|
|
else
|
|
{
|
|
++d;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
++p;
|
|
}
|
|
|
|
if (*p == 0)
|
|
{
|
|
*saveptr = NULL;
|
|
}
|
|
else if (delim_found)
|
|
{
|
|
*saveptr = p;
|
|
|
|
delim_found = true;
|
|
|
|
while (**saveptr && delim_found)
|
|
{
|
|
const char* d = delim;
|
|
while (*d)
|
|
{
|
|
if (**saveptr == *d)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
++d;
|
|
}
|
|
}
|
|
|
|
if (*d == 0)
|
|
{
|
|
delim_found = false;
|
|
}
|
|
else
|
|
{
|
|
++*saveptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
return token;
|
|
}
|
|
|
|
/**
|
|
* 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,
|
|
ChangeMasterOptions* config)
|
|
{
|
|
const char* sep = ",";
|
|
char* word, * brkb;
|
|
|
|
if ((word = get_next_token(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 = get_next_token(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,
|
|
ChangeMasterOptions* config)
|
|
{
|
|
/* space+TAB+= */
|
|
const char* sep = " \t=";
|
|
char* word, * brkb;
|
|
|
|
if ((word = get_next_token(input, sep, &brkb)) == NULL)
|
|
{
|
|
snprintf(error,
|
|
BINLOG_ERROR_MSG_LEN,
|
|
"error parsing %s",
|
|
brkb);
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
std::string* option_field;
|
|
|
|
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;
|
|
}
|
|
|
|
std::string value;
|
|
if (!blr_get_parsed_command_value(brkb, &value))
|
|
{
|
|
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 bool blr_get_parsed_command_value(char* input, std::string* output)
|
|
{
|
|
bool ret = false;
|
|
|
|
if (input && *input)
|
|
{
|
|
char value[strlen(input) + 1];
|
|
strcpy(value, input);
|
|
|
|
/* space+TAB+= */
|
|
const char* sep = " \t=";
|
|
char* word;
|
|
|
|
if ((word = get_next_token(NULL, sep, &input)) != NULL)
|
|
{
|
|
/* remove trailing spaces */
|
|
char* ptr = value + strlen(value) - 1;
|
|
while (ptr > value && isspace(*ptr))
|
|
{
|
|
*ptr-- = 0;
|
|
}
|
|
|
|
// Remove surrounding quotes.
|
|
char* p = strstr(value, word);
|
|
if (*p == '\'' || *p == '"')
|
|
{
|
|
char quote = *p;
|
|
++p;
|
|
int len = strlen(p);
|
|
if (len > 0 && p[len - 1] == quote)
|
|
{
|
|
p[len - 1] = 0;
|
|
}
|
|
}
|
|
|
|
*output = p;
|
|
ret = true;
|
|
}
|
|
}
|
|
|
|
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 std::string* blr_validate_change_master_option(const char* option, ChangeMasterOptions* 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 if (strcasecmp(option, "master_use_gtid") == 0)
|
|
{
|
|
return &config->use_mariadb10_gtid;
|
|
}
|
|
else if (strcasecmp(option, "master_heartbeat_period") == 0)
|
|
{
|
|
return &config->heartbeat_period;
|
|
}
|
|
else if (strcasecmp(option, "master_connect_retry") == 0)
|
|
{
|
|
return &config->connect_retry;
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send a MySQL protocol response for selected variable
|
|
*
|
|
* @param router The binlog router instance
|
|
* @param slave The connected slave server
|
|
* @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,
|
|
const char* variable,
|
|
const 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 = MYSQL_HEADER_LEN + (1 + 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 */
|
|
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, 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 connected slave server
|
|
* @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,
|
|
const char* variable,
|
|
const 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 = MYSQL_HEADER_LEN + (1 + vers_len) + (1 + var_len);
|
|
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 */
|
|
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, 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,
|
|
const 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(MYSQL_HEADER_LEN + 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 MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
|
|
}
|
|
|
|
static char* bypass_change_master(char* input)
|
|
{
|
|
// Very simple, basically ASSUMES there is a "CHANGE MASTER" at the beginning.
|
|
|
|
static const char CHANGE[] = "change";
|
|
|
|
char* p = strcasestr(input, CHANGE);
|
|
if (p)
|
|
{
|
|
input = p + sizeof(CHANGE) - 1;
|
|
|
|
static const char MASTER[] = "master";
|
|
|
|
p = strcasestr(input, MASTER);
|
|
if (p)
|
|
{
|
|
input = p + sizeof(MASTER) - 1;
|
|
}
|
|
}
|
|
|
|
return input;
|
|
}
|
|
|
|
/**
|
|
* 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,
|
|
ChangeMasterOptions* config)
|
|
{
|
|
return blr_parse_change_master_command(bypass_change_master(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,
|
|
const 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, bypass_change_master(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 connected slave server
|
|
* @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,= */
|
|
const 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 The write call result: non-zero if write was successful
|
|
*/
|
|
|
|
static int blr_slave_send_warning_message(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
const 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 MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
|
|
}
|
|
|
|
/**
|
|
* Send a MySQL SHOW WARNINGS packet with a message
|
|
* that has been stored in slave struct.
|
|
*
|
|
* If there is no warning 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 The write call result: 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)
|
|
{
|
|
const 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 = MYSQL_HEADER_LEN + (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 - MYSQL_HEADER_LEN, 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 */
|
|
}
|
|
|
|
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, 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 errors.
|
|
*/
|
|
static int blr_slave_handle_status_variables(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
char* stmt)
|
|
{
|
|
char* brkb = NULL;
|
|
char* word = NULL;
|
|
/* SPACE,TAB,= */
|
|
const 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 if (strcasecmp(word, "'slave_received_heartbeats'") == 0)
|
|
{
|
|
char hkbeats[41] = "";
|
|
snprintf(hkbeats, 40, "%d", router->stats.n_heartbeats);
|
|
return blr_slave_send_status_variable(router,
|
|
slave,
|
|
"Slave_received_heartbeats",
|
|
hkbeats,
|
|
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 connected slave server
|
|
* @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,
|
|
const char* variable,
|
|
const 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 = MYSQL_HEADER_LEN + (1 + vers_len) + (1 + var_len);
|
|
if ((pkt = gwbuf_alloc(len)) == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
ptr = GWBUF_DATA(pkt);
|
|
// Add length of data packet
|
|
encode_value(ptr, vers_len + 2 + var_len, 24);
|
|
ptr += 3;
|
|
// Sequence number in response
|
|
*ptr++ = seqno++;
|
|
// Length of result string
|
|
*ptr++ = var_len;
|
|
// Result string with var name
|
|
memcpy((char*)ptr, p, var_len);
|
|
ptr += var_len;
|
|
// Length of result string
|
|
*ptr++ = vers_len;
|
|
// Result string with var value
|
|
memcpy((char*)ptr, value, vers_len);
|
|
|
|
/* ptr += vers_len; Not required unless more data is added */
|
|
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, 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,
|
|
const 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;
|
|
const 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(MYSQL_HEADER_LEN + 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 MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
|
|
}
|
|
|
|
/**
|
|
* The heartbeat check function called
|
|
* from the housekeeper for registered slaves.
|
|
*
|
|
* @param router Current router instance
|
|
*/
|
|
|
|
static bool blr_send_slave_heartbeat(void* inst)
|
|
{
|
|
ROUTER_SLAVE* sptr = NULL;
|
|
ROUTER_INSTANCE* router = (ROUTER_INSTANCE*) inst;
|
|
time_t t_now = time(0);
|
|
|
|
pthread_mutex_lock(&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);
|
|
/* Set last event */
|
|
sptr->lastEventReceived = HEARTBEAT_EVENT;
|
|
/* Set last time */
|
|
sptr->lastReply = t_now;
|
|
}
|
|
|
|
sptr = sptr->next;
|
|
}
|
|
|
|
pthread_mutex_unlock(&router->lock);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 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 or 0 in case of failure
|
|
*/
|
|
static void send_heartbeat(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave)
|
|
{
|
|
REP_HEADER hdr;
|
|
GWBUF* h_event;
|
|
uint8_t* ptr;
|
|
int len = BINLOG_EVENT_HDR_LEN;
|
|
uint32_t chksum;
|
|
int filename_len = strlen(slave->binlog_name);
|
|
|
|
/* Add CRC32 4 bytes */
|
|
if (!slave->nocrc)
|
|
{
|
|
len += BINLOG_EVENT_CRC_SIZE;
|
|
}
|
|
|
|
/* 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
|
|
*/
|
|
h_event = gwbuf_alloc(MYSQL_HEADER_LEN + 1 + 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 Artificial flags */
|
|
hdr.flags = 0x20;
|
|
|
|
/* point just after the header */
|
|
ptr = blr_build_header(h_event, &hdr);
|
|
|
|
/* Copy binlog name */
|
|
memcpy(ptr, slave->binlog_name, filename_len);
|
|
|
|
ptr += filename_len;
|
|
|
|
/* Add the CRC32 */
|
|
if (!slave->nocrc)
|
|
{
|
|
chksum = crc32(0L, NULL, 0);
|
|
chksum = crc32(chksum,
|
|
GWBUF_DATA(h_event) + MYSQL_HEADER_LEN + 1,
|
|
hdr.event_size - BINLOG_EVENT_CRC_SIZE);
|
|
encode_value(ptr, chksum, 32);
|
|
}
|
|
|
|
/* Write the packet */
|
|
mxs::RoutingWorker* worker = static_cast<mxs::RoutingWorker*>(slave->dcb->owner);
|
|
worker->execute([slave, h_event]() {
|
|
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, h_event);
|
|
}, mxs::RoutingWorker::EXECUTE_AUTO);
|
|
}
|
|
|
|
static void blr_slave_send_heartbeat(ROUTER_INSTANCE* router, ROUTER_SLAVE* slave)
|
|
{
|
|
mxs::RoutingWorker* worker = static_cast<mxs::RoutingWorker*>(slave->dcb->owner);
|
|
worker->execute([router, slave]() {
|
|
send_heartbeat(router, slave);
|
|
}, mxs::RoutingWorker::EXECUTE_AUTO);
|
|
}
|
|
|
|
/**
|
|
* Change the replication SSL options
|
|
*
|
|
* @param router Current router instance
|
|
* @param config The current config
|
|
* @param error_message Pre-allocated string for error message is
|
|
* 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,
|
|
const ChangeMasterConfig& config,
|
|
char* error_message)
|
|
{
|
|
bool updated = 0;
|
|
|
|
if (config.ssl_enabled)
|
|
{
|
|
router->ssl_enabled = config.ssl_enabled;
|
|
}
|
|
|
|
if (router->ssl_enabled && !config.ssl_ca.empty() && !config.ssl_key.empty() && !config.ssl_cert.empty())
|
|
{
|
|
MXS_CONFIG_PARAMETER params;
|
|
params.set_from_list({
|
|
{CN_SSL, CN_REQUIRED},
|
|
{CN_SSL_KEY, config.ssl_key},
|
|
{CN_SSL_CERT, config.ssl_cert},
|
|
{CN_SSL_CA_CERT, config.ssl_ca},
|
|
{CN_SSL_CERT_VERIFY_DEPTH, "9"},
|
|
{CN_SSL_VERIFY_PEER_CERTIFICATE, "true"}
|
|
});
|
|
|
|
if (!config.ssl_version.empty())
|
|
{
|
|
mxb_assert((config.ssl_version.front() != '\'') && (config.ssl_version.front() != '"'));
|
|
params.set(CN_SSL_VERSION, config.ssl_version);
|
|
MXS_FREE(router->ssl_version);
|
|
router->ssl_version = MXS_STRDUP_A(config.ssl_version.c_str());
|
|
}
|
|
|
|
/* Update options in router fields */
|
|
mxb_assert((config.ssl_key.front() != '\'') && (config.ssl_key.front() != '"'));
|
|
MXS_FREE(router->ssl_key);
|
|
router->ssl_key = MXS_STRDUP_A(config.ssl_key.c_str());
|
|
|
|
mxb_assert((config.ssl_ca.front() != '\'') && (config.ssl_ca.front() != '"'));
|
|
MXS_FREE(router->ssl_ca);
|
|
router->ssl_ca = MXS_STRDUP_A(config.ssl_ca.c_str());
|
|
|
|
mxb_assert((config.ssl_cert.front() != '\'') && (config.ssl_cert.front() != '"'));
|
|
MXS_FREE(router->ssl_cert);
|
|
router->ssl_cert = MXS_STRDUP_A(config.ssl_cert.c_str());
|
|
|
|
std::unique_ptr<mxs::SSLContext> ssl(mxs::SSLContext::create(params));
|
|
|
|
if (ssl)
|
|
{
|
|
updated = 1;
|
|
router->service->dbref->server->ssl().set_context(std::move(ssl));
|
|
}
|
|
else
|
|
{
|
|
updated = -1;
|
|
}
|
|
}
|
|
|
|
return updated;
|
|
}
|
|
|
|
/**
|
|
* Notify a waiting slave that new events are stored in binlog file
|
|
*
|
|
* @param slave The current connected slave
|
|
* @return True if slave has been notified
|
|
*
|
|
*/
|
|
bool blr_notify_waiting_slave(ROUTER_SLAVE* slave)
|
|
{
|
|
bool ret = false;
|
|
pthread_mutex_lock(&slave->catch_lock);
|
|
if (slave->cstate & CS_WAIT_DATA)
|
|
{
|
|
ret = true;
|
|
/* Add fake event that will call the blr_slave_callback routine */
|
|
poll_fake_write_event(slave->dcb);
|
|
slave->cstate &= ~CS_WAIT_DATA;
|
|
}
|
|
pthread_mutex_unlock(&slave->catch_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Read MXS_START_ENCRYPTION_EVENT, after FDE
|
|
*
|
|
* @param router The router instance
|
|
* @param slave The connected slave server
|
|
* @param fde_end_pos The position of MXS_START_ENCRYPTION_EVENT, after FDE
|
|
* @return Non zero on success
|
|
*/
|
|
static int blr_slave_read_ste(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
uint32_t fde_end_pos)
|
|
{
|
|
REP_HEADER hdr;
|
|
GWBUF* record, * head;
|
|
uint8_t* ptr;
|
|
uint32_t chksum;
|
|
char err_msg[BINLOG_ERROR_MSG_LEN + 1];
|
|
MARIADB_GTID_INFO* f_tree = router->storage_type == BLR_BINLOG_STORAGE_TREE ?
|
|
&slave->f_info :
|
|
NULL;
|
|
|
|
err_msg[BINLOG_ERROR_MSG_LEN] = '\0';
|
|
|
|
memset(&hdr, 0, BINLOG_EVENT_HDR_LEN);
|
|
|
|
BLFILE* file;
|
|
if ((file = blr_open_binlog(router,
|
|
slave->binlog_name,
|
|
f_tree)) == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
/* Start Encryption Event is not encrypted, we pass NULL to last param */
|
|
if ((record = blr_read_binlog(router,
|
|
file,
|
|
fde_end_pos,
|
|
&hdr,
|
|
err_msg,
|
|
NULL)) == 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,
|
|
dcb_get_port(slave->dcb),
|
|
slave->serverid,
|
|
slave->binlog_name,
|
|
err_msg);
|
|
}
|
|
|
|
blr_close_binlog(router, file);
|
|
return 0;
|
|
}
|
|
|
|
blr_close_binlog(router, file);
|
|
|
|
/* check for MXS_START_ENCRYPTION_EVENT */
|
|
if (hdr.event_type == MARIADB10_START_ENCRYPTION_EVENT)
|
|
{
|
|
uint8_t* record_ptr = GWBUF_DATA(record);
|
|
void* mem = MXS_CALLOC(1, sizeof(SLAVE_ENCRYPTION_CTX));
|
|
SLAVE_ENCRYPTION_CTX* new_encryption_ctx =
|
|
static_cast<SLAVE_ENCRYPTION_CTX*>(mem);
|
|
|
|
if (!new_encryption_ctx)
|
|
{
|
|
gwbuf_free(record);
|
|
return 0;
|
|
}
|
|
record_ptr += BINLOG_EVENT_HDR_LEN;
|
|
// Set schema, 1 Byte
|
|
new_encryption_ctx->binlog_crypto_scheme = record_ptr[0];
|
|
// Set key version
|
|
memcpy(&new_encryption_ctx->binlog_key_version,
|
|
record_ptr + 1,
|
|
BLRM_KEY_VERSION_LENGTH);
|
|
// Set nonce
|
|
memcpy(new_encryption_ctx->nonce,
|
|
record_ptr + 1 + BLRM_KEY_VERSION_LENGTH,
|
|
BLRM_NONCE_LENGTH);
|
|
|
|
/* Set the pos of first encrypted event */
|
|
new_encryption_ctx->first_enc_event_pos = fde_end_pos + hdr.event_size;
|
|
|
|
pthread_mutex_lock(&slave->catch_lock);
|
|
|
|
SLAVE_ENCRYPTION_CTX* old_encryption_ctx = slave->encryption_ctx;
|
|
/* Set the new encryption ctx into slave */
|
|
slave->encryption_ctx = new_encryption_ctx;
|
|
|
|
pthread_mutex_unlock(&slave->catch_lock);
|
|
|
|
/* Free previous encryption ctx */
|
|
MXS_FREE(old_encryption_ctx);
|
|
|
|
MXS_INFO("Start Encryption event found. Binlog %s is encrypted. "
|
|
"First event at %lu",
|
|
slave->binlog_name,
|
|
(unsigned long)fde_end_pos + hdr.event_size);
|
|
/**
|
|
* Note: if the requested pos is equal to MXS_START_ENCRYPTION_EVENT pos
|
|
* the event will be skipped by blr_read_binlog() routine
|
|
*/
|
|
gwbuf_free(record);
|
|
return 1;
|
|
}
|
|
|
|
gwbuf_free(record);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Handle received SELECT statements from clients
|
|
*
|
|
* if a SELECT statement is one of suported one
|
|
* a proper reply to the connected client is done
|
|
*
|
|
* @param router Router instance
|
|
* @param slave Connected client/slave server
|
|
* @param select_stmt The SELECT statement
|
|
* @return True for handled queries, False otherwise
|
|
*/
|
|
static bool blr_handle_simple_select_stmt(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
char* select_stmt)
|
|
{
|
|
char* word;
|
|
char* brkb;
|
|
const char* sep = " \t,=";
|
|
|
|
if ((word = strtok_r(select_stmt, sep, &brkb)) == NULL)
|
|
{
|
|
MXS_ERROR("%s: Incomplete select query.", router->service->name());
|
|
return false;
|
|
}
|
|
else if (strcasecmp(word, "UNIX_TIMESTAMP()") == 0)
|
|
{
|
|
blr_slave_send_timestamp(router, slave);
|
|
return true;
|
|
}
|
|
else if (strcasecmp(word, "@master_binlog_checksum") == 0
|
|
|| strcasecmp(word, "@@global.binlog_checksum") == 0)
|
|
{
|
|
blr_slave_replay(router, slave, router->saved_master.chksum2);
|
|
return true;
|
|
}
|
|
else if (strcasecmp(word, "@@GLOBAL.GTID_MODE") == 0)
|
|
{
|
|
if (router->saved_master.gtid_mode)
|
|
{
|
|
blr_slave_replay(router, slave, router->saved_master.gtid_mode);
|
|
}
|
|
else
|
|
{
|
|
blr_slave_send_var_value(router,
|
|
slave,
|
|
"@@GLOBAL.GTID_MODE",
|
|
"OFF",
|
|
BLR_TYPE_STRING);
|
|
}
|
|
return true;
|
|
}
|
|
else if (strcasecmp(word, "1") == 0)
|
|
{
|
|
blr_slave_replay(router, slave, router->saved_master.select1);
|
|
return true;
|
|
}
|
|
else if (strcasecmp(word, "VERSION()") == 0)
|
|
{
|
|
if (router->set_master_version)
|
|
{
|
|
blr_slave_send_var_value(router,
|
|
slave,
|
|
"VERSION()",
|
|
router->set_master_version,
|
|
BLR_TYPE_STRING);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
blr_slave_replay(router, slave, router->saved_master.selectver);
|
|
return true;
|
|
}
|
|
}
|
|
else if (strcasecmp(word, "USER()") == 0)
|
|
{
|
|
/* Return user@host */
|
|
char user_host[MYSQL_USER_MAXLEN + 1 + MYSQL_HOST_MAXLEN + 1] = "";
|
|
|
|
snprintf(user_host,
|
|
sizeof(user_host),
|
|
"%s@%s",
|
|
slave->dcb->user,
|
|
slave->dcb->remote);
|
|
|
|
blr_slave_send_var_value(router,
|
|
slave,
|
|
"USER()",
|
|
user_host,
|
|
BLR_TYPE_STRING);
|
|
return true;
|
|
}
|
|
else if (strcasecmp(word, "@@version") == 0)
|
|
{
|
|
if (router->set_master_version)
|
|
{
|
|
blr_slave_send_var_value(router,
|
|
slave,
|
|
"@@version",
|
|
router->set_master_version,
|
|
BLR_TYPE_STRING);
|
|
return true;
|
|
}
|
|
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);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
else if (strcasecmp(word, "@@version_comment") == 0)
|
|
{
|
|
if (!router->saved_master.selectvercom)
|
|
/**
|
|
* This allows mysql client to get in when
|
|
* @@version_comment is not available
|
|
*/
|
|
{
|
|
blr_slave_send_ok(router, slave);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
blr_slave_replay(router,
|
|
slave,
|
|
router->saved_master.selectvercom);
|
|
return true;
|
|
}
|
|
}
|
|
else if (strcasecmp(word, "@@hostname") == 0)
|
|
{
|
|
if (router->set_master_hostname)
|
|
{
|
|
blr_slave_send_var_value(router,
|
|
slave,
|
|
"@@hostname",
|
|
router->set_master_hostname,
|
|
BLR_TYPE_STRING);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
blr_slave_replay(router,
|
|
slave,
|
|
router->saved_master.selecthostname);
|
|
return true;
|
|
}
|
|
}
|
|
else if ((strcasecmp(word, "@@server_uuid") == 0)
|
|
|| (strcasecmp(word, "@@global.server_uuid") == 0))
|
|
{
|
|
/* to ensure we match the case in query and response */
|
|
char heading[40];
|
|
strcpy(heading, word);
|
|
|
|
if (router->set_master_uuid)
|
|
{
|
|
blr_slave_send_var_value(router,
|
|
slave,
|
|
heading,
|
|
router->master_uuid,
|
|
BLR_TYPE_STRING);
|
|
return true;
|
|
}
|
|
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 true;
|
|
}
|
|
}
|
|
else if (strcasecmp(word, "@@max_allowed_packet") == 0)
|
|
{
|
|
blr_slave_replay(router, slave, router->saved_master.map);
|
|
return true;
|
|
}
|
|
else if (strcasecmp(word, "@@maxscale_version") == 0)
|
|
{
|
|
blr_slave_send_maxscale_version(router, slave);
|
|
return true;
|
|
}
|
|
else if ((strcasecmp(word, "@@server_id") == 0)
|
|
|| (strcasecmp(word, "@@global.server_id") == 0))
|
|
{
|
|
char server_id[40];
|
|
/* to ensure we match the case in query and response */
|
|
char heading[40];
|
|
|
|
sprintf(server_id, "%d", router->masterid);
|
|
strcpy(heading, word);
|
|
|
|
blr_slave_send_var_value(router,
|
|
slave,
|
|
heading,
|
|
server_id,
|
|
BLR_TYPE_INT);
|
|
return true;
|
|
}
|
|
/* Handle MariaDB 10 GTID vars */
|
|
else if ((strcasecmp(word, "@@gtid_current_pos") == 0)
|
|
|| (strcasecmp(word, "@@global.gtid_current_pos") == 0)
|
|
|| (strcasecmp(word, "@@gtid_binlog_pos") == 0)
|
|
|| (strcasecmp(word, "@@global.gtid_binlog_pos") == 0)
|
|
|| (strcasecmp(word, "@@gtid_slave_pos") == 0)
|
|
|| (strcasecmp(word, "@@global.gtid_slave_pos") == 0))
|
|
{
|
|
char heading[40];
|
|
char mariadb_gtid[GTID_MAX_LEN + 1];
|
|
mariadb_gtid[0] = 0;
|
|
strcpy(heading, word);
|
|
|
|
if (router->mariadb10_compat
|
|
&& router->mariadb10_gtid)
|
|
{
|
|
pthread_mutex_lock(&router->binlog_lock);
|
|
strcpy(mariadb_gtid, router->last_mariadb_gtid);
|
|
pthread_mutex_unlock(&router->binlog_lock);
|
|
}
|
|
|
|
/* Return empty gtid_slave_pos if master GTID registration is off */
|
|
if (!router->mariadb10_master_gtid
|
|
&& strcasestr(word, "gtid_slave_pos") != NULL)
|
|
{
|
|
mariadb_gtid[0] = 0;
|
|
}
|
|
|
|
blr_slave_send_var_value(router,
|
|
slave,
|
|
heading,
|
|
mariadb_gtid,
|
|
BLR_TYPE_INT);
|
|
return true;
|
|
}
|
|
else if (strcasecmp(word, "@@GLOBAL.gtid_domain_id") == 0)
|
|
{
|
|
/* If not mariadb10 mastergtid an error message will be returned */
|
|
if (slave->mariadb10_compat
|
|
&& router->mariadb10_gtid)
|
|
{
|
|
char heading[40];
|
|
char gtid_domain[40];
|
|
sprintf(gtid_domain,
|
|
"%" PRIu32 "",
|
|
router->mariadb10_gtid_domain);
|
|
strcpy(heading, word);
|
|
|
|
blr_slave_send_var_value(router,
|
|
slave,
|
|
heading,
|
|
gtid_domain,
|
|
BLR_TYPE_INT);
|
|
return true;
|
|
}
|
|
}
|
|
else if ((strcasecmp(word, "@@global.max_connections") == 0)
|
|
|| (strcasecmp(word, "@@max_connections") == 0))
|
|
{
|
|
char max_conns[40];
|
|
/* to ensure we match the case in query and response */
|
|
char heading[40];
|
|
|
|
sprintf(max_conns,
|
|
"%d",
|
|
!router->service->max_connections ?
|
|
BLR_DEFAULT_MAX_CONNS :
|
|
router->service->max_connections);
|
|
strcpy(heading, word);
|
|
|
|
blr_slave_send_var_value(router,
|
|
slave,
|
|
heading,
|
|
max_conns,
|
|
BLR_TYPE_INT);
|
|
}
|
|
else if ((strcasecmp(word, "@@global.read_only") == 0)
|
|
|| (strcasecmp(word, "@@read_only") == 0))
|
|
{
|
|
/* to ensure we match the case in query and response */
|
|
char heading[40];
|
|
strcpy(heading, word);
|
|
|
|
blr_slave_send_var_value(router,
|
|
slave,
|
|
heading,
|
|
"0",
|
|
BLR_TYPE_INT);
|
|
}
|
|
else if ((strcasecmp(word, "@@global.log_bin") == 0)
|
|
|| (strcasecmp(word, "@@log_bin") == 0))
|
|
{
|
|
/* to ensure we match the case in query and response */
|
|
char heading[40];
|
|
strcpy(heading, word);
|
|
|
|
blr_slave_send_var_value(router,
|
|
slave,
|
|
heading,
|
|
"1",
|
|
BLR_TYPE_INT);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Build and send a Fake Rotate event to the new client
|
|
*
|
|
* @param router The router instance
|
|
* @param slave The new connected client
|
|
* @return Non-zero if the rotate was sent
|
|
*/
|
|
|
|
static int blr_send_connect_fake_rotate(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave)
|
|
{
|
|
/* Build Fake Rotate Event */
|
|
GWBUF* r_event = blr_build_fake_rotate_event(slave,
|
|
slave->binlog_pos,
|
|
slave->binlog_name,
|
|
router->masterid);
|
|
|
|
/* Send Fake Rotate Event or return 0*/
|
|
return r_event ? MXS_SESSION_ROUTE_REPLY(slave->dcb->session, r_event) : 0;
|
|
}
|
|
|
|
/**
|
|
* Build a fake rotate event
|
|
*
|
|
* @param slave The current connected client
|
|
* @param pos The position to set in the event
|
|
* @param filename The filename to set in the event
|
|
* @param serverid The serverid to set in the event
|
|
* @return A GWBUF with the binlog event or NULL
|
|
*/
|
|
static GWBUF* blr_build_fake_rotate_event(ROUTER_SLAVE* slave,
|
|
unsigned long pos,
|
|
const char* filename,
|
|
unsigned long serverid)
|
|
{
|
|
GWBUF* r_event;
|
|
uint8_t* ptr;
|
|
int len;
|
|
int flen;
|
|
REP_HEADER hdr;
|
|
uint32_t chksum;
|
|
|
|
flen = strlen(filename);
|
|
|
|
/* Event size: header + 8 bytes pos + filename */
|
|
len = BINLOG_EVENT_HDR_LEN + 8 + flen;
|
|
|
|
/* Add CRC32 bytes if needed */
|
|
len += slave->nocrc ? 0 : BINLOG_EVENT_CRC_SIZE;
|
|
|
|
/* Allocate space for packet header, status and data */
|
|
if ((r_event = gwbuf_alloc(MYSQL_HEADER_LEN + 1 + len)) == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Add 1 byte to paylod for status indicator */
|
|
hdr.payload_len = len + 1;
|
|
|
|
/* Add sequence and increment it */
|
|
hdr.seqno = slave->seqno++;
|
|
|
|
/* Set status indicator byte to OK */
|
|
hdr.ok = 0;
|
|
|
|
/* No timestamp is required */
|
|
hdr.timestamp = 0L;
|
|
|
|
/* Rotate Event Type */
|
|
hdr.event_type = ROTATE_EVENT;
|
|
hdr.serverid = serverid;
|
|
hdr.event_size = len;
|
|
|
|
/* Next pos is not needed */
|
|
hdr.next_pos = 0;
|
|
|
|
/* Artificial Event Flag */
|
|
hdr.flags = 0x20;
|
|
|
|
/* Add replication hdr to resp */
|
|
ptr = blr_build_header(r_event, &hdr);
|
|
|
|
/* Add 8 bytes pos */
|
|
encode_value(ptr, pos, 64);
|
|
ptr += 8;
|
|
|
|
/* Add binlog filename, no trailing 0 */
|
|
memcpy(ptr, filename, flen);
|
|
ptr += flen;
|
|
|
|
/* Now add the CRC to the fake binlog rotate event */
|
|
if (!slave->nocrc)
|
|
{
|
|
/*
|
|
* First checksum of an empty buffer
|
|
* then the checksum of the event portion of the message:
|
|
* we do not include the len, seq number and ok byte that are part of
|
|
* 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(r_event) + MYSQL_HEADER_LEN + 1,
|
|
hdr.event_size - BINLOG_EVENT_CRC_SIZE);
|
|
encode_value(ptr, chksum, 32);
|
|
}
|
|
|
|
return r_event;
|
|
}
|
|
|
|
/**
|
|
* Look for a MariaDB GTID in the gtid maps database
|
|
*
|
|
* The caller specifies the position from COM_BINLOG_DUMP
|
|
* packet and if a filename is present or not in the request.
|
|
*
|
|
* Default position is 4, default file is router->binlog_file.
|
|
*
|
|
* If req_file is false then the file to read data from
|
|
* could be either router->binlog_file or the file the GTID
|
|
* belongs to.
|
|
*
|
|
* Note: rmpty GTID means send data from router->binlog_file pos 4.
|
|
*
|
|
* @param router The router instance
|
|
* @param slave The current slave server connected
|
|
* @param req_file Using binlog filename or not
|
|
* @param req_pos The requested file pos
|
|
* @return False if GTID is not found and slave
|
|
* is connectig with gtid_strict_mode=1,
|
|
* other errors.
|
|
* True otherwise.
|
|
*/
|
|
static bool blr_slave_gtid_request(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
bool req_file,
|
|
unsigned long req_pos)
|
|
{
|
|
MARIADB_GTID_INFO f_gtid = {};
|
|
uint32_t router_pos;
|
|
char router_curr_file[BINLOG_FNAMELEN + 1];
|
|
char last_gtid[GTID_MAX_LEN + 1];
|
|
|
|
memset(&f_gtid, 0, sizeof(f_gtid));
|
|
|
|
pthread_mutex_lock(&router->binlog_lock);
|
|
// Set gtid as current router gtid
|
|
strcpy(last_gtid, router->last_mariadb_gtid);
|
|
// Set file as router current file
|
|
strcpy(router_curr_file, router->binlog_name);
|
|
// Set safe postion of current ruter file
|
|
router_pos = router->binlog_position;
|
|
// Set domain_id, server_id in case of empty/not found GTID
|
|
if (router->storage_type == BLR_BINLOG_STORAGE_TREE)
|
|
{
|
|
f_gtid.gtid_elms.domain_id = router->mariadb10_gtid_domain;
|
|
f_gtid.gtid_elms.server_id = router->orig_masterid;
|
|
}
|
|
pthread_mutex_unlock(&router->binlog_lock);
|
|
|
|
MXS_INFO("Slave %lu is registering with MariaDB GTID '%s'",
|
|
(unsigned long)slave->serverid,
|
|
slave->mariadb_gtid);
|
|
|
|
if (!slave->mariadb_gtid[0])
|
|
{
|
|
/**
|
|
* Empty GTID:
|
|
* Sending data from the router current file and pos 4
|
|
*/
|
|
char t_prefix[BINLOG_FILE_EXTRA_INFO] = "";
|
|
|
|
// Add file prefix
|
|
if (router->storage_type == BLR_BINLOG_STORAGE_TREE)
|
|
{
|
|
sprintf(t_prefix,
|
|
"%" PRIu32 "/%" PRIu32 "/",
|
|
f_gtid.gtid_elms.domain_id,
|
|
f_gtid.gtid_elms.server_id);
|
|
}
|
|
|
|
strcpy(slave->binlog_name, router_curr_file);
|
|
slave->binlog_pos = 4;
|
|
|
|
MXS_INFO("Slave %d is registering with empty GTID:"
|
|
" sending events from current binlog file %s%s,"
|
|
" pos %" PRIu32 "",
|
|
slave->serverid,
|
|
t_prefix,
|
|
slave->binlog_name,
|
|
slave->binlog_pos);
|
|
|
|
/* Add GTID details to slave struct */
|
|
memcpy(&slave->f_info, &f_gtid, sizeof(MARIADB_GTID_INFO));
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
char dbpath[PATH_MAX + 1];
|
|
snprintf(dbpath,
|
|
sizeof(dbpath),
|
|
"/%s/%s",
|
|
router->binlogdir,
|
|
GTID_MAPS_DB);
|
|
|
|
/* Open GTID maps read-only database */
|
|
if (sqlite3_open_v2(dbpath,
|
|
&slave->gtid_maps,
|
|
SQLITE_OPEN_READONLY,
|
|
NULL) != SQLITE_OK)
|
|
{
|
|
char errmsg[BINLOG_ERROR_MSG_LEN + sizeof(dbpath) + 1];
|
|
snprintf(errmsg,
|
|
sizeof(errmsg),
|
|
"Slave %lu: failed to open GTID maps db '%s': %s",
|
|
(unsigned long)slave->serverid,
|
|
dbpath,
|
|
sqlite3_errmsg(slave->gtid_maps));
|
|
errmsg[BINLOG_ERROR_MSG_LEN] = '\0';
|
|
|
|
MXS_ERROR("%s", errmsg);
|
|
strcpy(slave->binlog_name, "");
|
|
slave->binlog_pos = 0;
|
|
blr_send_custom_error(slave->dcb,
|
|
slave->seqno + 1,
|
|
0,
|
|
"Cannot open GTID maps storage.",
|
|
"HY000",
|
|
BINLOG_FATAL_ERROR_READING);
|
|
|
|
slave->gtid_maps = NULL;
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
/* Fetch the GTID from the maps storage */
|
|
blr_fetch_mariadb_gtid(slave, slave->mariadb_gtid, &f_gtid);
|
|
|
|
/* Close GTID maps database */
|
|
sqlite3_close_v2(slave->gtid_maps);
|
|
slave->gtid_maps = NULL;
|
|
}
|
|
|
|
/* Requested GTID Not Found */
|
|
if (!f_gtid.gtid[0])
|
|
{
|
|
char errmsg[BINLOG_ERROR_MSG_LEN + 1];
|
|
snprintf(errmsg,
|
|
BINLOG_ERROR_MSG_LEN,
|
|
"Requested MariaDB GTID '%s' by server %lu"
|
|
" not found. GTID_STRICT_MODE=%s",
|
|
slave->mariadb_gtid,
|
|
(unsigned long)slave->serverid,
|
|
slave->gtid_strict_mode ? "ON" : "OFF");
|
|
errmsg[BINLOG_ERROR_MSG_LEN] = '\0';
|
|
|
|
/* Check strict mode */
|
|
if (slave->gtid_strict_mode)
|
|
{
|
|
MXS_ERROR("%s", errmsg);
|
|
strcpy(slave->binlog_name, "");
|
|
slave->binlog_pos = 0;
|
|
blr_send_custom_error(slave->dcb,
|
|
slave->seqno + 1,
|
|
0,
|
|
"connecting slave requested to start"
|
|
" from non existent GTID.",
|
|
"HY000",
|
|
BINLOG_FATAL_ERROR_READING);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
/* No strict mode: */
|
|
MXS_WARNING("%s", errmsg);
|
|
|
|
// - 1 -Set request GTID as current master one
|
|
MXS_FREE(slave->mariadb_gtid);
|
|
slave->mariadb_gtid = MXS_STRDUP_A(last_gtid);
|
|
// - 2 - Use current router file and position
|
|
strcpy(slave->binlog_name, router_curr_file);
|
|
slave->binlog_pos = router_pos;
|
|
|
|
// - 3 Set GTID details for filename
|
|
if (router->storage_type == BLR_BINLOG_STORAGE_TREE)
|
|
{
|
|
memcpy(&slave->f_info, &f_gtid, sizeof(MARIADB_GTID_INFO));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* GTID has been found */
|
|
MXS_INFO("Found GTID '%s' for slave %" PRIu32 ""
|
|
" at %" PRIu32 "/%" PRIu32 "/%s:%" PRIu64 ""
|
|
". Next event at %" PRIu64 "",
|
|
slave->mariadb_gtid,
|
|
slave->serverid,
|
|
f_gtid.gtid_elms.domain_id,
|
|
f_gtid.gtid_elms.server_id,
|
|
f_gtid.binlog_name,
|
|
f_gtid.start,
|
|
f_gtid.end);
|
|
|
|
/**
|
|
* Checks:
|
|
* a) GTID request has no binlog file at all:
|
|
* use GTID info file
|
|
* b) binlog file & position:
|
|
* if the requested binlog file is equal to GTID info file use it.
|
|
*/
|
|
if (!req_file
|
|
|| (strcmp(slave->binlog_name, f_gtid.binlog_name) == 0))
|
|
{
|
|
/* Set binlog file to the GTID one */
|
|
strcpy(slave->binlog_name, f_gtid.binlog_name);
|
|
|
|
/* Set pos to GTID next event pos */
|
|
slave->binlog_pos = f_gtid.end;
|
|
}
|
|
else
|
|
{
|
|
/**
|
|
* The requested binlog file is not the GTID info file.
|
|
* The binlog file could be different due to:
|
|
* a rotate event or other non GTID events written
|
|
* after that GTID.
|
|
* If file exists and pos >=4, events will be sent
|
|
* from requested file@pos, otherwise from GTID file & pos.
|
|
*/
|
|
|
|
// Add tree prefix
|
|
char t_prefix[BINLOG_FILE_EXTRA_INFO] = "";
|
|
char file_path[PATH_MAX + 1] = "";
|
|
if (router->storage_type == BLR_BINLOG_STORAGE_TREE)
|
|
{
|
|
sprintf(t_prefix,
|
|
"%" PRIu32 "/%" PRIu32 "/",
|
|
f_gtid.gtid_elms.domain_id,
|
|
f_gtid.gtid_elms.server_id);
|
|
}
|
|
|
|
// Get binlog filename full-path
|
|
blr_get_file_fullpath(slave->binlog_name,
|
|
router->binlogdir,
|
|
file_path,
|
|
t_prefix[0] ? t_prefix : NULL);
|
|
// File size is >=4 read: set pos.
|
|
if (blr_slave_get_file_size(file_path) >= 4)
|
|
{
|
|
slave->binlog_pos = req_pos;
|
|
}
|
|
else
|
|
{
|
|
/* Set binlog file to the GTID one */
|
|
strcpy(slave->binlog_name, f_gtid.binlog_name);
|
|
|
|
/* Set pos to GTID next event pos */
|
|
slave->binlog_pos = f_gtid.end;
|
|
}
|
|
}
|
|
|
|
/* Set GTID details in f_info */
|
|
memcpy(&slave->f_info, &f_gtid, sizeof(MARIADB_GTID_INFO));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Create a Fake GTID_LIST event
|
|
*
|
|
* The routine creates a Fake GTID_LIST event
|
|
*
|
|
* @param slave The connected client
|
|
* @param gtid The requested GTID from client
|
|
* @param serverid The router server_id to add
|
|
* in the replication event header
|
|
* @return Fake GTID_LIST event on success or NULL
|
|
*
|
|
*/
|
|
static GWBUF* blr_build_fake_gtid_list_event(ROUTER_SLAVE* slave,
|
|
const char* gtid,
|
|
uint32_t serverid)
|
|
{
|
|
int len;
|
|
GWBUF* gl_event;
|
|
uint8_t* ptr;
|
|
REP_HEADER hdr;
|
|
uint32_t chksum;
|
|
MARIADB_GTID_ELEMS req_gtid = {};
|
|
|
|
if (!blr_parse_gtid(gtid, &req_gtid))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* We only support one GTID in the GTID_LIST event
|
|
*
|
|
* Paylod is:
|
|
* BINLOG_EVENT_HDR_LEN + 4 bytes GTID count + 1 GTID
|
|
*/
|
|
len = BINLOG_EVENT_HDR_LEN + 4 + 1 * (4 + 4 + 8);
|
|
|
|
/* Add CRC32 bytes if needed */
|
|
len += slave->nocrc ? 0 : BINLOG_EVENT_CRC_SIZE;
|
|
|
|
/* Allocate space for packet header, status and data */
|
|
if ((gl_event = gwbuf_alloc(MYSQL_HEADER_LEN + 1 + len)) == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
/* Add 1 byte to paylod for status indicator */
|
|
hdr.payload_len = len + 1;
|
|
|
|
/* Add sequence and increment it */
|
|
hdr.seqno = slave->seqno++;
|
|
|
|
/* Set status indicator byte to OK */
|
|
hdr.ok = 0;
|
|
|
|
/* No timestamp is required */
|
|
hdr.timestamp = 0L;
|
|
|
|
/* GTID Event Type */
|
|
hdr.event_type = MARIADB10_GTID_GTID_LIST_EVENT;
|
|
hdr.serverid = serverid;
|
|
hdr.event_size = len;
|
|
|
|
/* Next pos is set */
|
|
hdr.next_pos = slave->binlog_pos;
|
|
|
|
/* Artificial Event Flag */
|
|
hdr.flags = 0x20;
|
|
|
|
/* Add replication hdr to resp */
|
|
ptr = blr_build_header(gl_event, &hdr);
|
|
|
|
/*
|
|
* Add 4 bytes count
|
|
* Note: We set only 1 GTID in GTID_LIST Event
|
|
*/
|
|
encode_value(ptr, 1, 32);
|
|
ptr += 4;
|
|
|
|
/* Add 4 bytes domain id */
|
|
encode_value(ptr, req_gtid.domain_id, 32);
|
|
ptr += 4;
|
|
|
|
/* Add 4 bytes server id*/
|
|
encode_value(ptr, req_gtid.server_id, 32);
|
|
ptr += 4;
|
|
|
|
/* Add 8 bytes sequence */
|
|
encode_value(ptr, req_gtid.seq_no, 64);
|
|
ptr += 8;
|
|
|
|
/* Now add the CRC to the fake binlog rotate event */
|
|
if (!slave->nocrc)
|
|
{
|
|
/*
|
|
* First checksum of an empty buffer
|
|
* then the checksum of the event portion of the message:
|
|
* we do not include the len, seq number and ok byte that are part of
|
|
* 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(gl_event) + MYSQL_HEADER_LEN + 1,
|
|
hdr.event_size - BINLOG_EVENT_CRC_SIZE);
|
|
encode_value(ptr, chksum, 32);
|
|
}
|
|
|
|
return gl_event;
|
|
}
|
|
|
|
/**
|
|
* Create and send a Fake GTID_LIST event
|
|
*
|
|
* @param slave Current slave server
|
|
* @param gtid The requested GTID from client
|
|
* @oaram serverid The server_id to use in replication header
|
|
* @return Non-zero if data has been sent
|
|
*/
|
|
static int blr_send_fake_gtid_list(ROUTER_SLAVE* slave,
|
|
const char* gtid,
|
|
uint32_t serverid)
|
|
{
|
|
/* Build Fake GTID_LIST Event */
|
|
GWBUF* gl_event = blr_build_fake_gtid_list_event(slave,
|
|
gtid,
|
|
serverid);
|
|
|
|
/* Send Fake GTID_LIST Event or return 0*/
|
|
return gl_event ? MXS_SESSION_ROUTE_REPLY(slave->dcb->session, gl_event) : 0;
|
|
}
|
|
|
|
/**
|
|
* Handle received Maxwell statements from clients
|
|
*
|
|
* if a Maxwell statement is suported
|
|
* a proper reply to the connected client is done
|
|
*
|
|
* @param router Router instance
|
|
* @param slave Connected client/slave server
|
|
* @param maxwell_stmt The admin command options
|
|
* @return True for handled queries, False otherwise
|
|
*/
|
|
static bool blr_handle_maxwell_stmt(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
const char* maxwell_stmt)
|
|
{
|
|
static const char mysql_connector_results_charset_query[] = "SET character_set_results = NULL";
|
|
static const char maxwell_server_id_query[] = "SELECT @@server_id as server_id";
|
|
static const char maxwell_log_bin_query[] = "SHOW VARIABLES LIKE 'log_bin'";
|
|
static const char maxwell_binlog_format_query[] = "SHOW VARIABLES LIKE 'binlog_format'";
|
|
static const char maxwell_binlog_row_image_query[] = "SHOW VARIABLES LIKE 'binlog_row_image'";
|
|
static const char maxwell_lower_case_tables_query[] = "select @@lower_case_table_names";
|
|
|
|
if (strcmp(blr_skip_leading_sql_comments(maxwell_stmt),
|
|
MYSQL_CONNECTOR_SERVER_VARS_QUERY) == 0)
|
|
{
|
|
int rc = blr_slave_replay(router,
|
|
slave,
|
|
router->saved_master.server_vars);
|
|
if (!rc)
|
|
{
|
|
MXS_ERROR("Error sending mysql-connector-j server variables");
|
|
}
|
|
return true;
|
|
}
|
|
else if (router->maxwell_compat
|
|
&& strcmp(maxwell_stmt, mysql_connector_results_charset_query) == 0)
|
|
{
|
|
blr_slave_send_ok(router, slave);
|
|
return true;
|
|
}
|
|
else if (router->maxwell_compat
|
|
&& strcmp(maxwell_stmt, MYSQL_CONNECTOR_SQL_MODE_QUERY) == 0)
|
|
{
|
|
blr_slave_send_ok(router, slave);
|
|
return true;
|
|
}
|
|
else if (strcmp(maxwell_stmt, maxwell_server_id_query) == 0)
|
|
{
|
|
char server_id[40];
|
|
sprintf(server_id, "%d", router->masterid);
|
|
blr_slave_send_var_value(router,
|
|
slave,
|
|
"server_id",
|
|
server_id,
|
|
BLR_TYPE_STRING);
|
|
return true;
|
|
}
|
|
else if (strcmp(maxwell_stmt, maxwell_log_bin_query) == 0)
|
|
{
|
|
char* log_bin = blr_extract_column(router->saved_master.binlog_vars, 1);
|
|
blr_slave_send_var_value(router,
|
|
slave,
|
|
"Value",
|
|
log_bin == NULL ? "" : log_bin,
|
|
BLR_TYPE_STRING);
|
|
MXS_FREE(log_bin);
|
|
return true;
|
|
}
|
|
else if (strcmp(maxwell_stmt, maxwell_binlog_format_query) == 0)
|
|
{
|
|
char* binlog_format = blr_extract_column(router->saved_master.binlog_vars, 2);
|
|
blr_slave_send_var_value(router,
|
|
slave,
|
|
"Value",
|
|
binlog_format == NULL ? "" : binlog_format,
|
|
BLR_TYPE_STRING);
|
|
MXS_FREE(binlog_format);
|
|
return true;
|
|
}
|
|
else if (strcmp(maxwell_stmt, maxwell_binlog_row_image_query) == 0)
|
|
{
|
|
char* binlog_row_image = blr_extract_column(router->saved_master.binlog_vars, 3);
|
|
blr_slave_send_var_value(router,
|
|
slave,
|
|
"Value",
|
|
binlog_row_image == NULL ? "" : binlog_row_image,
|
|
BLR_TYPE_STRING);
|
|
MXS_FREE(binlog_row_image);
|
|
return true;
|
|
}
|
|
else if (strcmp(maxwell_stmt, maxwell_lower_case_tables_query) == 0)
|
|
{
|
|
int rc = blr_slave_replay(router,
|
|
slave,
|
|
router->saved_master.lower_case_tables);
|
|
if (!rc)
|
|
{
|
|
MXS_ERROR("Error sending lower_case_tables query response");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Handle received SHOW statements from clients
|
|
*
|
|
* if a SHOW statement is one of suported one
|
|
* a proper reply to the connected client is done
|
|
*
|
|
* @param router Router instance
|
|
* @param slave Connected client/slave server
|
|
* @param show_stmt The SHOW statement
|
|
* @return True for handled queries, False otherwise
|
|
*/
|
|
static bool blr_handle_show_stmt(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
char* show_stmt)
|
|
{
|
|
char* word;
|
|
char* brkb;
|
|
const char* sep = " \t,=";
|
|
if ((word = strtok_r(show_stmt, sep, &brkb)) == NULL)
|
|
{
|
|
MXS_ERROR("%s: Incomplete show query.", router->service->name());
|
|
return false;
|
|
}
|
|
else if (strcasecmp(word, "WARNINGS") == 0)
|
|
{
|
|
blr_slave_show_warnings(router, slave);
|
|
return true;
|
|
}
|
|
else if (strcasecmp(word, "BINARY") == 0)
|
|
{
|
|
if (router->mariadb10_gtid)
|
|
{
|
|
blr_show_binary_logs(router, slave, word);
|
|
}
|
|
else
|
|
{
|
|
const char* errmsg =
|
|
"SHOW [FULL] BINARY LOGS needs the"
|
|
" 'mariadb10_slave_gtid' option to be set.";
|
|
MXS_ERROR("%s: %s",
|
|
errmsg,
|
|
router->service->name());
|
|
|
|
blr_slave_send_error_packet(slave,
|
|
errmsg,
|
|
1198,
|
|
NULL);
|
|
}
|
|
return true;
|
|
}
|
|
else if (strcasecmp(word, "GLOBAL") == 0)
|
|
{
|
|
if (router->master_state == BLRM_UNCONFIGURED)
|
|
{
|
|
blr_slave_send_ok(router, slave);
|
|
return true;
|
|
}
|
|
|
|
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
|
|
{
|
|
MXS_ERROR("%s: Expected VARIABLES in SHOW GLOBAL",
|
|
router->service->name());
|
|
return false;
|
|
}
|
|
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)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
MXS_ERROR("%s: Expected LIKE clause in SHOW GLOBAL VARIABLES.",
|
|
router->service->name());
|
|
return false;
|
|
}
|
|
}
|
|
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)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
MXS_ERROR("%s: Expected LIKE clause in SHOW GLOBAL STATUS.",
|
|
router->service->name());
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else if (strcasecmp(word, "VARIABLES") == 0)
|
|
{
|
|
int rc;
|
|
if (router->master_state == BLRM_UNCONFIGURED)
|
|
{
|
|
blr_slave_send_ok(router, slave);
|
|
return true;
|
|
}
|
|
|
|
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)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
MXS_ERROR("%s: Expected LIKE clause in SHOW VARIABLES.",
|
|
router->service->name());
|
|
return false;
|
|
}
|
|
}
|
|
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());
|
|
return false;
|
|
}
|
|
else if (strcasecmp(word, "STATUS") == 0)
|
|
{
|
|
/* if state is BLRM_UNCONFIGURED return empty result */
|
|
|
|
if (router->master_state > BLRM_UNCONFIGURED)
|
|
{
|
|
blr_slave_send_master_status(router, slave);
|
|
}
|
|
else
|
|
{
|
|
blr_slave_send_ok(router, slave);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
/* Added support for SHOW ALL SLAVES STATUS */
|
|
else if (strcasecmp(word, "SLAVE") == 0
|
|
|| (strcasecmp(word, "ALL") == 0))
|
|
{
|
|
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
|
|
{
|
|
MXS_ERROR("%s: Expected SHOW SLAVE STATUS command",
|
|
router->service->name());
|
|
return false;
|
|
}
|
|
else if (strcasecmp(word, "STATUS") == 0
|
|
|| (strcasecmp(word, "SLAVES") == 0
|
|
&& strcasecmp(brkb, "STATUS") == 0))
|
|
{
|
|
/* if state is BLRM_UNCONFIGURED return empty result */
|
|
if (router->master_state > BLRM_UNCONFIGURED)
|
|
{
|
|
bool s_all = strcasecmp(word, "SLAVES") == 0 ? true : false;
|
|
blr_slave_send_slave_status(router, slave, s_all);
|
|
}
|
|
else
|
|
{
|
|
blr_slave_send_ok(router, slave);
|
|
}
|
|
return true;
|
|
}
|
|
else if (strcasecmp(word, "HOSTS") == 0)
|
|
{
|
|
/* if state is BLRM_UNCONFIGURED return empty result */
|
|
if (router->master_state > BLRM_UNCONFIGURED)
|
|
{
|
|
blr_slave_send_slave_hosts(router, slave);
|
|
}
|
|
else
|
|
{
|
|
blr_slave_send_ok(router, slave);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
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)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
MXS_ERROR("%s: Expected LIKE clause in SHOW STATUS.",
|
|
router->service->name());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Handle received SET statements from clients
|
|
*
|
|
* if a SHOW statement is one of suported one
|
|
* a proper reply to the connected client is done
|
|
*
|
|
* @param router Router instance
|
|
* @param slave Connected client/slave server
|
|
* @param set_stmt The SET statement
|
|
* @return True for handled queries, False otherwise
|
|
*/
|
|
static bool blr_handle_set_stmt(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
char* set_stmt)
|
|
{
|
|
char* word;
|
|
char* brkb;
|
|
const char* sep = " \t,=";
|
|
|
|
if ((word = strtok_r(set_stmt, sep, &brkb)) == NULL)
|
|
{
|
|
MXS_ERROR("%s: Incomplete set command.", router->service->name());
|
|
return false;
|
|
}
|
|
else if ((strcasecmp(word, "autocommit") == 0)
|
|
|| (strcasecmp(word, "@@session.autocommit") == 0))
|
|
{
|
|
blr_slave_send_ok(router, slave);
|
|
return true;
|
|
}
|
|
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 = mxb_strndup_a(word, v_len - 6);
|
|
slave->heartbeat = atoi(new_val) / 1000;
|
|
}
|
|
else
|
|
{
|
|
new_val = mxb_strndup_a(word, v_len);
|
|
slave->heartbeat = atoi(new_val) / 1000000;
|
|
}
|
|
|
|
MXS_FREE(new_val);
|
|
}
|
|
blr_slave_replay(router, slave, router->saved_master.heartbeat);
|
|
return true;
|
|
}
|
|
else if (strcasecmp(word, "@mariadb_slave_capability") == 0)
|
|
{
|
|
/* mariadb10 compatibility is set for the slave */
|
|
slave->mariadb10_compat = true;
|
|
|
|
if (router->mariadb10_compat)
|
|
{
|
|
blr_slave_replay(router, slave, router->saved_master.mariadb10);
|
|
}
|
|
else
|
|
{
|
|
blr_slave_send_ok(router, slave);
|
|
}
|
|
return true;
|
|
}
|
|
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;
|
|
}
|
|
|
|
blr_slave_replay(router, slave, router->saved_master.chksum1);
|
|
return true;
|
|
}
|
|
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++;
|
|
}
|
|
}
|
|
/* Free previous value */
|
|
MXS_FREE(slave->uuid);
|
|
slave->uuid = MXS_STRDUP_A(word_ptr);
|
|
}
|
|
|
|
if (router->saved_master.setslaveuuid)
|
|
{
|
|
blr_slave_replay(router, slave, router->saved_master.setslaveuuid);
|
|
}
|
|
else
|
|
{
|
|
blr_slave_send_ok(router, slave);
|
|
}
|
|
return true;
|
|
}
|
|
else if (strcasecmp(word, "@@global.gtid_slave_pos") == 0)
|
|
{
|
|
if (slave->serverid != 0)
|
|
{
|
|
MXS_ERROR("Master GTID registration can be sent only"
|
|
" via administration connection");
|
|
blr_slave_send_error_packet(slave,
|
|
"Master GTID registration cannot be"
|
|
" issued by a registrating slave.",
|
|
1198,
|
|
NULL);
|
|
return false;
|
|
}
|
|
if (router->master_state != BLRM_SLAVE_STOPPED
|
|
&& router->master_state != BLRM_UNCONFIGURED)
|
|
{
|
|
const char* err_msg_u = "configured replication: Issue CHANGE MASTER TO first.";
|
|
const char* err_msg_s = "stopped replication: issue STOP SLAVE first.";
|
|
char error_string[BINLOG_ERROR_MSG_LEN + 1] = "";
|
|
MXS_ERROR("GTID registration without %s",
|
|
router->master_state == BLRM_SLAVE_STOPPED ?
|
|
err_msg_s : err_msg_u);
|
|
|
|
snprintf(error_string,
|
|
BINLOG_ERROR_MSG_LEN,
|
|
"Cannot use Master GTID registration without %s",
|
|
router->master_state == BLRM_SLAVE_STOPPED ?
|
|
err_msg_s :
|
|
err_msg_u);
|
|
|
|
blr_slave_send_error_packet(slave,
|
|
error_string,
|
|
1198,
|
|
NULL);
|
|
return true;
|
|
}
|
|
/* If not mariadb GTID an error message will be returned */
|
|
if (router->mariadb10_master_gtid)
|
|
{
|
|
if ((word = strtok_r(NULL, sep, &brkb)) != NULL)
|
|
{
|
|
char heading[GTID_MAX_LEN + 1];
|
|
MARIADB_GTID_ELEMS gtid_elms = {};
|
|
|
|
// TODO: gtid_strip_chars routine for this
|
|
strcpy(heading, word + 1);
|
|
heading[strlen(heading) - 1] = '\0';
|
|
|
|
MXS_INFO("Requesting GTID (%s) from Master server.",
|
|
!heading[0] ? "empty value" : heading);
|
|
|
|
/* Parse the non empty GTID value */
|
|
if (heading[0] && !blr_parse_gtid(heading, >id_elms))
|
|
{
|
|
const char err_fmt[] = "Invalid format for GTID ('%s')"
|
|
" set request; use 'X-Y-Z'";
|
|
char err_msg[sizeof(err_fmt) + GTID_MAX_LEN + 1];
|
|
sprintf(err_msg, err_fmt, heading);
|
|
|
|
MXS_ERROR("%s", err_msg);
|
|
|
|
/* Stop Master registration */
|
|
blr_slave_send_error_packet(slave,
|
|
err_msg,
|
|
1198,
|
|
NULL);
|
|
}
|
|
else
|
|
{
|
|
strcpy(router->last_mariadb_gtid, heading);
|
|
blr_slave_send_ok(router, slave);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MXS_ERROR("Master GTID registration needs 'mariadb10_master_gtid'"
|
|
" option to be set.");
|
|
blr_slave_send_error_packet(slave,
|
|
"Master GTID registration needs"
|
|
" 'mariadb10_master_gtid' option"
|
|
" to be set first.",
|
|
1198,
|
|
NULL);
|
|
return true;
|
|
}
|
|
}
|
|
else if (strcasestr(word, "@slave_connect_state") != NULL)
|
|
{
|
|
/* If not mariadb an error message will be returned */
|
|
if (slave->mariadb10_compat
|
|
&& router->mariadb10_gtid
|
|
&& (word = strtok_r(NULL, sep, &brkb)) != NULL)
|
|
{
|
|
char heading[GTID_MAX_LEN + 1];
|
|
|
|
MXS_DEBUG("Received GTID request '%s' from slave %u",
|
|
word,
|
|
slave->serverid);
|
|
|
|
strcpy(heading, word + 1);
|
|
heading[strlen(heading) - 1] = '\0';
|
|
|
|
/**
|
|
* Set the GTID string, it could be an empty
|
|
* in case of a fresh new setup.
|
|
*/
|
|
MXS_FREE(slave->mariadb_gtid);
|
|
slave->mariadb_gtid = MXS_STRDUP_A(heading);
|
|
|
|
blr_slave_send_ok(router, slave);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
MXS_ERROR("GTID Master registration is not enabled");
|
|
return false;
|
|
}
|
|
}
|
|
else if (strcasecmp(word, "@slave_gtid_strict_mode") == 0)
|
|
{
|
|
/* If not mariadb an error message will be returned */
|
|
if (slave->mariadb10_compat
|
|
&& router->mariadb10_gtid
|
|
&& (word = strtok_r(NULL, sep, &brkb)) != NULL)
|
|
{
|
|
/* Set strict mode */
|
|
slave->gtid_strict_mode = atoi(word);
|
|
blr_slave_send_ok(router, slave);
|
|
return true;
|
|
}
|
|
}
|
|
else if (strcasecmp(word, "@slave_gtid_ignore_duplicates") == 0)
|
|
{
|
|
/* If not mariadb an error message will be returned */
|
|
if (slave->mariadb10_compat
|
|
&& router->mariadb10_gtid)
|
|
{
|
|
blr_slave_send_ok(router, slave);
|
|
return true;
|
|
}
|
|
}
|
|
else if (strcasecmp(word, "NAMES") == 0)
|
|
{
|
|
if ((word = strtok_r(NULL, sep, &brkb)) == NULL)
|
|
{
|
|
MXS_ERROR("%s: Truncated SET NAMES command.",
|
|
router->service->name());
|
|
return false;
|
|
}
|
|
else if (strcasecmp(word, "latin1") == 0)
|
|
{
|
|
blr_slave_replay(router, slave, router->saved_master.setnames);
|
|
return true;
|
|
}
|
|
else if (strcasecmp(word, "utf8") == 0)
|
|
{
|
|
blr_slave_replay(router, slave, router->saved_master.utf8);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
blr_slave_send_ok(router, slave);
|
|
return true;
|
|
}
|
|
}
|
|
else if (strcasecmp(word, "SQL_MODE") == 0)
|
|
{
|
|
blr_slave_send_ok(router, slave);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Handle received admin statements from clients
|
|
*
|
|
* if an admin statement is one of suported one
|
|
* a proper reply to the connected client is done
|
|
*
|
|
* @param router Router instance
|
|
* @param slave Connected client/slave server
|
|
* @param admin_stmt The admin statement
|
|
* @param admin_opts The admin command options
|
|
* @return True for handled queries, False otherwise
|
|
*/
|
|
static bool blr_handle_admin_stmt(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
char* admin_stmt,
|
|
char* admin_opts)
|
|
{
|
|
char* word;
|
|
char* brkb;
|
|
const char* sep = " \t,=";
|
|
|
|
if (admin_opts == NULL || !admin_opts[0])
|
|
{
|
|
MXS_ERROR("%s: Incomplete admin command.", router->service->name());
|
|
return false;
|
|
}
|
|
/* Handle PURGE command */
|
|
else if (strcasecmp(admin_stmt, "PURGE") == 0)
|
|
{
|
|
if (router->master_state != BLRM_SLAVE_STOPPED)
|
|
{
|
|
blr_slave_send_error_packet(slave,
|
|
"Cannot execute PURGE BINARY LOGS "
|
|
"with a running slave; "
|
|
"run STOP SLAVE first.",
|
|
1198,
|
|
NULL);
|
|
return true;
|
|
}
|
|
|
|
/* Check for GTID support */
|
|
if (router->mariadb10_gtid)
|
|
{
|
|
blr_purge_binary_logs(router, slave, admin_opts);
|
|
}
|
|
else
|
|
{
|
|
const char* errmsg =
|
|
"PURGE BINARY LOGS needs the "
|
|
"'mariadb10_slave_gtid' option to be set.";
|
|
MXS_ERROR("%s: %s",
|
|
errmsg,
|
|
router->service->name());
|
|
|
|
blr_slave_send_error_packet(slave,
|
|
errmsg,
|
|
1198,
|
|
NULL);
|
|
}
|
|
return true;
|
|
}
|
|
/* Handle RESET command */
|
|
else if (strcasecmp(admin_stmt, "RESET") == 0)
|
|
{
|
|
if ((word = strtok_r(admin_opts, sep, &brkb)) == NULL)
|
|
{
|
|
MXS_ERROR("%s: Incomplete RESET command.", router->service->name());
|
|
return false;
|
|
}
|
|
/* RESET the current configured master cfg */
|
|
else if (strcasecmp(word, "SLAVE") == 0)
|
|
{
|
|
if (router->master_state == BLRM_SLAVE_STOPPED)
|
|
{
|
|
char error_string[BINLOG_ERROR_MSG_LEN + 1] = "";
|
|
MasterServerConfig current_master;
|
|
int removed_cfg = 0;
|
|
|
|
/* get current data */
|
|
blr_master_get_config(router, ¤t_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.c_str(),
|
|
current_master.port,
|
|
current_master.logfile.c_str(),
|
|
current_master.pos,
|
|
current_master.user.c_str());
|
|
|
|
/* 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)
|
|
{
|
|
snprintf(error_string,
|
|
BINLOG_ERROR_MSG_LEN,
|
|
"Error removing %s, %s, errno %u",
|
|
path,
|
|
mxs_strerror(errno),
|
|
errno);
|
|
MXS_ERROR("%s: %s", router->service->name(), error_string);
|
|
}
|
|
|
|
pthread_mutex_lock(&router->lock);
|
|
|
|
/* Set the BLRM_UNCONFIGURED state */
|
|
router->master_state = BLRM_UNCONFIGURED;
|
|
blr_master_set_empty_config(router);
|
|
|
|
/* Remove any error message and errno */
|
|
free(router->m_errmsg);
|
|
router->m_errmsg = NULL;
|
|
router->m_errno = 0;
|
|
|
|
pthread_mutex_unlock(&router->lock);
|
|
|
|
if (removed_cfg == -1)
|
|
{
|
|
blr_slave_send_error_packet(slave,
|
|
error_string,
|
|
1201,
|
|
NULL);
|
|
}
|
|
else
|
|
{
|
|
blr_slave_send_ok(router, slave);
|
|
}
|
|
return true;
|
|
}
|
|
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",
|
|
1198,
|
|
NULL);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
/* Start replication from the current configured master */
|
|
else if (strcasecmp(admin_stmt, "START") == 0)
|
|
{
|
|
if ((word = strtok_r(admin_opts, sep, &brkb)) == NULL)
|
|
{
|
|
MXS_ERROR("%s: Incomplete START command.",
|
|
router->service->name());
|
|
return false;
|
|
}
|
|
else if (strcasecmp(word, "SLAVE") == 0)
|
|
{
|
|
blr_start_slave(router, slave);
|
|
return true;
|
|
}
|
|
}
|
|
/* Stop replication from the current master*/
|
|
else if (strcasecmp(admin_stmt, "STOP") == 0)
|
|
{
|
|
if ((word = strtok_r(admin_opts, sep, &brkb)) == NULL)
|
|
{
|
|
MXS_ERROR("%s: Incomplete STOP command.", router->service->name());
|
|
return false;
|
|
}
|
|
else if (strcasecmp(word, "SLAVE") == 0)
|
|
{
|
|
blr_stop_slave(router, slave);
|
|
return true;
|
|
}
|
|
}
|
|
/* Change the server to replicate from */
|
|
else if (strcasecmp(admin_stmt, "CHANGE") == 0)
|
|
{
|
|
if ((word = strtok_r(admin_opts, sep, &brkb)) == NULL)
|
|
{
|
|
MXS_ERROR("%s: Incomplete CHANGE command.", router->service->name());
|
|
return false;
|
|
}
|
|
else if (strcasecmp(word, "MASTER") == 0)
|
|
{
|
|
if (router->master_state != BLRM_SLAVE_STOPPED
|
|
&& router->master_state != BLRM_UNCONFIGURED)
|
|
{
|
|
blr_slave_send_error_packet(slave,
|
|
"Cannot change master with a running slave; "
|
|
"run STOP SLAVE first",
|
|
(unsigned int)1198,
|
|
NULL);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
int rc;
|
|
char error_string[BINLOG_ERROR_MSG_LEN + 1 + BINLOG_ERROR_MSG_LEN + 1] = "";
|
|
// TODO: Why is this without a lock, but blr_master_apply_config() below with
|
|
// TODO: a lock. One of them must be wrong.
|
|
MasterServerConfig current_master;
|
|
blr_master_get_config(router, ¤t_master);
|
|
vector<ChangeMasterConfig> configs = router->configs;
|
|
|
|
rc = blr_handle_change_master(router, brkb, error_string);
|
|
|
|
if (rc < 0)
|
|
{
|
|
/* CHANGE MASTER TO has failed */
|
|
blr_slave_send_error_packet(slave,
|
|
error_string,
|
|
1234,
|
|
"42000");
|
|
|
|
return true;
|
|
}
|
|
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 != 0)
|
|
{
|
|
/* file operation failure: restore config */
|
|
pthread_mutex_lock(&router->lock);
|
|
|
|
blr_master_apply_config(router, current_master);
|
|
|
|
pthread_mutex_unlock(&router->lock);
|
|
|
|
snprintf(error_string,
|
|
sizeof(error_string),
|
|
"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,
|
|
1201,
|
|
NULL);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Mark as active the master server struct */
|
|
pthread_mutex_lock(&router->lock);
|
|
if (!router->service->dbref->server->is_active)
|
|
{
|
|
router->service->dbref->server->is_active = true;
|
|
router->service->dbref->active = true;
|
|
}
|
|
pthread_mutex_unlock(&router->lock);
|
|
|
|
/**
|
|
* check if router is BLRM_UNCONFIGURED
|
|
* and change state to BLRM_SLAVE_STOPPED
|
|
*/
|
|
if (rc == 1 || router->master_state == BLRM_UNCONFIGURED)
|
|
{
|
|
pthread_mutex_lock(&router->lock);
|
|
|
|
router->master_state = BLRM_SLAVE_STOPPED;
|
|
|
|
pthread_mutex_unlock(&router->lock);
|
|
|
|
/*
|
|
* The binlog server has just been configured
|
|
* master.ini file written in router->binlogdir.
|
|
*
|
|
* Create the binlog_name specified in MASTER_LOG_FILE
|
|
* only if MariaDB GTID 'mariadb10_master_gtid' is Off
|
|
*/
|
|
|
|
if (!router->mariadb10_master_gtid
|
|
&& blr_file_new_binlog(router, router->binlog_name))
|
|
{
|
|
MXS_INFO("%s: 'master.ini' created, binlog file '%s' created",
|
|
router->service->name(),
|
|
router->binlog_name);
|
|
}
|
|
blr_slave_send_ok(router, slave);
|
|
return true;
|
|
}
|
|
|
|
if (router->trx_safe
|
|
&& router->pending_transaction.state > BLRM_NO_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.c_str(),
|
|
current_master.safe_pos,
|
|
current_master.pos);
|
|
|
|
blr_slave_send_warning_message(router, slave, message);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The CHAMGE MASTER command might specify a new binlog file.
|
|
* Let's create the binlog_name specified in MASTER_LOG_FILE
|
|
* only if MariaDB GTID 'mariadb10_master_gtid' is Off
|
|
*/
|
|
|
|
if (!router->mariadb10_master_gtid
|
|
&& (strlen(router->prevbinlog)
|
|
&& strcmp(router->prevbinlog, router->binlog_name) != 0))
|
|
{
|
|
if (blr_file_new_binlog(router, router->binlog_name))
|
|
{
|
|
MXS_INFO("%s: created new binlog file '%s' by "
|
|
"'CHANGE MASTER TO' command",
|
|
router->service->name(),
|
|
router->binlog_name);
|
|
}
|
|
}
|
|
blr_slave_send_ok(router, slave);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* Discnnect conneted client(s) */
|
|
else if (strcasecmp(admin_stmt, "DISCONNECT") == 0)
|
|
{
|
|
if ((word = strtok_r(admin_opts, sep, &brkb)) == NULL)
|
|
{
|
|
MXS_ERROR("%s: Incomplete DISCONNECT command.",
|
|
router->service->name());
|
|
return false;
|
|
}
|
|
else if (strcasecmp(word, "ALL") == 0)
|
|
{
|
|
blr_slave_disconnect_all(router, slave);
|
|
return true;
|
|
}
|
|
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());
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
int serverid = atoi(word);
|
|
blr_slave_disconnect_server(router, slave, serverid);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Skip reading empty binlog files (4 bytes only)
|
|
*
|
|
* @param router Current router instance
|
|
* @param slave Current connected slave
|
|
*/
|
|
static void blr_slave_skip_empty_files(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave)
|
|
{
|
|
char binlog_file[BINLOG_FNAMELEN + 1];
|
|
char router_curr_file[BINLOG_FNAMELEN + 1];
|
|
char file_path[PATH_MAX + 1] = "";
|
|
unsigned int seqno;
|
|
bool skipped_files = false;
|
|
char t_prefix[BINLOG_FILE_EXTRA_INFO] = "";
|
|
MARIADB_GTID_INFO* f_tree = router->storage_type == BLR_BINLOG_STORAGE_TREE ?
|
|
&slave->f_info :
|
|
NULL;
|
|
char next_file[BINLOG_FNAMELEN + 1] = "";
|
|
|
|
// Save the current router binlog filename
|
|
pthread_mutex_lock(&router->binlog_lock);
|
|
strcpy(router_curr_file, router->binlog_name);
|
|
pthread_mutex_unlock(&router->binlog_lock);
|
|
|
|
// Set the starting filename
|
|
strcpy(binlog_file, slave->binlog_name);
|
|
|
|
// Add tree prefix
|
|
if (f_tree)
|
|
{
|
|
sprintf(t_prefix,
|
|
"%" PRIu32 "/%" PRIu32 "/",
|
|
f_tree->gtid_elms.domain_id,
|
|
f_tree->gtid_elms.server_id);
|
|
}
|
|
|
|
// Get binlog filename full-path
|
|
blr_get_file_fullpath(binlog_file,
|
|
router->binlogdir,
|
|
file_path,
|
|
t_prefix[0] ? t_prefix : NULL);
|
|
|
|
/**
|
|
* Get the next file in sequence or next by GTID maps
|
|
* if current file has 4 bytes size or it doesn't exist at all.
|
|
* Stop if the new file is the current binlog file.
|
|
*/
|
|
while (!blr_compare_binlogs(router,
|
|
&f_tree->gtid_elms,
|
|
router_curr_file,
|
|
binlog_file)
|
|
&& blr_slave_get_file_size(file_path) <= 4
|
|
&& blr_file_next_exists(router, slave, next_file))
|
|
{
|
|
// Log skipped file
|
|
MXS_INFO("Slave %s:%i, skip reading empty file '%s' "
|
|
"(0 or 4 bytes size).",
|
|
slave->dcb->remote,
|
|
dcb_get_port(slave->dcb),
|
|
binlog_file);
|
|
|
|
// Update binlog_file name
|
|
strcpy(binlog_file, next_file);
|
|
|
|
// Get binlog file full-path
|
|
blr_get_file_fullpath(binlog_file,
|
|
router->binlogdir,
|
|
file_path,
|
|
t_prefix[0] ? t_prefix : NULL);
|
|
|
|
skipped_files = true;
|
|
}
|
|
|
|
// One or more files skipped: set last found filename and pos = 4
|
|
if (skipped_files)
|
|
{
|
|
strcpy(slave->binlog_name, binlog_file);
|
|
slave->binlog_pos = 4;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the full path of a binlog filename.
|
|
*
|
|
* @param binlog_file The binlog filename
|
|
* @param root_dir The binlog storage directory
|
|
* @param full_path The output fullpahth name:
|
|
* the memory area must be preallocated.
|
|
* @param t_prefix The file_tree prefix with rep_domain
|
|
* and server_id.
|
|
*/
|
|
static inline void blr_get_file_fullpath(const char* binlog_file,
|
|
const char* root_dir,
|
|
char* full_path,
|
|
const char* t_prefix)
|
|
{
|
|
strcpy(full_path, root_dir);
|
|
strcat(full_path, "/");
|
|
if (t_prefix)
|
|
{
|
|
strcat(full_path, t_prefix);
|
|
}
|
|
strcat(full_path, binlog_file);
|
|
}
|
|
|
|
/**
|
|
* Returns the list of binlog files
|
|
* saved in GTID repo.
|
|
*
|
|
* It's called olny if mariadb10_slave_gtid option is set
|
|
*
|
|
* @param router The router instance
|
|
* @param slave The connected client
|
|
* @param extra_data Whether to dispay path file
|
|
* info before filename
|
|
* @retun Sent bytes
|
|
*/
|
|
static int blr_show_binary_logs(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
const char* extra_data)
|
|
{
|
|
char current_file[BINLOG_FNAMELEN];
|
|
uint64_t current_pos = 0;
|
|
static const char select_query[] = "SELECT binlog_file, "
|
|
"MAX(end_pos) AS size, "
|
|
"rep_domain, "
|
|
"server_id "
|
|
"FROM gtid_maps "
|
|
"GROUP BY binlog_file "
|
|
"ORDER BY id ASC;";
|
|
static const char select_query_full[] = "SELECT binlog_file, "
|
|
"MAX(end_pos) AS size, "
|
|
"rep_domain, "
|
|
"server_id "
|
|
"FROM gtid_maps "
|
|
"GROUP BY rep_domain, "
|
|
"server_id, "
|
|
"binlog_file "
|
|
"ORDER BY id ASC;";
|
|
int seqno;
|
|
char* errmsg = NULL;
|
|
BINARY_LOG_DATA_RESULT result = {};
|
|
|
|
/* Get current binlog finename and position */
|
|
pthread_mutex_lock(&router->binlog_lock);
|
|
|
|
strcpy(current_file, router->binlog_name);
|
|
current_pos = router->current_pos;
|
|
|
|
pthread_mutex_unlock(&router->binlog_lock);
|
|
|
|
/**
|
|
* First part of result set:
|
|
* send 2 columns and their defintions.
|
|
*/
|
|
|
|
/* This call sets seq to 1 in the packet */
|
|
blr_slave_send_fieldcount(router, slave, 2);
|
|
/* Set 'seqno' counter to next value: 2 */
|
|
seqno = 2;
|
|
/* Col 1 def */
|
|
blr_slave_send_columndef(router,
|
|
slave,
|
|
"Log_name",
|
|
BLR_TYPE_STRING,
|
|
40,
|
|
seqno++);
|
|
/* Col 2 def */
|
|
blr_slave_send_columndef(router,
|
|
slave,
|
|
"File_size",
|
|
BLR_TYPE_INT,
|
|
40,
|
|
seqno++);
|
|
/* Cols EOF */
|
|
blr_slave_send_eof(router, slave, seqno);
|
|
/* Increment sequence */
|
|
seqno++;
|
|
|
|
/* Initialise the result data struct */
|
|
result.seq_no = seqno;
|
|
result.client = slave->dcb;
|
|
result.last_file = NULL;
|
|
result.binlogdir = router->binlogdir;
|
|
result.use_tree = router->storage_type == BLR_BINLOG_STORAGE_TREE;
|
|
|
|
/**
|
|
* Second part of result set:
|
|
*
|
|
* add rows for select binlog files.
|
|
*
|
|
* Note:
|
|
* - result.last_file is freed and updated by binary_logs_select_cb()
|
|
* - result.seq_no is increased
|
|
*/
|
|
|
|
if (sqlite3_exec(router->gtid_maps,
|
|
!result.use_tree ?
|
|
select_query :
|
|
select_query_full,
|
|
binary_logs_select_cb,
|
|
&result,
|
|
&errmsg) != SQLITE_OK)
|
|
{
|
|
MXS_ERROR("Failed to exec 'SELECT binlog_file FROM gtid_maps': "
|
|
"%s",
|
|
errmsg ? errmsg : "database is not available");
|
|
sqlite3_free(errmsg);
|
|
|
|
/* Free last_file */
|
|
MXS_FREE(result.last_file);
|
|
result.last_file = NULL;
|
|
|
|
/* Add EOF for empty result set */
|
|
return blr_slave_send_eof(router, slave, result.seq_no);
|
|
}
|
|
|
|
/* Use seqno of last sent packet */
|
|
seqno = result.seq_no;
|
|
|
|
/**
|
|
* Check whether the last file is the current binlog file,
|
|
* GTID repo might also contain no data at all.
|
|
*
|
|
* Add the new row if needed.
|
|
*/
|
|
if (!result.last_file || strcmp(current_file, result.last_file) != 0)
|
|
{
|
|
char pos[40]; // Buffer for a 64-bit integer.
|
|
GWBUF* pkt;
|
|
/* Free last file */
|
|
MXS_FREE(result.last_file);
|
|
/* Create the string value for pos */
|
|
sprintf(pos, "%" PRIu64, current_pos);
|
|
|
|
char* filename;
|
|
char last_filename[BINLOG_FILE_EXTRA_INFO + strlen(current_file) + 1];
|
|
if (result.use_tree)
|
|
{
|
|
char t_prefix[BINLOG_FILE_EXTRA_INFO];
|
|
sprintf(t_prefix,
|
|
"%" PRIu32 "/%" PRIu32 "/",
|
|
router->mariadb10_gtid_domain,
|
|
router->orig_masterid);
|
|
|
|
// Add prefix before filename
|
|
sprintf(last_filename,
|
|
"%s%s",
|
|
t_prefix,
|
|
current_file);
|
|
filename = last_filename;
|
|
}
|
|
else
|
|
{
|
|
filename = current_file;
|
|
}
|
|
|
|
/* Create & write the new row */
|
|
if ((pkt = blr_create_result_row(filename,
|
|
pos,
|
|
seqno)) != NULL)
|
|
{
|
|
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
|
|
/* Increment sequence */
|
|
seqno++;
|
|
}
|
|
}
|
|
|
|
/* Add the result set EOF */
|
|
return blr_slave_send_eof(router, slave, seqno);
|
|
}
|
|
|
|
/**
|
|
* Creates a Result Set row with two STRING columns
|
|
*
|
|
* @param val1 First column value
|
|
* @param val2 Second column value
|
|
* @param seq_no Sequence number for this row
|
|
* @return An allocated GWBUF or NULL
|
|
*/
|
|
GWBUF* blr_create_result_row(const char* val1,
|
|
const char* val2,
|
|
int seq_no)
|
|
{
|
|
int val1_len = strlen(val1);
|
|
int val2_len = strlen(val2);
|
|
GWBUF* pkt;
|
|
uint8_t* ptr;
|
|
int len = MYSQL_HEADER_LEN + (1 + val1_len + (1 + val2_len));
|
|
|
|
// Allocate a new GWBUF buffer
|
|
if ((pkt = gwbuf_alloc(len)) == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
ptr = GWBUF_DATA(pkt);
|
|
// Add length of data packet
|
|
encode_value(ptr, len - MYSQL_HEADER_LEN, 24);
|
|
ptr += 3;
|
|
// Sequence number in response
|
|
*ptr++ = seq_no;
|
|
// Length of result string "val1"
|
|
*ptr++ = val1_len;
|
|
memcpy((char*)ptr, val1, val1_len);
|
|
ptr += val1_len;
|
|
// Length of result string "val2"
|
|
*ptr++ = val2_len;
|
|
memcpy((char*)ptr, val2, val2_len);
|
|
|
|
return pkt;
|
|
}
|
|
|
|
/**
|
|
* Binary logs select callback for sqlite3 database
|
|
*
|
|
* @param data Data pointer from caller
|
|
* @param cols Number of columns
|
|
* @param values The values
|
|
* @param names The column names
|
|
*
|
|
* @return 0 on success, 1 otherwise
|
|
*/
|
|
static int binary_logs_select_cb(void* data,
|
|
int cols,
|
|
char** values,
|
|
char** names)
|
|
{
|
|
BINARY_LOG_DATA_RESULT* data_set = (BINARY_LOG_DATA_RESULT*)data;
|
|
DCB* dcb = data_set->client;
|
|
int ret = 1; // Failure
|
|
uint32_t fsize;
|
|
char file_size[40];
|
|
|
|
mxb_assert(cols >= 4 && dcb);
|
|
|
|
if (values[0] // File Name
|
|
&& values[1] // File Size
|
|
&& values[2] // Domain ID
|
|
&& values[3]) // Server ID
|
|
{
|
|
GWBUF* pkt;
|
|
char file_path[PATH_MAX + 1];
|
|
char filename[1
|
|
+ strlen(values[0])
|
|
+ BINLOG_FILE_EXTRA_INFO];
|
|
char t_prefix[BINLOG_FILE_EXTRA_INFO] = "";
|
|
|
|
sprintf(t_prefix,
|
|
"%s/%s/",
|
|
values[2], // domain ID
|
|
values[3]); // server ID
|
|
|
|
fsize = atoll(values[1]);
|
|
|
|
/* File size != 0 && server ID != 0 */
|
|
mxb_assert(fsize && atoll(values[3]));
|
|
|
|
/**
|
|
* In GTID repo binlog file last pos is last GTID.
|
|
* In case of rotate_event or any event the "file_size"
|
|
* it's not correct.
|
|
* In case of binlog files with no transactions at all
|
|
* the saved size is 4.
|
|
*
|
|
* Let's get the real size by calling blr_slave_get_file_size()
|
|
*/
|
|
|
|
// Get filename full-path, use prefix only if binlog_structure is TREE
|
|
blr_get_file_fullpath(values[0],
|
|
data_set->binlogdir,
|
|
file_path,
|
|
data_set->use_tree ?
|
|
t_prefix :
|
|
NULL);
|
|
// Get the file size
|
|
fsize = blr_slave_get_file_size(file_path);
|
|
|
|
sprintf(file_size, "%" PRIu32 "", fsize);
|
|
|
|
// Include prefix in the output
|
|
if (data_set->use_tree)
|
|
{
|
|
sprintf(filename,
|
|
"%s%s",
|
|
t_prefix,
|
|
values[0]); // filename
|
|
}
|
|
else
|
|
{
|
|
sprintf(filename, "%s", values[0]); // filename only
|
|
}
|
|
|
|
/* Create the MySQL Result Set row */
|
|
if ((pkt = blr_create_result_row(filename, // File name
|
|
file_size, // File size
|
|
data_set->seq_no)) != NULL)
|
|
{
|
|
/* Increase sequence for next row */
|
|
data_set->seq_no++;
|
|
/* Free last file name */
|
|
MXS_FREE(data_set->last_file);
|
|
/* Set last file name */
|
|
data_set->last_file = MXS_STRDUP_A(values[0]);
|
|
/* Write packet to client */
|
|
MXS_SESSION_ROUTE_REPLY(dcb->session, pkt);
|
|
/* Set success */
|
|
ret = 0;
|
|
}
|
|
return ret; /* Return success or fallure */
|
|
}
|
|
else
|
|
{
|
|
return 0; /* Success: no data from db or end of result set */
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle SELECT @@server_id, @@read_only
|
|
* that MaxScale MySQL monitor sends to monitored servers
|
|
*
|
|
* @param router The router instance
|
|
* @param slave The connected client
|
|
* @return Number of bytes written
|
|
*/
|
|
static int blr_slave_send_id_ro(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave)
|
|
{
|
|
int seqno;
|
|
GWBUF* pkt;
|
|
/**
|
|
* First part of result set:
|
|
* send 2 columns and their defintions.
|
|
*/
|
|
|
|
/* This call sets seq to 1 in the packet */
|
|
blr_slave_send_fieldcount(router, slave, 2);
|
|
/* Set 'seqno' counter to next value: 2 */
|
|
seqno = 2;
|
|
/* Col 1 def */
|
|
blr_slave_send_columndef(router,
|
|
slave,
|
|
"@@server_id",
|
|
BLR_TYPE_INT,
|
|
40,
|
|
seqno++);
|
|
/* Col 2 def */
|
|
blr_slave_send_columndef(router,
|
|
slave,
|
|
"@@read_only",
|
|
BLR_TYPE_INT,
|
|
40,
|
|
seqno++);
|
|
/* Cols EOF */
|
|
blr_slave_send_eof(router, slave, seqno++);
|
|
|
|
/* Create the MySQL Result Set row */
|
|
char server_id[40] = "";
|
|
/* Set identy for MySQL replication monitor */
|
|
sprintf(server_id,
|
|
"%d",
|
|
router->set_master_server_id ?
|
|
router->masterid :
|
|
router->serverid);
|
|
|
|
if ((pkt = blr_create_result_row(server_id, // File name
|
|
"0", // o = OFF
|
|
seqno++)) != NULL)
|
|
{
|
|
/* Write packet to client */
|
|
MXS_SESSION_ROUTE_REPLY(slave->dcb->session, pkt);
|
|
}
|
|
|
|
/* Add the result set EOF and return */
|
|
return blr_slave_send_eof(router, slave, seqno);
|
|
}
|
|
|
|
/**
|
|
* Handle a SELECT with more than one column.
|
|
*
|
|
* Only SELECT @@server_id, @@read_only is supported.
|
|
* That query is sent by MaxScale MySQL monitor.
|
|
*
|
|
* @param router The router instance
|
|
* @param slave The connected client
|
|
* @param col1 The first column
|
|
* @param coln Whatever is after first column
|
|
* @return True is handled, false otherwise
|
|
*/
|
|
static bool blr_handle_complex_select(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
const char* col1,
|
|
const char* coln)
|
|
{
|
|
/* Strip leading spaces */
|
|
while (isspace(*coln))
|
|
{
|
|
coln++;
|
|
}
|
|
|
|
if ((strcasecmp(col1, "@@server_id") == 0
|
|
|| strcasecmp(col1, "@@global.server_id") == 0)
|
|
&& (strcasecmp(coln, "@@read_only") == 0
|
|
|| strcasecmp(coln, "@@global.read_only") == 0))
|
|
{
|
|
blr_slave_send_id_ro(router, slave);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Purge binary logs find binlog callback for sqlite3 database
|
|
*
|
|
* @param data Data pointer from caller
|
|
* @param cols Number of columns
|
|
* @param values The values
|
|
* @param names The column names
|
|
*
|
|
* @return 0 on success, 1 otherwise
|
|
*/
|
|
static int binary_logs_find_file_cb(void* data,
|
|
int cols,
|
|
char** values,
|
|
char** names)
|
|
{
|
|
mxb_assert(cols == 2);
|
|
BINARY_LOG_DATA_RESULT* data_set = (BINARY_LOG_DATA_RESULT*)data;
|
|
|
|
if (values[0]) // Server ID
|
|
{
|
|
data_set->rowid = atoll(values[0]);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Purge binary logs delete files callback for sqlite3 database
|
|
*
|
|
* @param data Data pointer from caller
|
|
* @param cols Number of columns
|
|
* @param values The values
|
|
* @param names The column names
|
|
*
|
|
* @return 0 on success, 1 otherwise
|
|
*/
|
|
static int binary_logs_purge_cb(void* data,
|
|
int cols,
|
|
char** values,
|
|
char** names)
|
|
{
|
|
mxb_assert(cols == 2);
|
|
|
|
BINARY_LOG_DATA_RESULT* result_data = (BINARY_LOG_DATA_RESULT*)data;
|
|
|
|
if (values[0] && values[1])
|
|
{
|
|
char* filename;
|
|
char full_path[PATH_MAX + 1];
|
|
|
|
/* values[0] is filename, values[1] is prefix + file */
|
|
filename = !result_data->use_tree ?
|
|
values[0] :
|
|
values[1];
|
|
|
|
sprintf(full_path, "%s/%s", result_data->binlogdir, filename);
|
|
|
|
MXS_DEBUG("Deleting binlog file %s", full_path);
|
|
|
|
if (unlink(full_path) == -1 && errno != ENOENT)
|
|
{
|
|
MXS_ERROR("Failed to remove binlog file '%s': %d, %s",
|
|
full_path,
|
|
errno,
|
|
mxs_strerror(errno));
|
|
}
|
|
result_data->n_files++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Parse the PURGE BINARY LOGS TO 'file' SQL statement.
|
|
*
|
|
* @param purge_command The SQL command to parse
|
|
* @return The file found in the command.
|
|
* or NULL in case of parse errors.
|
|
*/
|
|
static const char* blr_purge_getfile(char* purge_command)
|
|
{
|
|
char* word;
|
|
char* brkb;
|
|
const char* sep = " \t";
|
|
|
|
word = strtok_r(purge_command, sep, &brkb);
|
|
|
|
// Check BINARY
|
|
if (strcasecmp(word, "BINARY") != 0)
|
|
{
|
|
MXS_ERROR("Invalid PURGE command: PURGE %s", word);
|
|
return NULL;
|
|
}
|
|
|
|
word = strtok_r(NULL, sep, &brkb);
|
|
|
|
// Check LOGS
|
|
if (!word || strcasecmp(word, "LOGS") != 0)
|
|
{
|
|
MXS_ERROR("Invalid PURGE command: PURGE BINARY %s",
|
|
word ? word : "");
|
|
return NULL;
|
|
}
|
|
|
|
word = strtok_r(NULL, sep, &brkb);
|
|
|
|
// Nothing else, return error
|
|
if (!word)
|
|
{
|
|
MXS_ERROR("Invalid PURGE command: PURGE BINARY LOGS");
|
|
return NULL;
|
|
}
|
|
else
|
|
// Check for TO 'file'
|
|
{
|
|
if (strcasecmp(word, "TO") != 0)
|
|
{
|
|
MXS_ERROR("Invalid PURGE command: PURGE BINARY LOGS %s", word);
|
|
return NULL;
|
|
}
|
|
// Get filename
|
|
if ((word = strtok_r(NULL, sep, &brkb)) != NULL)
|
|
{
|
|
// Remove heading and trailing "'"
|
|
char* p = word;
|
|
if (*p == '\'')
|
|
{
|
|
word++;
|
|
}
|
|
if (p[strlen(p) - 1] == '\'')
|
|
{
|
|
p[strlen(p) - 1] = '\0';
|
|
}
|
|
return word;
|
|
}
|
|
else
|
|
{
|
|
MXS_ERROR("Invalid PURGE command: PURGE BINARY LOGS TO");
|
|
return NULL;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Purge MaxScale binlog files
|
|
*
|
|
* The routine it's called olny if mariadb10_slave_gtid option is set
|
|
* as the up to date list of binlog files is in the GTID maps repo.
|
|
*
|
|
* Note: the current binlog file is not deleted frm disk/db.
|
|
*
|
|
* @param router The router instance
|
|
* @param slave The connected client
|
|
* @param purge_opts The PURGE BINARY LOGS options
|
|
* @retun Sent bytes
|
|
*/
|
|
static bool blr_purge_binary_logs(ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
char* purge_opts)
|
|
{
|
|
char* errmsg = NULL;
|
|
size_t n_delete = 0;
|
|
// Select first ROWID of user specifed file
|
|
static const char find_file_tpl[] = "SELECT MIN(id) AS min_id, "
|
|
"(rep_domain || '/' || "
|
|
"server_id || '/' || "
|
|
"binlog_file) AS file "
|
|
"FROM gtid_maps "
|
|
"WHERE binlog_file = '%s' "
|
|
"GROUP BY binlog_file "
|
|
"ORDER BY id ASC;";
|
|
// SELECT files with ROWID < given one and DELETE
|
|
static const char delete_list_tpl[] = "SELECT binlog_file, "
|
|
"(rep_domain || '/' || "
|
|
"server_id || '/' || "
|
|
"binlog_file) AS file "
|
|
"FROM gtid_maps "
|
|
"WHERE id < %" PRIu64 " "
|
|
"GROUP BY file "
|
|
"ORDER BY id ASC; "
|
|
"DELETE FROM gtid_maps "
|
|
"WHERE id < %" PRIu64 ";";
|
|
static char sql_stmt[GTID_SQL_BUFFER_SIZE];
|
|
BINARY_LOG_DATA_RESULT result;
|
|
static const char* selected_file;
|
|
|
|
/**
|
|
* Parse PURGE BINARY LOGS TO 'file' statement
|
|
*/
|
|
if ((selected_file = blr_purge_getfile(purge_opts)) == NULL)
|
|
{
|
|
// Abort on parsing failure
|
|
blr_slave_send_error_packet(slave,
|
|
"Malformed PURGE BINARY LOGS TO 'file' detected.",
|
|
1064,
|
|
"42000");
|
|
return false;
|
|
}
|
|
|
|
/* Initialise result data fields */
|
|
result.rowid = 0;
|
|
result.n_files = 0;
|
|
result.binlogdir = router->binlogdir;
|
|
result.use_tree = router->storage_type == BLR_BINLOG_STORAGE_TREE;
|
|
|
|
/* Use the provided name, no prefix: find the first row */
|
|
sprintf(sql_stmt,
|
|
find_file_tpl,
|
|
selected_file);
|
|
|
|
/* Get file rowid */
|
|
if (sqlite3_exec(router->gtid_maps,
|
|
sql_stmt,
|
|
binary_logs_find_file_cb,
|
|
&result,
|
|
&errmsg) != SQLITE_OK)
|
|
{
|
|
MXS_ERROR("PURGE BINARY LOGS: failed to select ROWID of current file "
|
|
"from GTID maps DB, %s, select [%s]",
|
|
errmsg,
|
|
sql_stmt);
|
|
sqlite3_free(errmsg);
|
|
|
|
blr_slave_send_error_packet(slave,
|
|
"Cannot find current file in binlog GTID DB.",
|
|
1373,
|
|
NULL);
|
|
return false;
|
|
}
|
|
|
|
if (result.rowid)
|
|
{
|
|
/* Prepare SQL statement for ROWID < result.rowid */
|
|
sprintf(sql_stmt,
|
|
delete_list_tpl,
|
|
result.rowid,
|
|
result.rowid);
|
|
|
|
/* Purge all files with ROWID < result.rowid */
|
|
if (sqlite3_exec(router->gtid_maps,
|
|
sql_stmt,
|
|
binary_logs_purge_cb,
|
|
&result,
|
|
&errmsg) != SQLITE_OK)
|
|
{
|
|
MXS_ERROR("Failed to select list of files to purge"
|
|
"from GTID maps DB: %s, select [%s]",
|
|
errmsg,
|
|
sql_stmt);
|
|
sqlite3_free(errmsg);
|
|
|
|
blr_slave_send_error_packet(slave,
|
|
"Cannot build the purge list of files.",
|
|
1373,
|
|
NULL);
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
blr_slave_send_error_packet(slave,
|
|
"Target log not found in binlog index",
|
|
1373,
|
|
NULL);
|
|
return false;
|
|
}
|
|
|
|
MXS_INFO("Deleted %lu binlog files in %s",
|
|
result.n_files,
|
|
result.binlogdir);
|
|
|
|
// Send OK and nithing else
|
|
blr_slave_send_ok(router, slave);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Log previus master details configuration and new added options.
|
|
*
|
|
* @param router The current router instance
|
|
* @param current_master The current master server details
|
|
* @param change_master The options in CHANGE MASTER TO command
|
|
*/
|
|
static void blr_log_config_changes(ROUTER_INSTANCE* router,
|
|
const MasterServerConfig& current_master,
|
|
const ChangeMasterConfig& change_master)
|
|
{
|
|
/* Prepare heartbeat and retry msgs */
|
|
static const char heartbeat[] = ", MASTER_HEARTBEAT_PERIOD=";
|
|
static const char retry[] = ", MASTER_CONNECT_RETRY=";
|
|
std::string h;
|
|
if (change_master.heartbeat_period != -1)
|
|
{
|
|
h = std::to_string(change_master.heartbeat_period);
|
|
}
|
|
std::string r;
|
|
if (change_master.connect_retry != -1)
|
|
{
|
|
r = std::to_string(change_master.connect_retry);
|
|
}
|
|
char heartbeat_msg[sizeof(heartbeat) + h.length()];
|
|
char retry_msg[sizeof(retry) + r.length()];
|
|
heartbeat_msg[0] = 0;
|
|
retry_msg[0] = 0;
|
|
|
|
if (!h.empty())
|
|
{
|
|
sprintf(heartbeat_msg,
|
|
"%s%lu",
|
|
heartbeat,
|
|
router->heartbeat); // Display the current "long" value
|
|
}
|
|
|
|
if (!r.empty())
|
|
{
|
|
sprintf(retry_msg,
|
|
"%s%d",
|
|
retry,
|
|
router->retry_interval); // Display the current "long" value
|
|
}
|
|
|
|
/* Prepare GTID msg */
|
|
const char* gtid_msg =
|
|
!change_master.use_mariadb10_gtid.empty() ?
|
|
", MASTER_USE_GTID=Slave_pos" :
|
|
"";
|
|
|
|
/* Log previous state and new changes */
|
|
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'"
|
|
"%s%s%s",
|
|
router->service->name(),
|
|
current_master.host.c_str(),
|
|
current_master.port,
|
|
current_master.logfile.c_str(),
|
|
current_master.pos,
|
|
current_master.user.c_str(),
|
|
router->service->dbref->server->address,
|
|
router->service->dbref->server->port,
|
|
router->binlog_name,
|
|
router->current_pos,
|
|
router->user,
|
|
gtid_msg,
|
|
heartbeat_msg,
|
|
retry_msg);
|
|
}
|
|
|
|
/**
|
|
* Check whether connecting slave server can continue
|
|
* the MySQL Slave protocol registration to binlog server.
|
|
*
|
|
* (1) Binlog Server should be configured.
|
|
*
|
|
* (2) Connecting Slave must be MariaDB 10 one,
|
|
* if master->mariadb10_compat is set
|
|
*
|
|
* (3) If mariadb10_master_gtid option is set,
|
|
* a slave without GTID Request will be rejected.
|
|
*
|
|
* @param router The router instance
|
|
* @param slave The registering slave
|
|
* @param check The check requested by the caller
|
|
* @return true on succes, false otherwise
|
|
*/
|
|
static bool blr_check_connecting_slave(const ROUTER_INSTANCE* router,
|
|
ROUTER_SLAVE* slave,
|
|
enum blr_slave_check check)
|
|
{
|
|
int rv = true;
|
|
const char* err_msg = NULL;
|
|
const char* err_status = "HY000";
|
|
int err_code = BINLOG_FATAL_ERROR_READING;
|
|
const char* msg_detail = "";
|
|
|
|
switch (check)
|
|
{
|
|
case BLR_SLAVE_CONNECTING: // (1)
|
|
if (router->master_state == BLRM_UNCONFIGURED)
|
|
{
|
|
err_msg = "Binlog router is not yet configured"
|
|
" for replication.";
|
|
rv = false;
|
|
}
|
|
break;
|
|
|
|
case BLR_SLAVE_IS_MARIADB10: // (2)
|
|
/**
|
|
* If Master is MariaDB10 don't allow registration from
|
|
* MariaDB/Mysql 5 Slaves
|
|
*/
|
|
if (router->mariadb10_compat && !slave->mariadb10_compat)
|
|
{
|
|
err_msg = "MariaDB 10 Slave is required"
|
|
" for Slave registration.";
|
|
rv = false;
|
|
}
|
|
break;
|
|
|
|
case BLR_SLAVE_HAS_MARIADB10_GTID: // (3)
|
|
/**
|
|
* Check for mariadb10_master_gtid option set to On and
|
|
* connecting slave with GTID request.
|
|
*/
|
|
if (router->mariadb10_master_gtid && !slave->mariadb_gtid)
|
|
{
|
|
err_msg = "MariaDB 10 Slave GTID is required"
|
|
" for Slave registration.";
|
|
msg_detail = " Please use: CHANGE MASTER TO master_use_gtid=slave_pos.";
|
|
|
|
rv = false;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
MXS_WARNING("%s: Slave %s: Unkwon status check %d.",
|
|
router->service->name(),
|
|
slave->dcb->remote,
|
|
check);
|
|
break;
|
|
}
|
|
|
|
if (!rv)
|
|
{
|
|
/* Force BLRS_ERRORED state */
|
|
pthread_mutex_lock(&slave->catch_lock);
|
|
slave->state = BLRS_ERRORED;
|
|
pthread_mutex_unlock(&slave->catch_lock);
|
|
|
|
/* Send error that stops slave replication */
|
|
blr_send_custom_error(slave->dcb,
|
|
++slave->seqno,
|
|
0,
|
|
err_msg,
|
|
err_status,
|
|
err_code);
|
|
MXS_ERROR("%s: Slave %s: %s%s",
|
|
router->service->name(),
|
|
slave->dcb->remote,
|
|
err_msg,
|
|
msg_detail);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
* Abort the change master process
|
|
*
|
|
* @param router The router instance
|
|
* @param current_master Current master configuration
|
|
* @param change_master The CHANGE MASTER TO options
|
|
* @param error Error message to log
|
|
*/
|
|
static void blr_abort_change_master(ROUTER_INSTANCE* router,
|
|
const MasterServerConfig& current_master,
|
|
const char* error)
|
|
{
|
|
MXS_ERROR("%s: %s", router->service->name(), error);
|
|
/* restore previous master_host and master_port */
|
|
blr_master_restore_config(router, current_master);
|
|
}
|
|
|
|
/**
|
|
* Abort the COM_BINLOG_DUMP slave request
|
|
*
|
|
* @param slave The connecting slave
|
|
* @param errmsg Error message to send and log
|
|
*/
|
|
static void blr_slave_abort_dump_request(ROUTER_SLAVE* slave,
|
|
const char* errmsg)
|
|
{
|
|
MXS_ERROR("Slave %lu requests COM_BINLOG_DUMP: %s. Aborting.",
|
|
(unsigned long)slave->serverid,
|
|
errmsg);
|
|
|
|
blr_send_custom_error(slave->dcb,
|
|
slave->seqno++,
|
|
0,
|
|
errmsg,
|
|
"HY000",
|
|
BINLOG_FATAL_ERROR_READING);
|
|
}
|
|
|
|
/**
|
|
* Check whether the current binlog file can be changed
|
|
*
|
|
* @param router The curret router instance
|
|
* @param change_master The options in CHANGE_MASTER TO command
|
|
* @param error Pointer to outout error message
|
|
*
|
|
* @return True if binlog can be changed or false.
|
|
*/
|
|
static bool blr_binlog_change_check(const ROUTER_INSTANCE* router,
|
|
const ChangeMasterConfig& change_master,
|
|
char* error)
|
|
{
|
|
char* master_logfile = NULL;
|
|
|
|
/**
|
|
* MASTER_LOG_FILE is not set in CHANGE MASTER TO.
|
|
* If binlog server is not configured and
|
|
* mariadb10_master_gtid is not set, then return an error.
|
|
*/
|
|
if (change_master.binlog_file.empty())
|
|
{
|
|
if (router->master_state == BLRM_UNCONFIGURED
|
|
&& !router->mariadb10_master_gtid)
|
|
{
|
|
snprintf(error,
|
|
BINLOG_ERROR_MSG_LEN,
|
|
"Router is not configured for master connection, "
|
|
"MASTER_LOG_FILE is required");
|
|
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
/**
|
|
* If binlog file is set in CHANGE MASTER TO
|
|
* and MASTER_USE_GTID option is on, then return an error.
|
|
*/
|
|
{
|
|
/**
|
|
* Check first MASTER_USE_GTID option:
|
|
*
|
|
* if not present return an error:
|
|
*/
|
|
if (router->mariadb10_master_gtid
|
|
&& change_master.use_mariadb10_gtid.empty())
|
|
{
|
|
snprintf(error,
|
|
BINLOG_ERROR_MSG_LEN,
|
|
"%s MASTER_USE_GTID=Slave_pos is required",
|
|
router->master_state == BLRM_UNCONFIGURED ?
|
|
"Router is not configured for master connection," :
|
|
"Cannot use MASTER_LOG_FILE for master connection,");
|
|
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Set the new binlog filenanme from MASTER_LOG_FILE
|
|
*
|
|
* If no name change, the current router file is set.
|
|
* Note:
|
|
* Empty filename, MASTER_LOG_FILE = '', can be passed only
|
|
* if mariadb10_master_gtid option is set.
|
|
*
|
|
* @param router The router instance
|
|
* @param binlog_file The binlog file in MASTER_LOG_FILE
|
|
* @param error Ouput error message
|
|
* @param new_logfile The new log file name
|
|
*
|
|
* @return True if binlog name has been set or false
|
|
* on errors
|
|
*/
|
|
static bool blr_change_binlog_name(ROUTER_INSTANCE* router,
|
|
const char* binlog_file,
|
|
char** new_logfile,
|
|
char* error)
|
|
{
|
|
bool ret = true;
|
|
|
|
/* MASTER_LOG_FILE is not present in CHANGE MASTER TO */
|
|
if (binlog_file == NULL)
|
|
{
|
|
/* Use current binlog file */
|
|
*new_logfile = MXS_STRDUP_A(router->binlog_name);
|
|
}
|
|
else
|
|
{
|
|
/**
|
|
* Change the binlog filename as from MASTER_LOG_FILE
|
|
* New binlog file can be:
|
|
* - the next in sequence router file
|
|
* - current router file
|
|
* - empty if router->mariadb10_master_gtid is set.
|
|
*/
|
|
*new_logfile = blr_set_master_logfile(router,
|
|
binlog_file,
|
|
error);
|
|
if (*new_logfile == NULL)
|
|
{
|
|
if (!router->mariadb10_master_gtid
|
|
|| strlen(binlog_file) > 1)
|
|
{
|
|
/* Binlog name can not be changed */
|
|
ret = false;
|
|
}
|
|
else
|
|
{
|
|
*new_logfile = MXS_STRDUP_A("");
|
|
// Blank the error message
|
|
error[0] = 0;
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static bool blr_change_binlog_name(ROUTER_INSTANCE* router,
|
|
const std::string& binlog_file,
|
|
char** new_logfile,
|
|
char* error)
|
|
{
|
|
return blr_change_binlog_name(router,
|
|
binlog_file.empty() ? nullptr : binlog_file.c_str(),
|
|
new_logfile,
|
|
error);
|
|
}
|
|
|
|
/**
|
|
* Apply binlog filename and position changes
|
|
*
|
|
* @param router The current router instance
|
|
* @param change_master The change master options
|
|
* @param error The error message to fill
|
|
* @param new_logfile The new binlog filename
|
|
* set in previous stage
|
|
*
|
|
* @return True if changes have been set
|
|
* or false on errors.
|
|
*/
|
|
static bool blr_apply_changes(ROUTER_INSTANCE* router,
|
|
const ChangeMasterConfig& change_master,
|
|
char* new_logfile,
|
|
char* error)
|
|
{
|
|
bool ret = true;
|
|
const char* master_log_pos = NULL;
|
|
long long pos = 0;
|
|
|
|
/* Set new binlog position from MASTER_LOG_POS */
|
|
master_log_pos = change_master.binlog_pos.empty() ? nullptr : change_master.binlog_pos.c_str();
|
|
if (master_log_pos == NULL)
|
|
{
|
|
pos = 0;
|
|
}
|
|
else
|
|
{
|
|
pos = atoll(master_log_pos);
|
|
}
|
|
|
|
/**
|
|
* Binlog name and position are checked if:
|
|
* - mariadb10_master_gtid is off and
|
|
* - master connection is already configured.
|
|
* Checks:
|
|
* (1) filename is different from current router log file and pos != 4
|
|
* (2) position is not current one for current file
|
|
*
|
|
* Binlog file and pos can set after all checks.
|
|
*/
|
|
|
|
/* MariaDB 10 GTID request */
|
|
if (router->mariadb10_master_gtid)
|
|
{
|
|
if (!change_master.use_mariadb10_gtid.empty())
|
|
{
|
|
/* MASTER_USE_GTID=Slave_pos is set */
|
|
MXS_INFO("%s: MASTER_USE_GTID is [%s]",
|
|
router->service->name(),
|
|
change_master.use_mariadb10_gtid.c_str());
|
|
}
|
|
|
|
/* Always log the current GTID value with CHANGE_MASTER TO */
|
|
MXS_INFO("%s: CHANGE MASTER TO, current GTID value is [%s]",
|
|
router->service->name(),
|
|
router->last_mariadb_gtid);
|
|
|
|
/* Always set empty filename at pos 4 with CHANGE_MASTER TO */
|
|
strcpy(router->binlog_name, "");
|
|
|
|
router->current_pos = 4;
|
|
router->binlog_position = 4;
|
|
router->current_safe_event = 4;
|
|
}
|
|
/* The new filename is not the current one */
|
|
else if (strcmp(new_logfile, router->binlog_name) != 0
|
|
&& router->master_state != BLRM_UNCONFIGURED)
|
|
{
|
|
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",
|
|
new_logfile,
|
|
4,
|
|
router->binlog_name,
|
|
router->current_pos);
|
|
|
|
ret = false;
|
|
}
|
|
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,
|
|
new_logfile,
|
|
4,
|
|
router->binlog_name,
|
|
router->current_pos);
|
|
|
|
ret = false;
|
|
}
|
|
}
|
|
|
|
/* Set new binlog name at pos 4 */
|
|
if (ret)
|
|
{
|
|
strcpy(router->binlog_name, new_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).
|
|
* Note:
|
|
* pos is not checked if router->mariadb10_master_gtid is set
|
|
*/
|
|
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,
|
|
new_logfile);
|
|
|
|
ret = false;
|
|
}
|
|
else
|
|
{
|
|
/**
|
|
* BLRM_UNCONFIGURED state:
|
|
* - set pos to 4
|
|
* - set binlog name
|
|
*/
|
|
router->current_pos = 4;
|
|
router->binlog_position = 4;
|
|
router->current_safe_event = 4;
|
|
strcpy(router->binlog_name, new_logfile);
|
|
|
|
MXS_INFO("%s: New MASTER_LOG_FILE is [%s]",
|
|
router->service->name(),
|
|
router->binlog_name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (master_log_pos != NULL
|
|
&& pos != static_cast<long long>(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);
|
|
|
|
ret = false;
|
|
}
|
|
}
|
|
|
|
if (ret)
|
|
{
|
|
MXS_INFO("%s: New MASTER_LOG_POS is [%lu]",
|
|
router->service->name(),
|
|
router->current_pos);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Saves a MARIADB_GTID_INFO data for later usage
|
|
*
|
|
* @param info The MARIADB_GTID_INFO data to copy
|
|
* @param save_info The MARIADB_GTID_INFO allocated
|
|
* buffer to save data
|
|
* @param save_prefix The allocated buffer where
|
|
* to save file prefix
|
|
*/
|
|
static void blr_slave_info_save(const MARIADB_GTID_INFO* info,
|
|
MARIADB_GTID_INFO* save_info,
|
|
char* save_prefix)
|
|
{
|
|
/* Save current file details */
|
|
memcpy(save_info, info, sizeof(MARIADB_GTID_INFO));
|
|
|
|
/* Fill save file prefix */
|
|
sprintf(save_prefix,
|
|
"%" PRIu32 "/%" PRIu32 "/",
|
|
save_info->gtid_elms.domain_id,
|
|
save_info->gtid_elms.server_id);
|
|
}
|
|
|
|
/**
|
|
* Log message for slave file End Of File
|
|
*
|
|
* @param router The current router instance
|
|
* @param slave The connected slave
|
|
* @param c_prefix The file prefix of slave file
|
|
* @param next_file The next file to read or fake rotate to
|
|
* @param log_action The action type to log
|
|
*/
|
|
static void blr_slave_log_next_file_action(const ROUTER_INSTANCE* router,
|
|
const ROUTER_SLAVE* slave,
|
|
const char* c_prefix,
|
|
const char* next_file,
|
|
slave_eof_action_t log_action)
|
|
{
|
|
char m_prefix[BINLOG_FILE_EXTRA_INFO] = "";
|
|
char r_prefix[BINLOG_FILE_EXTRA_INFO] = "";
|
|
bool s_tree = router->storage_type == BLR_BINLOG_STORAGE_TREE;
|
|
bool have_heartbeat = router->send_slave_heartbeat
|
|
&& (slave->heartbeat > 0);
|
|
|
|
pthread_mutex_lock(&router->binlog_lock);
|
|
if (s_tree)
|
|
{
|
|
/* Get master file prefix */
|
|
sprintf(m_prefix,
|
|
"%" PRIu32 "/%" PRIu32 "/",
|
|
router->mariadb10_gtid_domain,
|
|
router->orig_masterid);
|
|
/* Get rotating slave file prefix */
|
|
sprintf(r_prefix,
|
|
"%" PRIu32 "/%" PRIu32 "/",
|
|
slave->f_info.gtid_elms.domain_id,
|
|
slave->f_info.gtid_elms.server_id);
|
|
}
|
|
pthread_mutex_unlock(&router->binlog_lock);
|
|
|
|
switch (log_action)
|
|
{
|
|
case SLAVE_EOF_ROTATE:
|
|
/* This has to be always logged */
|
|
MXS_WARNING("%s: Slave [%s]:%d, server-id %d reached end of file for binlog file [%s%s] "
|
|
"at %lu which is not the file currently being downloaded or last file found. "
|
|
"This may be caused by a previous failure of the master. "
|
|
"Current master binlog is [%s%s] at %lu, replication state is [%s]. "
|
|
"Now rotating to new file [%s%s]",
|
|
router->service->name(),
|
|
slave->dcb->remote,
|
|
dcb_get_port(slave->dcb),
|
|
slave->serverid,
|
|
c_prefix,
|
|
slave->binlog_name,
|
|
(unsigned long)slave->binlog_pos,
|
|
m_prefix,
|
|
router->binlog_name[0] ? router->binlog_name : "no_set_yet",
|
|
router->binlog_position,
|
|
blrm_states[router->master_state],
|
|
r_prefix,
|
|
next_file);
|
|
break;
|
|
|
|
case SLAVE_EOF_ERROR:
|
|
/* Log error */
|
|
MXS_ERROR("%s: Slave [%s]:%d, server-id %d reached "
|
|
"end of file for '%s%s' and next file to read%s%s%s%s "
|
|
"is not %s. Force replication abort after %d retries.",
|
|
router->service->name(),
|
|
slave->dcb->remote,
|
|
dcb_get_port(slave->dcb),
|
|
slave->serverid,
|
|
c_prefix,
|
|
slave->binlog_name,
|
|
next_file[0] ? " '" : "",
|
|
next_file[0] ? r_prefix : "",
|
|
next_file,
|
|
next_file[0] ? "'" : "",
|
|
next_file[0] ? "accessible" : "existent",
|
|
MISSING_FILE_READ_RETRIES);
|
|
break;
|
|
|
|
case SLAVE_EOF_WARNING:
|
|
/* We don't have the next_file, just warning */
|
|
MXS_WARNING("%s: Slave [%s]:%d, server-id %d reached end "
|
|
"of file for binlog file [%s%s] "
|
|
"at %lu. This is the last downloaded or "
|
|
"the last file found. "
|
|
"Next file%s%s%s%s is not %s. "
|
|
"This may be caused by a previous failure of "
|
|
"the master server. Current master binlog is "
|
|
"[%s%s] at %lu and replication state is [%s]. "
|
|
"The slave server is now in '%s' state.",
|
|
router->service->name(),
|
|
slave->dcb->remote,
|
|
dcb_get_port(slave->dcb),
|
|
slave->serverid,
|
|
c_prefix,
|
|
slave->binlog_name,
|
|
(unsigned long)slave->binlog_pos,
|
|
next_file[0] ? " '" : "",
|
|
next_file[0] ? r_prefix : "",
|
|
next_file,
|
|
next_file[0] ? "'" : "",
|
|
next_file[0] ? "accessible" : "existent",
|
|
m_prefix,
|
|
router->binlog_name[0] ? router->binlog_name : "no_set_yet",
|
|
router->binlog_position,
|
|
blrm_states[router->master_state],
|
|
have_heartbeat ? "wait_state" : "read_again");
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|