diff options
author | Robert Haas <rhaas@postgresql.org> | 2020-04-03 14:59:47 -0400 |
---|---|---|
committer | Robert Haas <rhaas@postgresql.org> | 2020-04-03 15:05:59 -0400 |
commit | 0d8c9c1210c44b36ec2efcb223a1dfbe897a3661 (patch) | |
tree | af5225aa493720c40a8d6142f043dde444a131af /src/bin/pg_validatebackup/t | |
parent | ce77abe63cfc85fb0bc236deb2cc34ae35cb5324 (diff) |
Generate backup manifests for base backups, and validate them.
A manifest is a JSON document which includes (1) the file name, size,
last modification time, and an optional checksum for each file backed
up, (2) timelines and LSNs for whatever WAL will need to be replayed
to make the backup consistent, and (3) a checksum for the manifest
itself. By default, we use CRC-32C when checksumming data files,
because we are trying to detect corruption and user error, not foil an
adversary. However, pg_basebackup and the server-side BASE_BACKUP
command now have options to select a different algorithm, so users
wanting a cryptographic hash function can select SHA-224, SHA-256,
SHA-384, or SHA-512. Users not wanting file checksums at all can
disable them, or disable generating of the backup manifest altogether.
Using a cryptographic hash function in place of CRC-32C consumes
significantly more CPU cycles, which may slow down backups in some
cases.
A new tool called pg_validatebackup can validate a backup against the
manifest. If no checksums are present, it can still check that the
right files exist and that they have the expected sizes. If checksums
are present, it can also verify that each file has the expected
checksum. Additionally, it calls pg_waldump to verify that the
expected WAL files are present and parseable. Only plain format
backups can be validated directly, but tar format backups can be
validated after extracting them.
Robert Haas, with help, ideas, review, and testing from David Steele,
Stephen Frost, Andrew Dunstan, Rushabh Lathia, Suraj Kharage, Tushar
Ahuja, Rajkumar Raghuwanshi, Mark Dilger, Davinder Singh, Jeevan
Chalke, Amit Kapila, Andres Freund, and Noah Misch.
Discussion: http://postgr.es/m/CA+TgmoZV8dw1H2bzZ9xkKwdrk8+XYa+DC9H=F7heO2zna5T6qg@mail.gmail.com
Diffstat (limited to 'src/bin/pg_validatebackup/t')
-rw-r--r-- | src/bin/pg_validatebackup/t/001_basic.pl | 30 | ||||
-rw-r--r-- | src/bin/pg_validatebackup/t/002_algorithm.pl | 58 | ||||
-rw-r--r-- | src/bin/pg_validatebackup/t/003_corruption.pl | 251 | ||||
-rw-r--r-- | src/bin/pg_validatebackup/t/004_options.pl | 89 | ||||
-rw-r--r-- | src/bin/pg_validatebackup/t/005_bad_manifest.pl | 201 | ||||
-rw-r--r-- | src/bin/pg_validatebackup/t/006_encoding.pl | 27 | ||||
-rw-r--r-- | src/bin/pg_validatebackup/t/007_wal.pl | 55 |
7 files changed, 711 insertions, 0 deletions
diff --git a/src/bin/pg_validatebackup/t/001_basic.pl b/src/bin/pg_validatebackup/t/001_basic.pl new file mode 100644 index 00000000000..6d4b8ea01a6 --- /dev/null +++ b/src/bin/pg_validatebackup/t/001_basic.pl @@ -0,0 +1,30 @@ +use strict; +use warnings; +use TestLib; +use Test::More tests => 16; + +my $tempdir = TestLib::tempdir; + +program_help_ok('pg_validatebackup'); +program_version_ok('pg_validatebackup'); +program_options_handling_ok('pg_validatebackup'); + +command_fails_like(['pg_validatebackup'], + qr/no backup directory specified/, + 'target directory must be specified'); +command_fails_like(['pg_validatebackup', $tempdir], + qr/could not open file.*\/backup_manifest\"/, + 'pg_validatebackup requires a manifest'); +command_fails_like(['pg_validatebackup', $tempdir, $tempdir], + qr/too many command-line arguments/, + 'multiple target directories not allowed'); + +# create fake manifest file +open(my $fh, '>', "$tempdir/backup_manifest") || die "open: $!"; +close($fh); + +# but then try to use an alternate, nonexisting manifest +command_fails_like(['pg_validatebackup', '-m', "$tempdir/not_the_manifest", + $tempdir], + qr/could not open file.*\/not_the_manifest\"/, + 'pg_validatebackup respects -m flag'); diff --git a/src/bin/pg_validatebackup/t/002_algorithm.pl b/src/bin/pg_validatebackup/t/002_algorithm.pl new file mode 100644 index 00000000000..98871e12a5e --- /dev/null +++ b/src/bin/pg_validatebackup/t/002_algorithm.pl @@ -0,0 +1,58 @@ +# Verify that we can take and validate backups with various checksum types. + +use strict; +use warnings; +use Cwd; +use Config; +use File::Path qw(rmtree); +use PostgresNode; +use TestLib; +use Test::More tests => 19; + +my $master = get_new_node('master'); +$master->init(allows_streaming => 1); +$master->start; + +for my $algorithm (qw(bogus none crc32c sha224 sha256 sha384 sha512)) +{ + my $backup_path = $master->backup_dir . '/' . $algorithm; + my @backup = ('pg_basebackup', '-D', $backup_path, + '--manifest-checksums', $algorithm, + '--no-sync'); + my @validate = ('pg_validatebackup', '-e', $backup_path); + + # A backup with a bogus algorithm should fail. + if ($algorithm eq 'bogus') + { + $master->command_fails(\@backup, + "backup fails with algorithm \"$algorithm\""); + next; + } + + # A backup with a valid algorithm should work. + $master->command_ok(\@backup, "backup ok with algorithm \"$algorithm\""); + + # We expect each real checksum algorithm to be mentioned on every line of + # the backup manifest file except the first and last; for simplicity, we + # just check that it shows up lots of times. When the checksum algorithm + # is none, we just check that the manifest exists. + if ($algorithm eq 'none') + { + ok(-f "$backup_path/backup_manifest", "backup manifest exists"); + } + else + { + my $manifest = slurp_file("$backup_path/backup_manifest"); + my $count_of_algorithm_in_manifest = + (() = $manifest =~ /$algorithm/mig); + cmp_ok($count_of_algorithm_in_manifest, '>', 100, + "$algorithm is mentioned many times in the manifest"); + } + + # Make sure that it validates OK. + $master->command_ok(\@validate, + "validate backup with algorithm \"$algorithm\""); + + # Remove backup immediately to save disk space. + rmtree($backup_path); +} diff --git a/src/bin/pg_validatebackup/t/003_corruption.pl b/src/bin/pg_validatebackup/t/003_corruption.pl new file mode 100644 index 00000000000..6ad29a031f8 --- /dev/null +++ b/src/bin/pg_validatebackup/t/003_corruption.pl @@ -0,0 +1,251 @@ +# Verify that various forms of corruption are detected by pg_validatebackup. + +use strict; +use warnings; +use Cwd; +use Config; +use File::Path qw(rmtree); +use PostgresNode; +use TestLib; +use Test::More tests => 44; + +my $master = get_new_node('master'); +$master->init(allows_streaming => 1); +$master->start; + +# Include a user-defined tablespace in the hopes of detecting problems in that +# area. +my $source_ts_path = TestLib::tempdir; +$master->safe_psql('postgres', <<EOM); +CREATE TABLE x1 (a int); +INSERT INTO x1 VALUES (111); +CREATE TABLESPACE ts1 LOCATION '$source_ts_path'; +CREATE TABLE x2 (a int) TABLESPACE ts1; +INSERT INTO x1 VALUES (222); +EOM + +my @scenario = ( + { + 'name' => 'extra_file', + 'mutilate' => \&mutilate_extra_file, + 'fails_like' => + qr/extra_file.*present on disk but not in the manifest/ + }, + { + 'name' => 'extra_tablespace_file', + 'mutilate' => \&mutilate_extra_tablespace_file, + 'fails_like' => + qr/extra_ts_file.*present on disk but not in the manifest/ + }, + { + 'name' => 'missing_file', + 'mutilate' => \&mutilate_missing_file, + 'fails_like' => + qr/pg_xact\/0000.*present in the manifest but not on disk/ + }, + { + 'name' => 'missing_tablespace', + 'mutilate' => \&mutilate_missing_tablespace, + 'fails_like' => + qr/pg_tblspc.*present in the manifest but not on disk/ + }, + { + 'name' => 'append_to_file', + 'mutilate' => \&mutilate_append_to_file, + 'fails_like' => + qr/has size \d+ on disk but size \d+ in the manifest/ + }, + { + 'name' => 'truncate_file', + 'mutilate' => \&mutilate_truncate_file, + 'fails_like' => + qr/has size 0 on disk but size \d+ in the manifest/ + }, + { + 'name' => 'replace_file', + 'mutilate' => \&mutilate_replace_file, + 'fails_like' => qr/checksum mismatch for file/ + }, + { + 'name' => 'bad_manifest', + 'mutilate' => \&mutilate_bad_manifest, + 'fails_like' => qr/manifest checksum mismatch/ + }, + { + 'name' => 'open_file_fails', + 'mutilate' => \&mutilate_open_file_fails, + 'fails_like' => qr/could not open file/, + 'skip_on_windows' => 1 + }, + { + 'name' => 'open_directory_fails', + 'mutilate' => \&mutilate_open_directory_fails, + 'fails_like' => qr/could not open directory/, + 'skip_on_windows' => 1 + }, + { + 'name' => 'search_directory_fails', + 'mutilate' => \&mutilate_search_directory_fails, + 'cleanup' => \&cleanup_search_directory_fails, + 'fails_like' => qr/could not stat file or directory/, + 'skip_on_windows' => 1 + } +); + +for my $scenario (@scenario) +{ + my $name = $scenario->{'name'}; + + SKIP: + { + skip "unix-style permissions not supported on Windows", 4 + if $scenario->{'skip_on_windows'} && $windows_os; + + # Take a backup and check that it validates OK. + my $backup_path = $master->backup_dir . '/' . $name; + my $backup_ts_path = TestLib::tempdir; + $master->command_ok(['pg_basebackup', '-D', $backup_path, '--no-sync', + '-T', "${source_ts_path}=${backup_ts_path}"], + "base backup ok"); + command_ok(['pg_validatebackup', $backup_path ], + "intact backup validated"); + + # Mutilate the backup in some way. + $scenario->{'mutilate'}->($backup_path); + + # Now check that the backup no longer validates. + command_fails_like(['pg_validatebackup', $backup_path ], + $scenario->{'fails_like'}, + "corrupt backup fails validation: $name"); + + # Run cleanup hook, if provided. + $scenario->{'cleanup'}->($backup_path) + if exists $scenario->{'cleanup'}; + + # Finally, use rmtree to reclaim space. + rmtree($backup_path); + } +} + +sub create_extra_file +{ + my ($backup_path, $relative_path) = @_; + my $pathname = "$backup_path/$relative_path"; + open(my $fh, '>', $pathname) || die "open $pathname: $!"; + print $fh "This is an extra file.\n"; + close($fh); +} + +# Add a file into the root directory of the backup. +sub mutilate_extra_file +{ + my ($backup_path) = @_; + create_extra_file($backup_path, "extra_file"); +} + +# Add a file inside the user-defined tablespace. +sub mutilate_extra_tablespace_file +{ + my ($backup_path) = @_; + my ($tsoid) = grep { $_ ne '.' && $_ ne '..' } + slurp_dir("$backup_path/pg_tblspc"); + my ($catvdir) = grep { $_ ne '.' && $_ ne '..' } + slurp_dir("$backup_path/pg_tblspc/$tsoid"); + my ($tsdboid) = grep { $_ ne '.' && $_ ne '..' } + slurp_dir("$backup_path/pg_tblspc/$tsoid/$catvdir"); + create_extra_file($backup_path, + "pg_tblspc/$tsoid/$catvdir/$tsdboid/extra_ts_file"); +} + +# Remove a file. +sub mutilate_missing_file +{ + my ($backup_path) = @_; + my $pathname = "$backup_path/pg_xact/0000"; + unlink($pathname) || die "$pathname: $!"; +} + +# Remove the symlink to the user-defined tablespace. +sub mutilate_missing_tablespace +{ + my ($backup_path) = @_; + my ($tsoid) = grep { $_ ne '.' && $_ ne '..' } + slurp_dir("$backup_path/pg_tblspc"); + my $pathname = "$backup_path/pg_tblspc/$tsoid"; + if ($windows_os) + { + rmdir($pathname) || die "$pathname: $!"; + } + else + { + unlink($pathname) || die "$pathname: $!"; + } +} + +# Append an additional bytes to a file. +sub mutilate_append_to_file +{ + my ($backup_path) = @_; + append_to_file "$backup_path/global/pg_control", 'x'; +} + +# Truncate a file to zero length. +sub mutilate_truncate_file +{ + my ($backup_path) = @_; + my $pathname = "$backup_path/global/pg_control"; + open(my $fh, '>', $pathname) || die "open $pathname: $!"; + close($fh); +} + +# Replace a file's contents without changing the length of the file. This is +# not a particularly efficient way to do this, so we pick a file that's +# expected to be short. +sub mutilate_replace_file +{ + my ($backup_path) = @_; + my $pathname = "$backup_path/PG_VERSION"; + my $contents = slurp_file($pathname); + open(my $fh, '>', $pathname) || die "open $pathname: $!"; + print $fh 'q' x length($contents); + close($fh); +} + +# Corrupt the backup manifest. +sub mutilate_bad_manifest +{ + my ($backup_path) = @_; + append_to_file "$backup_path/backup_manifest", "\n"; +} + +# Create a file that can't be opened. (This is skipped on Windows.) +sub mutilate_open_file_fails +{ + my ($backup_path) = @_; + my $pathname = "$backup_path/PG_VERSION"; + chmod(0, $pathname) || die "chmod $pathname: $!"; +} + +# Create a directory that can't be opened. (This is skipped on Windows.) +sub mutilate_open_directory_fails +{ + my ($backup_path) = @_; + my $pathname = "$backup_path/pg_subtrans"; + chmod(0, $pathname) || die "chmod $pathname: $!"; +} + +# Create a directory that can't be searched. (This is skipped on Windows.) +sub mutilate_search_directory_fails +{ + my ($backup_path) = @_; + my $pathname = "$backup_path/base"; + chmod(0400, $pathname) || die "chmod $pathname: $!"; +} + +# rmtree can't cope with a mode 400 directory, so change back to 700. +sub cleanup_search_directory_fails +{ + my ($backup_path) = @_; + my $pathname = "$backup_path/base"; + chmod(0700, $pathname) || die "chmod $pathname: $!"; +} diff --git a/src/bin/pg_validatebackup/t/004_options.pl b/src/bin/pg_validatebackup/t/004_options.pl new file mode 100644 index 00000000000..8f185626ed6 --- /dev/null +++ b/src/bin/pg_validatebackup/t/004_options.pl @@ -0,0 +1,89 @@ +# Verify the behavior of assorted pg_validatebackup options. + +use strict; +use warnings; +use Cwd; +use Config; +use File::Path qw(rmtree); +use PostgresNode; +use TestLib; +use Test::More tests => 25; + +# Start up the server and take a backup. +my $master = get_new_node('master'); +$master->init(allows_streaming => 1); +$master->start; +my $backup_path = $master->backup_dir . '/test_options'; +$master->command_ok(['pg_basebackup', '-D', $backup_path, '--no-sync' ], + "base backup ok"); + +# Verify that pg_validatebackup -q succeeds and produces no output. +my $stdout; +my $stderr; +my $result = IPC::Run::run ['pg_validatebackup', '-q', $backup_path ], + '>', \$stdout, '2>', \$stderr; +ok($result, "-q succeeds: exit code 0"); +is($stdout, '', "-q succeeds: no stdout"); +is($stderr, '', "-q succeeds: no stderr"); + +# Corrupt the PG_VERSION file. +my $version_pathname = "$backup_path/PG_VERSION"; +my $version_contents = slurp_file($version_pathname); +open(my $fh, '>', $version_pathname) || die "open $version_pathname: $!"; +print $fh 'q' x length($version_contents); +close($fh); + +# Verify that pg_validatebackup -q now fails. +command_fails_like(['pg_validatebackup', '-q', $backup_path ], + qr/checksum mismatch for file \"PG_VERSION\"/, + '-q checksum mismatch'); + +# Since we didn't change the length of the file, validation should succeed +# if we ignore checksums. Check that we get the right message, too. +command_like(['pg_validatebackup', '-s', $backup_path ], + qr/backup successfully verified/, + '-s skips checksumming'); + +# Validation should succeed if we ignore the problem file. +command_like(['pg_validatebackup', '-i', 'PG_VERSION', $backup_path ], + qr/backup successfully verified/, + '-i ignores problem file'); + +# PG_VERSION is already corrupt; let's try also removing all of pg_xact. +rmtree($backup_path . "/pg_xact"); + +# We're ignoring the problem with PG_VERSION, but not the problem with +# pg_xact, so validation should fail here. +command_fails_like(['pg_validatebackup', '-i', 'PG_VERSION', $backup_path ], + qr/pg_xact.*is present in the manifest but not on disk/, + '-i does not ignore all problems'); + +# If we use -i twice, we should be able to ignore all of the problems. +command_like(['pg_validatebackup', '-i', 'PG_VERSION', '-i', 'pg_xact', + $backup_path ], + qr/backup successfully verified/, + 'multiple -i options work'); + +# Verify that when -i is not used, both problems are reported. +$result = IPC::Run::run ['pg_validatebackup', $backup_path ], + '>', \$stdout, '2>', \$stderr; +ok(!$result, "multiple problems: fails"); +like($stderr, qr/pg_xact.*is present in the manifest but not on disk/, + "multiple problems: missing files reported"); +like($stderr, qr/checksum mismatch for file \"PG_VERSION\"/, + "multiple problems: checksum mismatch reported"); + +# Verify that when -e is used, only the problem detected first is reported. +$result = IPC::Run::run ['pg_validatebackup', '-e', $backup_path ], + '>', \$stdout, '2>', \$stderr; +ok(!$result, "-e reports 1 error: fails"); +like($stderr, qr/pg_xact.*is present in the manifest but not on disk/, + "-e reports 1 error: missing files reported"); +unlike($stderr, qr/checksum mismatch for file \"PG_VERSION\"/, + "-e reports 1 error: checksum mismatch not reported"); + +# Test valid manifest with nonexistent backup directory. +command_fails_like(['pg_validatebackup', '-m', "$backup_path/backup_manifest", + "$backup_path/fake" ], + qr/could not open directory/, + 'nonexistent backup directory'); diff --git a/src/bin/pg_validatebackup/t/005_bad_manifest.pl b/src/bin/pg_validatebackup/t/005_bad_manifest.pl new file mode 100644 index 00000000000..8f2a486c83c --- /dev/null +++ b/src/bin/pg_validatebackup/t/005_bad_manifest.pl @@ -0,0 +1,201 @@ +# Test the behavior of pg_validatebackup when the backup manifest has +# problems. + +use strict; +use warnings; +use Cwd; +use Config; +use PostgresNode; +use TestLib; +use Test::More tests => 58; + +my $tempdir = TestLib::tempdir; + +test_bad_manifest('input string ended unexpectedly', + qr/could not parse backup manifest: The input string ended unexpectedly/, + <<EOM); +{ +EOM + +test_parse_error('unexpected object end', <<EOM); +{} +EOM + +test_parse_error('unexpected array start', <<EOM); +[] +EOM + +test_parse_error('expected version indicator', <<EOM); +{"not-expected": 1} +EOM + +test_parse_error('unexpected manifest version', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": "phooey"} +EOM + +test_parse_error('unexpected scalar', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "Files": true} +EOM + +test_parse_error('unknown toplevel field', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "Oops": 1} +EOM + +test_parse_error('unexpected object start', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "Files": {}} +EOM + +test_parse_error('missing pathname', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "Files": [{}]} +EOM + +test_parse_error('both pathname and encoded pathname', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "Files": [ + {"Path": "x", "Encoded-Path": "1234"} +]} +EOM + +test_parse_error('unexpected file field', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "Files": [ + {"Oops": 1} +]} +EOM + +test_parse_error('missing size', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "Files": [ + {"Path": "x"} +]} +EOM + +test_parse_error('file size is not an integer', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "Files": [ + {"Path": "x", "Size": "Oops"} +]} +EOM + +test_parse_error('unable to decode filename', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "Files": [ + {"Encoded-Path": "123", "Size": 0} +]} +EOM + +test_fatal_error('duplicate pathname in backup manifest', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "Files": [ + {"Path": "x", "Size": 0}, + {"Path": "x", "Size": 0} +]} +EOM + +test_parse_error('checksum without algorithm', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "Files": [ + {"Path": "x", "Size": 100, "Checksum": "Oops"} +]} +EOM + +test_fatal_error('unrecognized checksum algorithm', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "Files": [ + {"Path": "x", "Size": 100, "Checksum-Algorithm": "Oops", "Checksum": "00"} +]} +EOM + +test_fatal_error('invalid checksum for file', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "Files": [ + {"Path": "x", "Size": 100, "Checksum-Algorithm": "CRC32C", "Checksum": "0"} +]} +EOM + +test_parse_error('missing start LSN', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "WAL-Ranges": [ + {"Timeline": 1} +]} +EOM + +test_parse_error('missing end LSN', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "WAL-Ranges": [ + {"Timeline": 1, "Start-LSN": "0/0"} +]} +EOM + +test_parse_error('unexpected wal range field', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "WAL-Ranges": [ + {"Oops": 1} +]} +EOM + +test_parse_error('missing timeline', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "WAL-Ranges": [ + {} +]} +EOM + +test_parse_error('unexpected object end', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "WAL-Ranges": [ + {"Timeline": 1, "Start-LSN": "0/0", "End-LSN": "0/0"} +]} +EOM + +test_parse_error('timeline is not an integer', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "WAL-Ranges": [ + {"Timeline": true, "Start-LSN": "0/0", "End-LSN": "0/0"} +]} +EOM + +test_parse_error('unable to parse start LSN', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "WAL-Ranges": [ + {"Timeline": 1, "Start-LSN": "oops", "End-LSN": "0/0"} +]} +EOM + +test_parse_error('unable to parse end LSN', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "WAL-Ranges": [ + {"Timeline": 1, "Start-LSN": "0/0", "End-LSN": "oops"} +]} +EOM + +test_parse_error('expected at least 2 lines', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "Files": [], "Manifest-Checksum": null} +EOM + +my $manifest_without_newline = <<EOM; +{"PostgreSQL-Backup-Manifest-Version": 1, + "Files": [], + "Manifest-Checksum": null} +EOM +chomp($manifest_without_newline); +test_parse_error('last line not newline-terminated', + $manifest_without_newline); + +test_fatal_error('invalid manifest checksum', <<EOM); +{"PostgreSQL-Backup-Manifest-Version": 1, "Files": [], + "Manifest-Checksum": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890-"} +EOM + +sub test_parse_error +{ + my ($test_name, $manifest_contents) = @_; + + test_bad_manifest($test_name, + qr/could not parse backup manifest: $test_name/, + $manifest_contents); +} + +sub test_fatal_error +{ + my ($test_name, $manifest_contents) = @_; + + test_bad_manifest($test_name, + qr/fatal: $test_name/, + $manifest_contents); +} + +sub test_bad_manifest +{ + my ($test_name, $regexp, $manifest_contents) = @_; + + open(my $fh, '>', "$tempdir/backup_manifest") || die "open: $!"; + print $fh $manifest_contents; + close($fh); + + command_fails_like(['pg_validatebackup', $tempdir], $regexp, + $test_name); +} diff --git a/src/bin/pg_validatebackup/t/006_encoding.pl b/src/bin/pg_validatebackup/t/006_encoding.pl new file mode 100644 index 00000000000..5e3e7152a52 --- /dev/null +++ b/src/bin/pg_validatebackup/t/006_encoding.pl @@ -0,0 +1,27 @@ +# Verify that pg_validatebackup handles hex-encoded filenames correctly. + +use strict; +use warnings; +use Cwd; +use Config; +use PostgresNode; +use TestLib; +use Test::More tests => 5; + +my $master = get_new_node('master'); +$master->init(allows_streaming => 1); +$master->start; +my $backup_path = $master->backup_dir . '/test_encoding'; +$master->command_ok(['pg_basebackup', '-D', $backup_path, '--no-sync', + '--manifest-force-encode' ], + "backup ok with forced hex encoding"); + +my $manifest = slurp_file("$backup_path/backup_manifest"); +my $count_of_encoded_path_in_manifest = + (() = $manifest =~ /Encoded-Path/mig); +cmp_ok($count_of_encoded_path_in_manifest, '>', 100, + "many paths are encoded in the manifest"); + +command_like(['pg_validatebackup', '-s', $backup_path ], + qr/backup successfully verified/, + 'backup with forced encoding validated'); diff --git a/src/bin/pg_validatebackup/t/007_wal.pl b/src/bin/pg_validatebackup/t/007_wal.pl new file mode 100644 index 00000000000..b755e0f5e7f --- /dev/null +++ b/src/bin/pg_validatebackup/t/007_wal.pl @@ -0,0 +1,55 @@ +# Test pg_validatebackup's WAL validation. + +use strict; +use warnings; +use Cwd; +use Config; +use File::Path qw(rmtree); +use PostgresNode; +use TestLib; +use Test::More tests => 7; + +# Start up the server and take a backup. +my $master = get_new_node('master'); +$master->init(allows_streaming => 1); +$master->start; +my $backup_path = $master->backup_dir . '/test_wal'; +$master->command_ok(['pg_basebackup', '-D', $backup_path, '--no-sync' ], + "base backup ok"); + +# Rename pg_wal. +my $original_pg_wal = $backup_path . '/pg_wal'; +my $relocated_pg_wal = $master->backup_dir . '/relocated_pg_wal'; +rename($original_pg_wal, $relocated_pg_wal) || die "rename pg_wal: $!"; + +# WAL validation should fail. +command_fails_like(['pg_validatebackup', $backup_path ], + qr/WAL parsing failed for timeline 1/, + 'missing pg_wal causes failure'); + +# Should work if we skip WAL verification. +command_ok(['pg_validatebackup', '-n', $backup_path ], + 'missing pg_wal OK if not verifying WAL'); + +# Should also work if we specify the correct WAL location. +command_ok(['pg_validatebackup', '-w', $relocated_pg_wal, $backup_path ], + '-w can be used to specify WAL directory'); + +# Move directory back to original location. +rename($relocated_pg_wal, $original_pg_wal) || die "rename pg_wal back: $!"; + +# Get a list of files in that directory that look like WAL files. +my @walfiles = grep { /^[0-9A-F]{24}$/ } slurp_dir($original_pg_wal); + +# Replace the contents of one of the files with garbage of equal length. +my $wal_corruption_target = $original_pg_wal . '/' . $walfiles[0]; +my $wal_size = -s $wal_corruption_target; +open(my $fh, '>', $wal_corruption_target) + || die "open $wal_corruption_target: $!"; +print $fh 'w' x $wal_size; +close($fh); + +# WAL validation should fail. +command_fails_like(['pg_validatebackup', $backup_path ], + qr/WAL parsing failed for timeline 1/, + 'corrupt WAL file causes failure'); |