Browse Source
MDEV-22929 MariaBackup option to report and/or continue when corruption is encountered
MDEV-22929 MariaBackup option to report and/or continue when corruption is encountered
The new option --log-innodb-page-corruption is introduced. When this option is set, backup is not interrupted if innodb corrupted page is detected. Instead it logs all found corrupted pages in innodb_corrupted_pages file in backup directory and finishes with error. For incremental backup corrupted pages are also copied to .delta file, because we can't do LSN check for such pages during backup, innodb_corrupted_pages will also be created in incremental backup directory. During --prepare, corrupted pages list is read from the file just after redo log is applied, and each page from the list is checked if it is allocated in it's tablespace or not. If it is not allocated, then it is zeroed out, flushed to the tablespace and removed from the list. If all pages are removed from the list, then --prepare is finished successfully and innodb_corrupted_pages file is removed from backup directory. Otherwise --prepare is finished with error message and innodb_corrupted_pages contains the list of the pages, which are detected as corrupted during backup, and are allocated in their tablespaces, what means backup directory contains corrupted innodb pages, and backup can not be considered as consistent. For incremental --prepare corrupted pages from .delta files are applied to the base backup, innodb_corrupted_pages is read from both base in incremental directories, and the same action is proceded for corrupted pages list as for full --prepare. innodb_corrupted_pages file is modified or removed only in base directory. If DDL happens during backup, it is also processed at the end of backup to have correct tablespace names in innodb_corrupted_pages.10.2-wf
15 changed files with 1204 additions and 128 deletions
-
32extra/mariabackup/backup_copy.cc
-
3extra/mariabackup/backup_copy.h
-
32extra/mariabackup/backup_debug.h
-
1extra/mariabackup/encryption_plugin.cc
-
50extra/mariabackup/fil_cur.cc
-
15extra/mariabackup/fil_cur.h
-
26extra/mariabackup/write_filt.cc
-
8extra/mariabackup/write_filt.h
-
422extra/mariabackup/xtrabackup.cc
-
27extra/mariabackup/xtrabackup.h
-
146mysql-test/suite/mariabackup/include/corrupt-page.pl
-
2mysql-test/suite/mariabackup/incremental_ddl_during_backup.test
-
1mysql-test/suite/mariabackup/log_page_corruption.opt
-
141mysql-test/suite/mariabackup/log_page_corruption.result
-
426mysql-test/suite/mariabackup/log_page_corruption.test
@ -0,0 +1,32 @@ |
|||
#pragma once |
|||
#include "my_dbug.h" |
|||
#ifndef DBUG_OFF |
|||
extern char *dbug_mariabackup_get_val(const char *event, const char *key); |
|||
/* |
|||
In debug mode, execute SQL statement that was passed via environment. |
|||
To use this facility, you need to |
|||
|
|||
1. Add code DBUG_EXECUTE_MARIABACKUP_EVENT("my_event_name", key);); |
|||
to the code. key is usually a table name |
|||
2. Set environment variable my_event_name_$key SQL statement you want to execute |
|||
when event occurs, in DBUG_EXECUTE_IF from above. |
|||
In mtr , you can set environment via 'let' statement (do not use $ as the first char |
|||
for the variable) |
|||
3. start mariabackup with --dbug=+d,debug_mariabackup_events |
|||
*/ |
|||
extern void dbug_mariabackup_event( |
|||
const char *event,const char *key); |
|||
#define DBUG_MARIABACKUP_EVENT(A, B) \ |
|||
DBUG_EXECUTE_IF("mariabackup_events", \ |
|||
dbug_mariabackup_event(A,B);); |
|||
#define DBUG_EXECUTE_FOR_KEY(EVENT, KEY, CODE) \ |
|||
DBUG_EXECUTE_IF("mariabackup_inject_code", {\ |
|||
char *dbug_val = dbug_mariabackup_get_val(EVENT, KEY); \ |
|||
if (dbug_val && *dbug_val) CODE \ |
|||
}) |
|||
#else |
|||
#define DBUG_MARIABACKUP_EVENT(A,B) |
|||
#define DBUG_MARIABACKUP_EVENT_LOCK(A,B) |
|||
#define DBUG_EXECUTE_FOR_KEY(EVENT, KEY, CODE) |
|||
#endif |
|||
|
@ -0,0 +1,146 @@ |
|||
use strict; |
|||
use warnings; |
|||
use Fcntl qw(:DEFAULT :seek); |
|||
do "$ENV{MTR_SUITE_DIR}/../innodb/include/crc32.pl"; |
|||
|
|||
sub corrupt_space_page_id { |
|||
my $file_name = shift; |
|||
my @pages_to_corrupt = @_; |
|||
|
|||
my $page_size = $ENV{INNODB_PAGE_SIZE}; |
|||
|
|||
sysopen my $ibd_file, $file_name, O_RDWR || die "Cannot open $file_name\n"; |
|||
sysread($ibd_file, $_, 38) || die "Cannot read $file_name\n"; |
|||
my $space = unpack("x[34]N", $_); |
|||
foreach my $page_no (@pages_to_corrupt) { |
|||
$space += 10; # generate wrong space id |
|||
sysseek($ibd_file, $page_size * $page_no, SEEK_SET) |
|||
|| die "Cannot seek $file_name\n"; |
|||
|
|||
my $head = pack("Nx[18]", $page_no + 10); # generate wrong page number |
|||
my $body = chr(0) x ($page_size - 38 - 8); |
|||
|
|||
# Calculate innodb_checksum_algorithm=crc32 for the unencrypted page. |
|||
# The following bytes are excluded: |
|||
# bytes 0..3 (the checksum is stored there) |
|||
# bytes 26..37 (encryption key version, post-encryption checksum, tablespace id) |
|||
# bytes $page_size-8..$page_size-1 (checksum, LSB of FIL_PAGE_LSN) |
|||
my $polynomial = 0x82f63b78; # CRC-32C |
|||
my $ck = mycrc32($head, 0, $polynomial) ^ mycrc32($body, 0, $polynomial); |
|||
|
|||
my $page= pack("N",$ck).$head.pack("NNN",1,$ck,$space).$body.pack("Nx[4]",$ck); |
|||
die unless syswrite($ibd_file, $page, $page_size) == $page_size; |
|||
} |
|||
close $ibd_file; |
|||
} |
|||
|
|||
sub extend_space { |
|||
my $file_name = shift; |
|||
my $n_pages = shift; |
|||
|
|||
my $page_size = $ENV{INNODB_PAGE_SIZE}; |
|||
my $page; |
|||
|
|||
sysopen my $ibd_file, $file_name, O_RDWR || die "Cannot open $file_name\n"; |
|||
sysread($ibd_file, $page, $page_size) |
|||
|| die "Cannot read $file_name\n"; |
|||
my $size = unpack("N", substr($page, 46, 4)); |
|||
my $packed_new_size = pack("N", $size + $n_pages); |
|||
substr($page, 46, 4, $packed_new_size); |
|||
|
|||
my $head = substr($page, 4, 22); |
|||
my $body = substr($page, 38, $page_size - 38 - 8); |
|||
my $polynomial = 0x82f63b78; # CRC-32C |
|||
my $ck = mycrc32($head, 0, $polynomial) ^ mycrc32($body, 0, $polynomial); |
|||
my $packed_ck = pack("N", $ck); |
|||
substr($page, 0, 4, $packed_ck); |
|||
substr($page, $page_size - 8, 4, $packed_ck); |
|||
|
|||
sysseek($ibd_file, 0, SEEK_SET) |
|||
|| die "Cannot seek $file_name\n"; |
|||
die unless syswrite($ibd_file, $page, $page_size) == $page_size; |
|||
|
|||
sysseek($ibd_file, 0, SEEK_END) |
|||
|| die "Cannot seek $file_name\n"; |
|||
my $pages_size = $page_size*$n_pages; |
|||
my $pages = chr(0) x $pages_size; |
|||
die unless syswrite($ibd_file, $pages, $pages_size) == $pages_size; |
|||
close $ibd_file; |
|||
return $size; |
|||
} |
|||
|
|||
sub die_if_page_is_not_zero { |
|||
my $file_name = shift; |
|||
my @pages_to_check = @_; |
|||
|
|||
no locale; |
|||
my $page_size = $ENV{INNODB_PAGE_SIZE}; |
|||
my $zero_page = chr(0) x $page_size; |
|||
sysopen my $ibd_file, $file_name, O_RDWR || die "Cannot open $file_name\n"; |
|||
foreach my $page_no_to_check (@pages_to_check) { |
|||
sysseek($ibd_file, $page_size*$page_no_to_check, SEEK_SET) || |
|||
die "Cannot seek $file_name\n"; |
|||
sysread($ibd_file, my $read_page, $page_size) || |
|||
die "Cannot read $file_name\n"; |
|||
die "The page $page_no_to_check is not zero-filed in $file_name" |
|||
if ($read_page cmp $zero_page); |
|||
} |
|||
close $ibd_file; |
|||
} |
|||
|
|||
sub print_corrupted_pages_file { |
|||
my $file_in = shift; |
|||
my $file_out = shift; |
|||
open my $fh, '<', $file_in || die $!; |
|||
my $line_number = 0; |
|||
my $space = {}; |
|||
my @spaces; |
|||
while (my $line = <$fh>) { |
|||
++$line_number; |
|||
if ($line_number & 1) { |
|||
my ($name, $id) = split(/ /, $line); |
|||
$space->{name} = $name; |
|||
} |
|||
else { |
|||
$space->{pages} = $line; |
|||
push (@spaces, $space); |
|||
$space = {}; |
|||
} |
|||
} |
|||
close $fh; |
|||
my @sorted_spaces = sort { $a->{name} cmp $b->{name} } @spaces; |
|||
open $fh, '>', $file_out || die $!; |
|||
foreach my $space (@sorted_spaces) { |
|||
print $fh $space->{name}; |
|||
print $fh "\n"; |
|||
print $fh $space->{pages}; |
|||
} |
|||
close $fh; |
|||
} |
|||
|
|||
sub append_corrupted_pages { |
|||
my $file_name = shift; |
|||
my $space_name = shift; |
|||
my $pages = shift; |
|||
open my $fh, '<', $file_name || die $!; |
|||
my $line_number = 0; |
|||
my $space_line; |
|||
while (my $line = <$fh>) { |
|||
++$line_number; |
|||
if ($line_number & 1) { |
|||
my ($name, $id) = split(/ /, $line); |
|||
if ($name eq $space_name) { |
|||
$space_line = $line; |
|||
last; |
|||
} |
|||
} |
|||
} |
|||
close $fh; |
|||
if (not defined $space_line) { |
|||
die "Can't find requested space $space_name in file $file_name"; |
|||
} |
|||
open $fh, '>>', $file_name || die $!; |
|||
print $fh $space_line; |
|||
print $fh "$pages\n"; |
|||
close $fh; |
|||
} |
@ -0,0 +1 @@ |
|||
--innodb-checksum-algorithm=crc32 |
@ -0,0 +1,141 @@ |
|||
######## |
|||
# Test for generating "innodb_corrupted_pages" file during full and |
|||
# incremental backup, including DDL processing |
|||
### |
|||
|
|||
CREATE TABLE t1_corrupted(c INT) ENGINE INNODB; |
|||
CREATE TABLE t2_corrupted(c INT) ENGINE INNODB; |
|||
CREATE TABLE t3(c INT) ENGINE INNODB; |
|||
CREATE TABLE t5_corrupted_to_rename(c INT) ENGINE INNODB; |
|||
CREATE TABLE t6_corrupted_to_drop(c INT) ENGINE INNODB; |
|||
CREATE TABLE t7_corrupted_to_alter(c INT) ENGINE INNODB; |
|||
CREATE TABLE t1_inc_corrupted(c INT) ENGINE INNODB; |
|||
CREATE TABLE t2_inc_corrupted(c INT) ENGINE INNODB; |
|||
CREATE TABLE t3_inc(c INT) ENGINE INNODB; |
|||
CREATE TABLE t5_inc_corrupted_to_rename(c INT) ENGINE INNODB; |
|||
CREATE TABLE t6_inc_corrupted_to_drop(c INT) ENGINE INNODB; |
|||
CREATE TABLE t7_inc_corrupted_to_alter(c INT) ENGINE INNODB; |
|||
INSERT INTO t1_corrupted VALUES (3), (4), (5), (6), (7), (8), (9); |
|||
INSERT INTO t2_corrupted VALUES (3), (4), (5), (6), (7), (8), (9); |
|||
INSERT INTO t3 VALUES (3), (4), (5), (6), (7), (8), (9); |
|||
INSERT INTO t5_corrupted_to_rename VALUES (3), (4), (5), (6), (7), (8), (9); |
|||
INSERT INTO t6_corrupted_to_drop VALUES (3), (4), (5), (6), (7), (8), (9); |
|||
INSERT INTO t7_corrupted_to_alter VALUES (3), (4), (5), (6), (7), (8), (9); |
|||
# Corrupt tables |
|||
# Backup must fail due to page corruption |
|||
FOUND 1 /Database page corruption detected.*/ in backup.log |
|||
# "innodb_corrupted_pages" file must not exist |
|||
# Backup must fail, but "innodb_corrupted_pages" file must be created due to --log-innodb-page-corruption option |
|||
FOUND 1 /Database page corruption detected.*/ in backup.log |
|||
--- "innodb_corrupted_pages" file content: --- |
|||
test/t1_corrupted |
|||
6 8 9 |
|||
test/t2_corrupted |
|||
7 8 10 |
|||
test/t4_corrupted_new |
|||
1 |
|||
test/t5_corrupted_to_rename_renamed |
|||
6 |
|||
test/t7_corrupted_to_alter |
|||
3 |
|||
------ |
|||
INSERT INTO t1_inc_corrupted VALUES (3), (4), (5), (6), (7), (8), (9); |
|||
INSERT INTO t2_inc_corrupted VALUES (3), (4), (5), (6), (7), (8), (9); |
|||
INSERT INTO t3_inc VALUES (3), (4), (5), (6), (7), (8), (9); |
|||
# Backup must fail, but "innodb_corrupted_pages" file must be created due to --log-innodb-page-corruption option |
|||
--- "innodb_corrupted_pages" file content: --- |
|||
test/t1_corrupted |
|||
6 8 9 |
|||
test/t1_inc_corrupted |
|||
6 8 9 |
|||
test/t2_corrupted |
|||
7 8 10 |
|||
test/t2_inc_corrupted |
|||
7 8 10 |
|||
test/t4_inc_corrupted_new |
|||
1 |
|||
test/t5_corrupted_to_rename_renamed |
|||
6 |
|||
test/t5_inc_corrupted_to_rename_renamed |
|||
6 |
|||
test/t7_inc_corrupted_to_alter |
|||
3 |
|||
------ |
|||
# Check if corrupted pages were copied to delta files, and non-corrupted pages are not copied. |
|||
DROP TABLE t1_corrupted; |
|||
DROP TABLE t2_corrupted; |
|||
DROP TABLE t4_corrupted_new; |
|||
DROP TABLE t5_corrupted_to_rename_renamed; |
|||
DROP TABLE t7_corrupted_to_alter; |
|||
DROP TABLE t1_inc_corrupted; |
|||
DROP TABLE t2_inc_corrupted; |
|||
DROP TABLE t4_inc_corrupted_new; |
|||
DROP TABLE t5_inc_corrupted_to_rename_renamed; |
|||
DROP TABLE t7_inc_corrupted_to_alter; |
|||
|
|||
######## |
|||
# Test for --prepare with "innodb_corrupted_pages" file |
|||
### |
|||
|
|||
# Extend some tablespace and corrupt extended pages for full backup |
|||
# Full backup with --log-innodb-page-corruption |
|||
--- "innodb_corrupted_pages" file content: --- |
|||
test/t3 |
|||
6 8 |
|||
------ |
|||
# Extend some tablespace and corrupt extended pages for incremental backup |
|||
# Incremental backup --log-innodb-page-corruption |
|||
--- "innodb_corrupted_pages" file content: --- |
|||
test/t3 |
|||
6 8 |
|||
test/t3_inc |
|||
6 8 |
|||
------ |
|||
# Full backup prepare |
|||
# "innodb_corrupted_pages" file must not exist after successful prepare |
|||
FOUND 1 /was successfuly fixed.*/ in backup.log |
|||
# Check that fixed pages are zero-filled |
|||
# Incremental backup prepare |
|||
# "innodb_corrupted_pages" file must not exist after successful prepare |
|||
# do not remove "innodb_corrupted_pages" in incremental dir |
|||
FOUND 1 /was successfuly fixed.*/ in backup.log |
|||
# Check that fixed pages are zero-filled |
|||
# shutdown server |
|||
# remove datadir |
|||
# xtrabackup move back |
|||
# restart server |
|||
SELECT * FROM t3; |
|||
c |
|||
3 |
|||
4 |
|||
5 |
|||
6 |
|||
7 |
|||
8 |
|||
9 |
|||
SELECT * FROM t3_inc; |
|||
c |
|||
3 |
|||
4 |
|||
5 |
|||
6 |
|||
7 |
|||
8 |
|||
9 |
|||
# Test the case when not all corrupted pages are fixed |
|||
|
|||
# Add some fake corrupted pages |
|||
# Full backup prepare |
|||
FOUND 1 /Error: corrupted page.*/ in backup.log |
|||
--- "innodb_corrupted_pages" file content: --- |
|||
test/t3 |
|||
3 |
|||
------ |
|||
# Incremental backup prepare |
|||
FOUND 1 /Error: corrupted page.*/ in backup.log |
|||
--- "innodb_corrupted_pages" file content: --- |
|||
test/t3 |
|||
3 |
|||
------ |
|||
DROP TABLE t3; |
|||
DROP TABLE t3_inc; |
@ -0,0 +1,426 @@ |
|||
--source include/have_debug.inc |
|||
|
|||
--echo ######## |
|||
--echo # Test for generating "innodb_corrupted_pages" file during full and |
|||
--echo # incremental backup, including DDL processing |
|||
--echo ### |
|||
--echo |
|||
|
|||
CREATE TABLE t1_corrupted(c INT) ENGINE INNODB; |
|||
CREATE TABLE t2_corrupted(c INT) ENGINE INNODB; |
|||
CREATE TABLE t3(c INT) ENGINE INNODB; |
|||
CREATE TABLE t5_corrupted_to_rename(c INT) ENGINE INNODB; |
|||
CREATE TABLE t6_corrupted_to_drop(c INT) ENGINE INNODB; |
|||
CREATE TABLE t7_corrupted_to_alter(c INT) ENGINE INNODB; |
|||
|
|||
CREATE TABLE t1_inc_corrupted(c INT) ENGINE INNODB; |
|||
CREATE TABLE t2_inc_corrupted(c INT) ENGINE INNODB; |
|||
CREATE TABLE t3_inc(c INT) ENGINE INNODB; |
|||
CREATE TABLE t5_inc_corrupted_to_rename(c INT) ENGINE INNODB; |
|||
CREATE TABLE t6_inc_corrupted_to_drop(c INT) ENGINE INNODB; |
|||
CREATE TABLE t7_inc_corrupted_to_alter(c INT) ENGINE INNODB; |
|||
|
|||
# Fill tables with several pages |
|||
INSERT INTO t1_corrupted VALUES (3), (4), (5), (6), (7), (8), (9); |
|||
INSERT INTO t2_corrupted VALUES (3), (4), (5), (6), (7), (8), (9); |
|||
INSERT INTO t3 VALUES (3), (4), (5), (6), (7), (8), (9); |
|||
INSERT INTO t5_corrupted_to_rename VALUES (3), (4), (5), (6), (7), (8), (9); |
|||
INSERT INTO t6_corrupted_to_drop VALUES (3), (4), (5), (6), (7), (8), (9); |
|||
INSERT INTO t7_corrupted_to_alter VALUES (3), (4), (5), (6), (7), (8), (9); |
|||
|
|||
--let MYSQLD_DATADIR=`select @@datadir` |
|||
--let INNODB_PAGE_SIZE=`select @@innodb_page_size` |
|||
|
|||
--source include/shutdown_mysqld.inc |
|||
--echo # Corrupt tables |
|||
perl; |
|||
do "$ENV{MTR_SUITE_DIR}/include/corrupt-page.pl"; |
|||
my $schema = "$ENV{MYSQLD_DATADIR}/test"; |
|||
|
|||
my $last_page_no = extend_space("$schema/t1_corrupted.ibd", 4); |
|||
corrupt_space_page_id("$schema/t1_corrupted.ibd", |
|||
$last_page_no, $last_page_no + 2, $last_page_no + 3); |
|||
|
|||
$last_page_no = extend_space("$schema/t2_corrupted.ibd", 5); |
|||
corrupt_space_page_id("$schema/t2_corrupted.ibd", |
|||
$last_page_no + 1, $last_page_no + 2, $last_page_no + 4); |
|||
|
|||
$last_page_no = extend_space("$schema/t5_corrupted_to_rename.ibd", 1); |
|||
corrupt_space_page_id("$schema/t5_corrupted_to_rename.ibd", $last_page_no); |
|||
|
|||
$last_page_no = extend_space("$schema/t6_corrupted_to_drop.ibd", ); |
|||
corrupt_space_page_id("$schema/t6_corrupted_to_drop.ibd", $last_page_no); |
|||
EOF |
|||
--source include/start_mysqld.inc |
|||
|
|||
--let targetdir=$MYSQLTEST_VARDIR/tmp/backup |
|||
--let $backuplog=$MYSQLTEST_VARDIR/tmp/backup.log |
|||
--let corrupted_pages_file = $targetdir/innodb_corrupted_pages |
|||
--let corrupted_pages_file_filt = $MYSQLTEST_VARDIR/tmp/innodb_corrupted_pages_filt |
|||
--let perl_result_file=$MYSQLTEST_VARDIR/tmp/perl_result |
|||
|
|||
--echo # Backup must fail due to page corruption |
|||
--disable_result_log |
|||
--error 1 |
|||
exec $XTRABACKUP --defaults-file=$MYSQLTEST_VARDIR/my.cnf --backup --target-dir=$targetdir > $backuplog; |
|||
--enable_result_log |
|||
|
|||
--let SEARCH_PATTERN=Database page corruption detected.* |
|||
--let SEARCH_FILE=$backuplog |
|||
--source include/search_pattern_in_file.inc |
|||
--echo # "innodb_corrupted_pages" file must not exist |
|||
--error 1 |
|||
--file_exists $corrupted_pages_file |
|||
--rmdir $targetdir |
|||
|
|||
--let after_load_tablespaces=CREATE TABLE test.t4_corrupted_new ENGINE=INNODB SELECT UUID() from test.seq_1_to_10 |
|||
--let add_corrupted_page_for_test_t4_corrupted_new=1 |
|||
--let after_copy_test_t5_corrupted_to_rename=RENAME TABLE test.t5_corrupted_to_rename TO test.t5_corrupted_to_rename_renamed |
|||
--let after_copy_test_t6_corrupted_to_drop=DROP TABLE test.t6_corrupted_to_drop |
|||
--let after_copy_test_t7_corrupted_to_alter=ALTER TABLE test.t7_corrupted_to_alter ADD COLUMN (d INT) |
|||
--let add_corrupted_page_for_test_t7_corrupted_to_alter=3 |
|||
|
|||
--echo # Backup must fail, but "innodb_corrupted_pages" file must be created due to --log-innodb-page-corruption option |
|||
--disable_result_log |
|||
--error 1 |
|||
--exec $XTRABACKUP --defaults-file=$MYSQLTEST_VARDIR/my.cnf --backup --log-innodb-page-corruption --target-dir=$targetdir --dbug=+d,mariabackup_events,mariabackup_inject_code > $backuplog |
|||
--enable_result_log |
|||
|
|||
--let SEARCH_PATTERN=Database page corruption detected.* |
|||
--let SEARCH_FILE=$backuplog |
|||
--source include/search_pattern_in_file.inc |
|||
--echo --- "innodb_corrupted_pages" file content: --- |
|||
perl; |
|||
do "$ENV{MTR_SUITE_DIR}/include/corrupt-page.pl"; |
|||
print_corrupted_pages_file($ENV{corrupted_pages_file}, |
|||
$ENV{corrupted_pages_file_filt}); |
|||
EOF |
|||
--cat_file $corrupted_pages_file_filt |
|||
--echo ------ |
|||
--let after_load_tablespaces= |
|||
--let add_corrupted_page_for_test_t4_corrupted_new= |
|||
--let after_copy_test_t5_corrupted_to_rename= |
|||
--let after_copy_test_t6_corrupted_to_drop= |
|||
--let after_copy_test_t7_corrupted_to_alter= |
|||
--let add_corrupted_page_for_test_t7_corrupted_to_alter= |
|||
# Fill tables for incremental backup with several pages |
|||
INSERT INTO t1_inc_corrupted VALUES (3), (4), (5), (6), (7), (8), (9); |
|||
INSERT INTO t2_inc_corrupted VALUES (3), (4), (5), (6), (7), (8), (9); |
|||
INSERT INTO t3_inc VALUES (3), (4), (5), (6), (7), (8), (9); |
|||
|
|||
--source include/shutdown_mysqld.inc |
|||
perl; |
|||
do "$ENV{MTR_SUITE_DIR}/include/corrupt-page.pl"; |
|||
my $schema="$ENV{MYSQLD_DATADIR}/test"; |
|||
|
|||
open(my $fh, '>', $ENV{perl_result_file}) or die $!; |
|||
|
|||
my $last_page_no = extend_space("$schema/t1_inc_corrupted.ibd", 4); |
|||
corrupt_space_page_id("$schema/t1_inc_corrupted.ibd", |
|||
$last_page_no, $last_page_no + 2, $last_page_no + 3); |
|||
print $fh "$last_page_no\n"; |
|||
|
|||
$last_page_no = extend_space("$schema/t2_inc_corrupted.ibd", 5); |
|||
corrupt_space_page_id("$schema/t2_inc_corrupted.ibd", |
|||
$last_page_no + 1, $last_page_no + 2, $last_page_no + 4); |
|||
print $fh "$last_page_no\n"; |
|||
|
|||
$last_page_no = extend_space("$schema/t5_inc_corrupted_to_rename.ibd", 1); |
|||
corrupt_space_page_id("$schema/t5_inc_corrupted_to_rename.ibd", $last_page_no); |
|||
print $fh "$last_page_no\n"; |
|||
|
|||
$last_page_no = extend_space("$schema/t6_inc_corrupted_to_drop.ibd", ); |
|||
corrupt_space_page_id("$schema/t6_inc_corrupted_to_drop.ibd", $last_page_no); |
|||
|
|||
close $fh; |
|||
EOF |
|||
--source include/start_mysqld.inc |
|||
|
|||
--let incdir=$MYSQLTEST_VARDIR/tmp/backup_inc |
|||
|
|||
--let after_load_tablespaces=CREATE TABLE test.t4_inc_corrupted_new ENGINE=INNODB SELECT UUID() from test.seq_1_to_10 |
|||
--let add_corrupted_page_for_test_t4_inc_corrupted_new=1 |
|||
--let after_copy_test_t5_inc_corrupted_to_rename=RENAME TABLE test.t5_inc_corrupted_to_rename TO test.t5_inc_corrupted_to_rename_renamed |
|||
--let after_copy_test_t6_inc_corrupted_to_drop=DROP TABLE test.t6_inc_corrupted_to_drop |
|||
--let after_copy_test_t7_inc_corrupted_to_alter=ALTER TABLE test.t7_inc_corrupted_to_alter ADD COLUMN (d INT) |
|||
--let add_corrupted_page_for_test_t7_inc_corrupted_to_alter=3 |
|||
|
|||
--echo # Backup must fail, but "innodb_corrupted_pages" file must be created due to --log-innodb-page-corruption option |
|||
--disable_result_log |
|||
--error 1 |
|||
--exec $XTRABACKUP --defaults-file=$MYSQLTEST_VARDIR/my.cnf --backup --log-innodb-page-corruption --target-dir=$incdir --incremental-basedir=$targetdir --dbug=+d,mariabackup_events,mariabackup_inject_code > $backuplog |
|||
--disable_result_log |
|||
|
|||
--let after_load_tablespaces= |
|||
--let add_corrupted_page_for_test_t4_inc_corrupted_new= |
|||
--let after_copy_test_t5_inc_corrupted_to_rename= |
|||
--let after_copy_test_t6_inc_corrupted_to_drop= |
|||
--let after_copy_test_t7_inc_corrupted_to_alter= |
|||
--let add_corrupted_page_for_test_t7_inc_corrupted_to_alter= |
|||
|
|||
--let SEARCH_PATTERN=Database page corruption detected.* |
|||
--let SEARCH_FILE=$backuplog |
|||
--source include/search_pattern_in_file.inc |
|||
--let corrupted_pages_file = $incdir/innodb_corrupted_pages |
|||
--echo --- "innodb_corrupted_pages" file content: --- |
|||
perl; |
|||
do "$ENV{MTR_SUITE_DIR}/include/corrupt-page.pl"; |
|||
print_corrupted_pages_file($ENV{corrupted_pages_file}, |
|||
$ENV{corrupted_pages_file_filt}); |
|||
EOF |
|||
--cat_file $corrupted_pages_file_filt |
|||
--echo ------ |
|||
|
|||
--echo # Check if corrupted pages were copied to delta files, and non-corrupted pages are not copied. |
|||
perl; |
|||
use strict; |
|||
use warnings; |
|||
my $schema = "$ENV{incdir}/test"; |
|||
|
|||
open(my $fh, '<', $ENV{perl_result_file}) or die $!; |
|||
|
|||
my $last_page_no = <$fh>; |
|||
die_if_no_pages("$schema/t1_corrupted.ibd.delta", |
|||
$last_page_no, $last_page_no + 2, $last_page_no + 3); |
|||
|
|||
$last_page_no = <$fh>; |
|||
die_if_no_pages("$schema/t2_corrupted.ibd.delta", |
|||
$last_page_no + 1, $last_page_no + 2, $last_page_no + 4); |
|||
|
|||
$last_page_no = <$fh>; |
|||
die_if_no_pages("$schema/t5_corrupted_to_rename_renamed.ibd.delta", |
|||
$last_page_no); |
|||
|
|||
close $fh; |
|||
|
|||
die_if_not_empty("$schema/t3.ibd.delta"); |
|||
|
|||
sub read_first_page_from_delta { |
|||
my $file_name = shift; |
|||
my $pages_count = shift; |
|||
|
|||
open my $file, '<:raw', $file_name || die "Cannot open $file_name\n"; |
|||
read $file, my $buffer, $pages_count*4 || die "Cannot read $file_name\n"; |
|||
close $file; |
|||
|
|||
return unpack("N[$pages_count]", $buffer); |
|||
} |
|||
|
|||
sub die_if_no_pages { |
|||
my $file_name = shift; |
|||
my @check_pages = @_; |
|||
my @read_pages = |
|||
read_first_page_from_delta($file_name, scalar(@check_pages) + 1); |
|||
for (my $i = 1; $i < @check_pages + 1; ++$i) { |
|||
my $check_page_no = $check_pages[$i - 1]; |
|||
die "Corrupted page $check_page_no was not copied to $file_name." |
|||
if ($i >= @read_pages || $read_pages[$i] != $check_page_no); |
|||
} |
|||
} |
|||
|
|||
sub die_if_not_empty { |
|||
my $file_name = shift; |
|||
my ($magic, $full) = read_first_page_from_delta($file_name, 2); |
|||
die "Delta $file_name must be empty." |
|||
if ($full != 0xFFFFFFFF); |
|||
} |
|||
EOF |
|||
--rmdir $incdir |
|||
--rmdir $targetdir |
|||
|
|||
DROP TABLE t1_corrupted; |
|||
DROP TABLE t2_corrupted; |
|||
DROP TABLE t4_corrupted_new; |
|||
DROP TABLE t5_corrupted_to_rename_renamed; |
|||
DROP TABLE t7_corrupted_to_alter; |
|||
DROP TABLE t1_inc_corrupted; |
|||
DROP TABLE t2_inc_corrupted; |
|||
DROP TABLE t4_inc_corrupted_new; |
|||
DROP TABLE t5_inc_corrupted_to_rename_renamed; |
|||
DROP TABLE t7_inc_corrupted_to_alter; |
|||
|
|||
--echo |
|||
--echo ######## |
|||
--echo # Test for --prepare with "innodb_corrupted_pages" file |
|||
--echo ### |
|||
--echo |
|||
|
|||
--echo # Extend some tablespace and corrupt extended pages for full backup |
|||
--source include/shutdown_mysqld.inc |
|||
perl; |
|||
do "$ENV{MTR_SUITE_DIR}/include/corrupt-page.pl"; |
|||
my $schema="$ENV{MYSQLD_DATADIR}/test"; |
|||
my $last_page_no = extend_space("$schema/t3.ibd", 3); |
|||
corrupt_space_page_id("$schema/t3.ibd", $last_page_no, $last_page_no + 2); |
|||
open(my $fh, '>', $ENV{perl_result_file}) or die $!; |
|||
print $fh "$last_page_no\n"; |
|||
close $fh; |
|||
EOF |
|||
--source include/start_mysqld.inc |
|||
|
|||
--echo # Full backup with --log-innodb-page-corruption |
|||
--disable_result_log |
|||
--error 1 |
|||
--exec $XTRABACKUP --defaults-file=$MYSQLTEST_VARDIR/my.cnf --backup --log-innodb-page-corruption --target-dir=$targetdir |
|||
--enable_result_log |
|||
--let corrupted_pages_file = $targetdir/innodb_corrupted_pages |
|||
--echo --- "innodb_corrupted_pages" file content: --- |
|||
perl; |
|||
do "$ENV{MTR_SUITE_DIR}/include/corrupt-page.pl"; |
|||
print_corrupted_pages_file($ENV{corrupted_pages_file}, |
|||
$ENV{corrupted_pages_file_filt}); |
|||
EOF |
|||
--cat_file $corrupted_pages_file_filt |
|||
--echo ------ |
|||
|
|||
--echo # Extend some tablespace and corrupt extended pages for incremental backup |
|||
--source include/shutdown_mysqld.inc |
|||
perl; |
|||
do "$ENV{MTR_SUITE_DIR}/include/corrupt-page.pl"; |
|||
my $schema="$ENV{MYSQLD_DATADIR}/test"; |
|||
my $last_page_no = extend_space("$schema/t3_inc.ibd", 3); |
|||
corrupt_space_page_id("$schema/t3_inc.ibd", $last_page_no, $last_page_no + 2); |
|||
open(my $fh, '>>', $ENV{perl_result_file}) or die $!; |
|||
print $fh "$last_page_no"; |
|||
close $fh; |
|||
EOF |
|||
--source include/start_mysqld.inc |
|||
|
|||
--echo # Incremental backup --log-innodb-page-corruption |
|||
--disable_result_log |
|||
--error 1 |
|||
--exec $XTRABACKUP --defaults-file=$MYSQLTEST_VARDIR/my.cnf --backup --log-innodb-page-corruption --target-dir=$incdir --incremental-basedir=$targetdir --dbug=+d,mariabackup_events,mariabackup_inject_code > $backuplog |
|||
--disable_result_log |
|||
--let corrupted_pages_file = $incdir/innodb_corrupted_pages |
|||
--echo --- "innodb_corrupted_pages" file content: --- |
|||
perl; |
|||
do "$ENV{MTR_SUITE_DIR}/include/corrupt-page.pl"; |
|||
print_corrupted_pages_file($ENV{corrupted_pages_file}, |
|||
$ENV{corrupted_pages_file_filt}); |
|||
EOF |
|||
--cat_file $corrupted_pages_file_filt |
|||
--echo ------ |
|||
|
|||
--let targetdir2=$targetdir-2 |
|||
--let incdir2=$incdir-2 |
|||
perl; |
|||
use lib "lib"; |
|||
use My::Handles { suppress_init_messages => 1 }; |
|||
use My::File::Path; |
|||
copytree($ENV{'targetdir'}, $ENV{'targetdir2'}); |
|||
copytree($ENV{'incdir'}, $ENV{'incdir2'}); |
|||
EOF |
|||
|
|||
--echo # Full backup prepare |
|||
--disable_result_log |
|||
exec $XTRABACKUP --prepare --target-dir=$targetdir > $backuplog; |
|||
--enable_result_log |
|||
|
|||
--echo # "innodb_corrupted_pages" file must not exist after successful prepare |
|||
--error 1 |
|||
--file_exists $targetdir/innodb_corrupted_pages |
|||
--let SEARCH_PATTERN=was successfuly fixed.* |
|||
--let SEARCH_FILE=$backuplog |
|||
--source include/search_pattern_in_file.inc |
|||
|
|||
--echo # Check that fixed pages are zero-filled |
|||
perl; |
|||
do "$ENV{MTR_SUITE_DIR}/include/corrupt-page.pl"; |
|||
open(my $fh, '<', $ENV{perl_result_file}) or die $!; |
|||
my $last_page_no = <$fh>; |
|||
close $fh; |
|||
my $schema = "$ENV{targetdir}/test"; |
|||
die_if_page_is_not_zero("$schema/t3.ibd", $last_page_no, $last_page_no + 2); |
|||
EOF |
|||
|
|||
--echo # Incremental backup prepare |
|||
--disable_result_log |
|||
exec $XTRABACKUP --prepare --target-dir=$targetdir --incremental-dir=$incdir > $backuplog; |
|||
--enable_result_log |
|||
|
|||
--echo # "innodb_corrupted_pages" file must not exist after successful prepare |
|||
--error 1 |
|||
--file_exists $targetdir/innodb_corrupted_pages |
|||
--echo # do not remove "innodb_corrupted_pages" in incremental dir |
|||
--file_exists $incdir/innodb_corrupted_pages |
|||
--let SEARCH_PATTERN=was successfuly fixed.* |
|||
--let SEARCH_FILE=$backuplog |
|||
--source include/search_pattern_in_file.inc |
|||
|
|||
--echo # Check that fixed pages are zero-filled |
|||
perl; |
|||
do "$ENV{MTR_SUITE_DIR}/include/corrupt-page.pl"; |
|||
open(my $fh, '<', $ENV{perl_result_file}) or die $!; |
|||
my $last_page_no_full = <$fh>; |
|||
my $last_page_no_inc = <$fh>; |
|||
close $fh; |
|||
my $schema = "$ENV{targetdir}/test"; |
|||
die_if_page_is_not_zero("$schema/t3.ibd", |
|||
$last_page_no_full, $last_page_no_full + 2); |
|||
die_if_page_is_not_zero("$schema/t3_inc.ibd", |
|||
$last_page_no_inc, $last_page_no_inc + 2); |
|||
EOF |
|||
|
|||
--source include/restart_and_restore.inc |
|||
|
|||
SELECT * FROM t3; |
|||
SELECT * FROM t3_inc; |
|||
|
|||
--echo # Test the case when not all corrupted pages are fixed |
|||
--echo |
|||
--echo # Add some fake corrupted pages |
|||
perl; |
|||
do "$ENV{MTR_SUITE_DIR}/include/corrupt-page.pl"; |
|||
append_corrupted_pages( |
|||
"$ENV{targetdir2}/innodb_corrupted_pages", 'test/t3', '3 4'); |
|||
append_corrupted_pages( |
|||
"$ENV{incdir2}/innodb_corrupted_pages", 'test/t3_inc', '4 5'); |
|||
EOF |
|||
|
|||
--echo # Full backup prepare |
|||
--disable_result_log |
|||
--error 1 |
|||
exec $XTRABACKUP --prepare --target-dir=$targetdir2 > $backuplog; |
|||
--enable_result_log |
|||
|
|||
--let SEARCH_PATTERN=Error: corrupted page.* |
|||
--let SEARCH_FILE=$backuplog |
|||
--source include/search_pattern_in_file.inc |
|||
--let corrupted_pages_file = $targetdir2/innodb_corrupted_pages |
|||
--echo --- "innodb_corrupted_pages" file content: --- |
|||
perl; |
|||
do "$ENV{MTR_SUITE_DIR}/include/corrupt-page.pl"; |
|||
print_corrupted_pages_file($ENV{corrupted_pages_file}, |
|||
$ENV{corrupted_pages_file_filt}); |
|||
EOF |
|||
--cat_file $corrupted_pages_file_filt |
|||
--echo ------ |
|||
|
|||
--echo # Incremental backup prepare |
|||
--disable_result_log |
|||
--error 1 |
|||
exec $XTRABACKUP --prepare --target-dir=$targetdir2 --incremental-dir=$incdir2 > $backuplog; |
|||
--enable_result_log |
|||
|
|||
--let SEARCH_PATTERN=Error: corrupted page.* |
|||
--let SEARCH_FILE=$backuplog |
|||
--source include/search_pattern_in_file.inc |
|||
--let corrupted_pages_file = $targetdir2/innodb_corrupted_pages |
|||
--echo --- "innodb_corrupted_pages" file content: --- |
|||
perl; |
|||
do "$ENV{MTR_SUITE_DIR}/include/corrupt-page.pl"; |
|||
print_corrupted_pages_file($ENV{corrupted_pages_file}, |
|||
$ENV{corrupted_pages_file_filt}); |
|||
EOF |
|||
--cat_file $corrupted_pages_file_filt |
|||
--echo ------ |
|||
|
|||
DROP TABLE t3; |
|||
DROP TABLE t3_inc; |
|||
--remove_file $backuplog |
|||
--remove_file $perl_result_file |
|||
--remove_file $corrupted_pages_file_filt |
|||
--rmdir $targetdir |
|||
--rmdir $targetdir2 |
|||
--rmdir $incdir |
|||
--rmdir $incdir2 |
Write
Preview
Loading…
Cancel
Save
Reference in new issue