Commit a985ac3a authored by Kristian Nielsen's avatar Kristian Nielsen

MDEV-6336: mysqldump --master-data does not work with GTID setups

MDEV-6344: mysqldump issues FLUSH TABLES, which gets written into binlog and replicated

Add a --gtid option (for compatibility, the original behaviour is preserved
when --gtid is not used).

With --gtid, --master-data and --dump-slave output the GTID position (the
old-style file/offset position is still output, but commented out). Also, a
CHANGE MASTER TO master_use_gtid=slave_pos is output to ensure a provisioned
slave is configured in GTID, as requested.

Without --gtid, the GTID position is still output, if available, but commented
out.

Also fix MDEV-6344, to avoid FLUSH TABLES getting into the binlog. Otherwise a
mysqldump on a slave server will silently inject a GTID which does not exist
on the master, which is highly undesirable.

Also fix an incorrect error handling around obtaining binlog position with
--master-data (was probably unlikely to trigger in most cases).
parent c16c3b9e
...@@ -92,6 +92,7 @@ enum options_client ...@@ -92,6 +92,7 @@ enum options_client
OPT_REPORT_PROGRESS, OPT_REPORT_PROGRESS,
OPT_SKIP_ANNOTATE_ROWS_EVENTS, OPT_SKIP_ANNOTATE_ROWS_EVENTS,
OPT_SSL_CRL, OPT_SSL_CRLPATH, OPT_SSL_CRL, OPT_SSL_CRLPATH,
OPT_USE_GTID,
OPT_MAX_CLIENT_OPTION /* should be always the last */ OPT_MAX_CLIENT_OPTION /* should be always the last */
}; };
......
...@@ -87,6 +87,9 @@ ...@@ -87,6 +87,9 @@
/* Chars needed to store LONGLONG, excluding trailing '\0'. */ /* Chars needed to store LONGLONG, excluding trailing '\0'. */
#define LONGLONG_LEN 20 #define LONGLONG_LEN 20
/* Max length GTID position that we will output. */
#define MAX_GTID_LENGTH 1024
static void add_load_option(DYNAMIC_STRING *str, const char *option, static void add_load_option(DYNAMIC_STRING *str, const char *option,
const char *option_value); const char *option_value);
static ulong find_set(TYPELIB *lib, const char *x, uint length, static ulong find_set(TYPELIB *lib, const char *x, uint length,
...@@ -134,6 +137,7 @@ static ulong opt_compatible_mode= 0; ...@@ -134,6 +137,7 @@ static ulong opt_compatible_mode= 0;
#define MYSQL_OPT_SLAVE_DATA_COMMENTED_SQL 2 #define MYSQL_OPT_SLAVE_DATA_COMMENTED_SQL 2
static uint opt_mysql_port= 0, opt_master_data; static uint opt_mysql_port= 0, opt_master_data;
static uint opt_slave_data; static uint opt_slave_data;
static uint opt_use_gtid;
static uint my_end_arg; static uint my_end_arg;
static char * opt_mysql_unix_port=0; static char * opt_mysql_unix_port=0;
static int first_error=0; static int first_error=0;
...@@ -346,6 +350,13 @@ static struct my_option my_long_options[] = ...@@ -346,6 +350,13 @@ static struct my_option my_long_options[] =
{"force", 'f', "Continue even if we get an SQL error.", {"force", 'f', "Continue even if we get an SQL error.",
&ignore_errors, &ignore_errors, 0, GET_BOOL, NO_ARG, &ignore_errors, &ignore_errors, 0, GET_BOOL, NO_ARG,
0, 0, 0, 0, 0, 0}, 0, 0, 0, 0, 0, 0},
{"gtid", OPT_USE_GTID, "Used together with --master-data=1 or --dump-slave=1."
"When enabled, the output from those options will set the GTID position "
"instead of the binlog file and offset; the file/offset will appear only as "
"a comment. When disabled, the GTID position will still appear in the "
"output, but only commented.",
&opt_use_gtid, &opt_use_gtid, 0, GET_BOOL, NO_ARG,
0, 0, 0, 0, 0, 0},
{"help", '?', "Display this help message and exit.", 0, 0, 0, GET_NO_ARG, {"help", '?', "Display this help message and exit.", 0, 0, 0, GET_NO_ARG,
NO_ARG, 0, 0, 0, 0, 0, 0}, NO_ARG, 0, 0, 0, 0, 0, 0},
{"hex-blob", OPT_HEXBLOB, "Dump binary strings (BINARY, " {"hex-blob", OPT_HEXBLOB, "Dump binary strings (BINARY, "
...@@ -1177,7 +1188,7 @@ check_consistent_binlog_pos(char *binlog_pos_file, char *binlog_pos_offset) ...@@ -1177,7 +1188,7 @@ check_consistent_binlog_pos(char *binlog_pos_file, char *binlog_pos_offset)
if (mysql_query_with_error_report(mysql, &res, if (mysql_query_with_error_report(mysql, &res,
"SHOW STATUS LIKE 'binlog_snapshot_%'")) "SHOW STATUS LIKE 'binlog_snapshot_%'"))
return 1; return 0;
found= 0; found= 0;
while ((row= mysql_fetch_row(res))) while ((row= mysql_fetch_row(res)))
...@@ -1200,6 +1211,90 @@ check_consistent_binlog_pos(char *binlog_pos_file, char *binlog_pos_offset) ...@@ -1200,6 +1211,90 @@ check_consistent_binlog_pos(char *binlog_pos_file, char *binlog_pos_offset)
return (found == 2); return (found == 2);
} }
/*
Get the GTID position corresponding to a given old-style binlog position.
This uses BINLOG_GTID_POS(). The advantage is that the GTID position can
be obtained completely non-blocking in this way (without the need for
FLUSH TABLES WITH READ LOCK), as the old-style position can be obtained
with START TRANSACTION WITH CONSISTENT SNAPSHOT.
Returns 0 if ok, non-zero if error.
*/
static int
get_binlog_gtid_pos(char *binlog_pos_file, char *binlog_pos_offset,
char *out_gtid_pos)
{
DYNAMIC_STRING query;
MYSQL_RES *res;
MYSQL_ROW row;
int err;
char file_buf[FN_REFLEN*2+1], offset_buf[LONGLONG_LEN*2+1];
size_t len_pos_file= strlen(binlog_pos_file);
size_t len_pos_offset= strlen(binlog_pos_offset);
if (len_pos_file >= FN_REFLEN || len_pos_offset > LONGLONG_LEN)
return 0;
mysql_real_escape_string(mysql, file_buf, binlog_pos_file, len_pos_file);
mysql_real_escape_string(mysql, offset_buf, binlog_pos_offset, len_pos_offset);
init_dynamic_string_checked(&query, "SELECT BINLOG_GTID_POS('", 256, 1024);
dynstr_append_checked(&query, file_buf);
dynstr_append_checked(&query, "', '");
dynstr_append_checked(&query, offset_buf);
dynstr_append_checked(&query, "')");
err= mysql_query_with_error_report(mysql, &res, query.str);
dynstr_free(&query);
if (err)
return err;
err= 1;
if ((row= mysql_fetch_row(res)))
{
strmake(out_gtid_pos, row[0], MAX_GTID_LENGTH-1);
err= 0;
}
mysql_free_result(res);
return err;
}
/*
Get the GTID position on a master or slave.
The parameter MASTER is non-zero to get the position on a master
(@@gtid_binlog_pos) or zero for a slave (@@gtid_slave_pos).
This uses the @@gtid_binlog_pos or @@gtid_slave_pos, so requires FLUSH TABLES
WITH READ LOCK or similar to be consistent.
Returns 0 if ok, non-zero for error.
*/
static int
get_gtid_pos(char *out_gtid_pos, int master)
{
MYSQL_RES *res;
MYSQL_ROW row;
int found;
if (mysql_query_with_error_report(mysql, &res,
(master ?
"SELECT @@GLOBAL.gtid_binlog_pos" :
"SELECT @@GLOBAL.gtid_slave_pos")))
return 1;
found= 0;
if ((row= mysql_fetch_row(res)))
{
strmake(out_gtid_pos, row[0], MAX_GTID_LENGTH-1);
found++;
}
mysql_free_result(res);
return (found != 1);
}
static char *my_case_str(const char *str, static char *my_case_str(const char *str,
uint str_len, uint str_len,
const char *token, const char *token,
...@@ -4799,12 +4894,14 @@ static int dump_selected_tables(char *db, char **table_names, int tables) ...@@ -4799,12 +4894,14 @@ static int dump_selected_tables(char *db, char **table_names, int tables)
} /* dump_selected_tables */ } /* dump_selected_tables */
static int do_show_master_status(MYSQL *mysql_con, int consistent_binlog_pos) static int do_show_master_status(MYSQL *mysql_con, int consistent_binlog_pos,
int have_mariadb_gtid, int use_gtid)
{ {
MYSQL_ROW row; MYSQL_ROW row;
MYSQL_RES *UNINIT_VAR(master); MYSQL_RES *UNINIT_VAR(master);
char binlog_pos_file[FN_REFLEN]; char binlog_pos_file[FN_REFLEN];
char binlog_pos_offset[LONGLONG_LEN+1]; char binlog_pos_offset[LONGLONG_LEN+1];
char gtid_pos[MAX_GTID_LENGTH];
char *file, *offset; char *file, *offset;
const char *comment_prefix= const char *comment_prefix=
(opt_master_data == MYSQL_OPT_MASTER_DATA_COMMENTED_SQL) ? "-- " : ""; (opt_master_data == MYSQL_OPT_MASTER_DATA_COMMENTED_SQL) ? "-- " : "";
...@@ -4815,6 +4912,9 @@ static int do_show_master_status(MYSQL *mysql_con, int consistent_binlog_pos) ...@@ -4815,6 +4912,9 @@ static int do_show_master_status(MYSQL *mysql_con, int consistent_binlog_pos)
return 1; return 1;
file= binlog_pos_file; file= binlog_pos_file;
offset= binlog_pos_offset; offset= binlog_pos_offset;
if (have_mariadb_gtid &&
get_binlog_gtid_pos(binlog_pos_file, binlog_pos_offset, gtid_pos))
return 1;
} }
else else
{ {
...@@ -4844,6 +4944,9 @@ static int do_show_master_status(MYSQL *mysql_con, int consistent_binlog_pos) ...@@ -4844,6 +4944,9 @@ static int do_show_master_status(MYSQL *mysql_con, int consistent_binlog_pos)
return 0; return 0;
} }
} }
if (have_mariadb_gtid && get_gtid_pos(gtid_pos, 1))
return 1;
} }
/* SHOW MASTER STATUS reports file and position */ /* SHOW MASTER STATUS reports file and position */
...@@ -4852,7 +4955,19 @@ static int do_show_master_status(MYSQL *mysql_con, int consistent_binlog_pos) ...@@ -4852,7 +4955,19 @@ static int do_show_master_status(MYSQL *mysql_con, int consistent_binlog_pos)
"recovery from\n--\n\n"); "recovery from\n--\n\n");
fprintf(md_result_file, fprintf(md_result_file,
"%sCHANGE MASTER TO MASTER_LOG_FILE='%s', MASTER_LOG_POS=%s;\n", "%sCHANGE MASTER TO MASTER_LOG_FILE='%s', MASTER_LOG_POS=%s;\n",
comment_prefix, file, offset); (use_gtid ? "-- " : comment_prefix), file, offset);
if (have_mariadb_gtid)
{
print_comment(md_result_file, 0,
"\n--\n-- GTID to start replication from\n--\n\n");
if (use_gtid)
fprintf(md_result_file,
"%sCHANGE MASTER TO MASTER_USE_GTID=slave_pos;\n",
comment_prefix);
fprintf(md_result_file,
"%sSET GLOBAL gtid_slave_pos='%s';\n",
(!use_gtid ? "-- " : comment_prefix), gtid_pos);
}
check_io(md_result_file); check_io(md_result_file);
if (!consistent_binlog_pos) if (!consistent_binlog_pos)
...@@ -4922,12 +5037,16 @@ static int add_slave_statements(void) ...@@ -4922,12 +5037,16 @@ static int add_slave_statements(void)
return(0); return(0);
} }
static int do_show_slave_status(MYSQL *mysql_con) static int do_show_slave_status(MYSQL *mysql_con, int use_gtid,
int have_mariadb_gtid)
{ {
MYSQL_RES *UNINIT_VAR(slave); MYSQL_RES *UNINIT_VAR(slave);
MYSQL_ROW row; MYSQL_ROW row;
const char *comment_prefix= const char *comment_prefix=
(opt_slave_data == MYSQL_OPT_SLAVE_DATA_COMMENTED_SQL) ? "-- " : ""; (opt_slave_data == MYSQL_OPT_SLAVE_DATA_COMMENTED_SQL) ? "-- " : "";
const char *gtid_comment_prefix= (use_gtid ? comment_prefix : "-- ");
const char *nogtid_comment_prefix= (!use_gtid ? comment_prefix : "-- ");
int set_gtid_done= 0;
if (mysql_query_with_error_report(mysql_con, &slave, if (mysql_query_with_error_report(mysql_con, &slave,
multi_source ? multi_source ?
...@@ -4945,8 +5064,30 @@ static int do_show_slave_status(MYSQL *mysql_con) ...@@ -4945,8 +5064,30 @@ static int do_show_slave_status(MYSQL *mysql_con)
while ((row= mysql_fetch_row(slave))) while ((row= mysql_fetch_row(slave)))
{ {
if (multi_source && !set_gtid_done)
{
char gtid_pos[MAX_GTID_LENGTH];
if (have_mariadb_gtid && get_gtid_pos(gtid_pos, 0))
return 1;
if (opt_comments)
fprintf(md_result_file, "\n--\n-- Gtid position to start replication "
"from\n--\n\n");
fprintf(md_result_file, "%sSET GLOBAL gtid_slave_pos='%s';\n",
gtid_comment_prefix, gtid_pos);
set_gtid_done= 1;
}
if (row[9 + multi_source] && row[21 + multi_source]) if (row[9 + multi_source] && row[21 + multi_source])
{ {
if (use_gtid)
{
if (multi_source)
fprintf(md_result_file, "%sCHANGE MASTER '%.80s' TO "
"MASTER_USE_GTID=slave_pos;\n", gtid_comment_prefix, row[0]);
else
fprintf(md_result_file, "%sCHANGE MASTER TO "
"MASTER_USE_GTID=slave_pos;\n", gtid_comment_prefix);
}
/* SHOW MASTER STATUS reports file and position */ /* SHOW MASTER STATUS reports file and position */
if (opt_comments) if (opt_comments)
fprintf(md_result_file, fprintf(md_result_file,
...@@ -4955,9 +5096,9 @@ static int do_show_slave_status(MYSQL *mysql_con) ...@@ -4955,9 +5096,9 @@ static int do_show_slave_status(MYSQL *mysql_con)
if (multi_source) if (multi_source)
fprintf(md_result_file, "%sCHANGE MASTER '%.80s' TO ", fprintf(md_result_file, "%sCHANGE MASTER '%.80s' TO ",
comment_prefix, row[0]); nogtid_comment_prefix, row[0]);
else else
fprintf(md_result_file, "%sCHANGE MASTER TO ", comment_prefix); fprintf(md_result_file, "%sCHANGE MASTER TO ", nogtid_comment_prefix);
if (opt_include_master_host_port) if (opt_include_master_host_port)
{ {
...@@ -5030,12 +5171,13 @@ static int do_flush_tables_read_lock(MYSQL *mysql_con) ...@@ -5030,12 +5171,13 @@ static int do_flush_tables_read_lock(MYSQL *mysql_con)
FLUSH TABLES is to lower the probability of a stage where both mysqldump FLUSH TABLES is to lower the probability of a stage where both mysqldump
and most client connections are stalled. Of course, if a second long and most client connections are stalled. Of course, if a second long
update starts between the two FLUSHes, we have that bad stall. update starts between the two FLUSHes, we have that bad stall.
We use the LOCAL option, as we do not want the FLUSH TABLES replicated to
other servers.
*/ */
return return
( mysql_query_with_error_report(mysql_con, 0, ( mysql_query_with_error_report(mysql_con, 0,
((opt_master_data != 0) ? "FLUSH /*!40101 LOCAL */ TABLES") ||
"FLUSH /*!40101 LOCAL */ TABLES" :
"FLUSH TABLES")) ||
mysql_query_with_error_report(mysql_con, 0, mysql_query_with_error_report(mysql_con, 0,
"FLUSH TABLES WITH READ LOCK") ); "FLUSH TABLES WITH READ LOCK") );
} }
...@@ -5656,6 +5798,7 @@ int main(int argc, char **argv) ...@@ -5656,6 +5798,7 @@ int main(int argc, char **argv)
char bin_log_name[FN_REFLEN]; char bin_log_name[FN_REFLEN];
int exit_code; int exit_code;
int consistent_binlog_pos= 0; int consistent_binlog_pos= 0;
int have_mariadb_gtid= 0;
MY_INIT(argv[0]); MY_INIT(argv[0]);
sf_leaking_memory=1; /* don't report memory leaks on early exits */ sf_leaking_memory=1; /* don't report memory leaks on early exits */
...@@ -5696,7 +5839,10 @@ int main(int argc, char **argv) ...@@ -5696,7 +5839,10 @@ int main(int argc, char **argv)
/* Check if the server support multi source */ /* Check if the server support multi source */
if (mysql_get_server_version(mysql) >= 100000) if (mysql_get_server_version(mysql) >= 100000)
{
multi_source= 2; multi_source= 2;
have_mariadb_gtid= 1;
}
if (opt_slave_data && do_stop_slave_sql(mysql)) if (opt_slave_data && do_stop_slave_sql(mysql))
goto err; goto err;
...@@ -5743,9 +5889,11 @@ int main(int argc, char **argv) ...@@ -5743,9 +5889,11 @@ int main(int argc, char **argv)
/* Add 'STOP SLAVE to beginning of dump */ /* Add 'STOP SLAVE to beginning of dump */
if (opt_slave_apply && add_stop_slave()) if (opt_slave_apply && add_stop_slave())
goto err; goto err;
if (opt_master_data && do_show_master_status(mysql, consistent_binlog_pos)) if (opt_master_data && do_show_master_status(mysql, consistent_binlog_pos,
have_mariadb_gtid, opt_use_gtid))
goto err; goto err;
if (opt_slave_data && do_show_slave_status(mysql)) if (opt_slave_data && do_show_slave_status(mysql, opt_use_gtid,
have_mariadb_gtid))
goto err; goto err;
if (opt_single_transaction && do_unlock_tables(mysql)) /* unlock but no commit! */ if (opt_single_transaction && do_unlock_tables(mysql)) /* unlock but no commit! */
goto err; goto err;
......
...@@ -4,18 +4,59 @@ include/master-slave.inc ...@@ -4,18 +4,59 @@ include/master-slave.inc
# New --dump-slave, --apply-slave-statements functionality # New --dump-slave, --apply-slave-statements functionality
# #
use test; use test;
-- SET GLOBAL gtid_slave_pos='';
CHANGE MASTER '' TO MASTER_LOG_FILE='master-bin.000001', MASTER_LOG_POS=BINLOG_START; CHANGE MASTER '' TO MASTER_LOG_FILE='master-bin.000001', MASTER_LOG_POS=BINLOG_START;
STOP ALL SLAVES; STOP ALL SLAVES;
-- SET GLOBAL gtid_slave_pos='';
CHANGE MASTER '' TO MASTER_LOG_FILE='master-bin.000001', MASTER_LOG_POS=BINLOG_START; CHANGE MASTER '' TO MASTER_LOG_FILE='master-bin.000001', MASTER_LOG_POS=BINLOG_START;
START ALL SLAVES; START ALL SLAVES;
STOP ALL SLAVES; STOP ALL SLAVES;
-- SET GLOBAL gtid_slave_pos='';
CHANGE MASTER '' TO MASTER_HOST='127.0.0.1', MASTER_PORT=MASTER_MYPORT, MASTER_LOG_FILE='master-bin.000001', MASTER_LOG_POS=BINLOG_START; CHANGE MASTER '' TO MASTER_HOST='127.0.0.1', MASTER_PORT=MASTER_MYPORT, MASTER_LOG_FILE='master-bin.000001', MASTER_LOG_POS=BINLOG_START;
START ALL SLAVES; START ALL SLAVES;
start slave; start slave;
Warnings: Warnings:
Note 1254 Slave is already running Note 1254 Slave is already running
-- SET GLOBAL gtid_slave_pos='';
CHANGE MASTER '' TO MASTER_LOG_FILE='master-bin.000001', MASTER_LOG_POS=BINLOG_START; CHANGE MASTER '' TO MASTER_LOG_FILE='master-bin.000001', MASTER_LOG_POS=BINLOG_START;
start slave; start slave;
Warnings: Warnings:
Note 1254 Slave is already running Note 1254 Slave is already running
*** Test mysqldump --dump-slave GTID functionality.
SET gtid_seq_no = 1000;
CREATE TABLE t1 (a INT PRIMARY KEY);
DROP TABLE t1;
CREATE TABLE t2 (a INT PRIMARY KEY);
DROP TABLE t2;
1. --dump-slave=1
SET GLOBAL gtid_slave_pos='0-1-1001';
CHANGE MASTER '' TO MASTER_USE_GTID=slave_pos;
-- CHANGE MASTER '' TO MASTER_LOG_FILE='master-bin.000001', MASTER_LOG_POS=BINLOG_START;
2. --dump-slave=2
-- SET GLOBAL gtid_slave_pos='0-1-1001';
-- CHANGE MASTER '' TO MASTER_USE_GTID=slave_pos;
-- CHANGE MASTER '' TO MASTER_LOG_FILE='master-bin.000001', MASTER_LOG_POS=BINLOG_START;
*** Test mysqldump --master-data GTID functionality.
1. --master-data=1
-- CHANGE MASTER TO MASTER_LOG_FILE='slave-bin.000001', MASTER_LOG_POS=BINLOG_START;
CHANGE MASTER TO MASTER_USE_GTID=slave_pos;
SET GLOBAL gtid_slave_pos='0-2-1003';
2. --master-data=2
-- CHANGE MASTER TO MASTER_LOG_FILE='slave-bin.000001', MASTER_LOG_POS=BINLOG_START;
-- CHANGE MASTER TO MASTER_USE_GTID=slave_pos;
-- SET GLOBAL gtid_slave_pos='0-2-1003';
3. --master-data --single-transaction
-- CHANGE MASTER TO MASTER_LOG_FILE='slave-bin.000001', MASTER_LOG_POS=BINLOG_START;
CHANGE MASTER TO MASTER_USE_GTID=slave_pos;
SET GLOBAL gtid_slave_pos='0-2-1003';
include/rpl_end.inc include/rpl_end.inc
...@@ -36,4 +36,53 @@ start slave; ...@@ -36,4 +36,53 @@ start slave;
--exec $MYSQL_DUMP_SLAVE --compact --dump-slave no_such_db --exec $MYSQL_DUMP_SLAVE --compact --dump-slave no_such_db
start slave; start slave;
--echo *** Test mysqldump --dump-slave GTID functionality.
--connection master
SET gtid_seq_no = 1000;
CREATE TABLE t1 (a INT PRIMARY KEY);
DROP TABLE t1;
--sync_slave_with_master
--connection slave
# Inject a local transaction on the slave to check that this is not considered
# for --dump-slave.
CREATE TABLE t2 (a INT PRIMARY KEY);
DROP TABLE t2;
--echo
--echo 1. --dump-slave=1
--echo
--replace_regex /MASTER_LOG_POS=[0-9]+/MASTER_LOG_POS=BINLOG_START/
--exec $MYSQL_DUMP_SLAVE --compact --dump-slave=1 --gtid test
--echo
--echo 2. --dump-slave=2
--echo
--replace_regex /MASTER_LOG_POS=[0-9]+/MASTER_LOG_POS=BINLOG_START/
--exec $MYSQL_DUMP_SLAVE --compact --dump-slave=2 --gtid test
--echo *** Test mysqldump --master-data GTID functionality.
--echo
--echo 1. --master-data=1
--echo
--replace_regex /MASTER_LOG_POS=[0-9]+/MASTER_LOG_POS=BINLOG_START/
--exec $MYSQL_DUMP_SLAVE --compact --master-data=1 --gtid test
--echo
--echo 2. --master-data=2
--echo
--replace_regex /MASTER_LOG_POS=[0-9]+/MASTER_LOG_POS=BINLOG_START/
--exec $MYSQL_DUMP_SLAVE --compact --master-data=2 --gtid test
--echo
--echo 3. --master-data --single-transaction
--echo
--replace_regex /MASTER_LOG_POS=[0-9]+/MASTER_LOG_POS=BINLOG_START/
--exec $MYSQL_DUMP_SLAVE --compact --master-data --single-transaction --gtid test
--source include/rpl_end.inc --source include/rpl_end.inc
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment